From 7e0c55818721b78109e2bb58bf14f814036bf06d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Sat, 26 Nov 2022 02:29:56 +0100 Subject: [PATCH] tests: move integration tests to separate modules (#16816) --- cli/tests/bench_tests.rs | 200 + cli/tests/bundle_tests.rs | 473 ++ cli/tests/cache_tests.rs | 102 + cli/tests/cert_tests.rs | 321 ++ cli/tests/check_tests.rs | 212 + cli/tests/compile_tests.rs | 536 ++ cli/tests/coverage_tests.rs | 320 ++ cli/tests/doc_tests.rs | 79 + cli/tests/eval_tests.rs | 86 + cli/tests/flags_tests.rs | 50 + cli/tests/fmt_tests.rs | 257 + cli/tests/info_tests.rs | 136 + cli/tests/init_tests.rs | 163 + cli/tests/inspector_tests.rs | 1123 ++++ cli/tests/install_tests.rs | 220 + cli/tests/integration/bench_tests.rs | 195 - cli/tests/integration/bundle_tests.rs | 468 -- cli/tests/integration/cache_tests.rs | 99 - cli/tests/integration/cert_tests.rs | 316 -- cli/tests/integration/check_tests.rs | 210 - cli/tests/integration/compile_tests.rs | 533 -- cli/tests/integration/coverage_tests.rs | 315 -- cli/tests/integration/doc_tests.rs | 74 - cli/tests/integration/eval_tests.rs | 80 - cli/tests/integration/flags_tests.rs | 45 - cli/tests/integration/fmt_tests.rs | 248 - cli/tests/integration/info_tests.rs | 132 - cli/tests/integration/init_tests.rs | 159 - cli/tests/integration/inspector_tests.rs | 1117 ---- cli/tests/integration/install_tests.rs | 210 - cli/tests/integration/lint_tests.rs | 133 - cli/tests/integration/lsp_tests.rs | 6265 --------------------- cli/tests/integration/mod.rs | 95 - cli/tests/integration/npm_tests.rs | 1521 ------ cli/tests/integration/repl_tests.rs | 877 --- cli/tests/integration/run_tests.rs | 3660 ------------- cli/tests/integration/task_tests.rs | 132 - cli/tests/integration/test_tests.rs | 449 -- cli/tests/integration/upgrade_tests.rs | 193 - cli/tests/integration/vendor_tests.rs | 574 -- cli/tests/integration/watcher_tests.rs | 1234 ----- cli/tests/integration/worker_tests.rs | 112 - cli/tests/integration_tests.rs | 6 - cli/tests/js_unit_tests.rs | 45 + cli/tests/lint_tests.rs | 139 + cli/tests/lsp_tests.rs | 6276 ++++++++++++++++++++++ cli/tests/npm_tests.rs | 1526 ++++++ cli/tests/repl_tests.rs | 884 +++ cli/tests/run_tests.rs | 3681 +++++++++++++ cli/tests/task_tests.rs | 136 + cli/tests/test_tests.rs | 453 ++ cli/tests/upgrade_tests.rs | 197 + cli/tests/vendor_tests.rs | 583 ++ cli/tests/watcher_tests.rs | 1247 +++++ cli/tests/worker_tests.rs | 116 + 55 files changed, 19561 insertions(+), 19452 deletions(-) create mode 100644 cli/tests/bench_tests.rs create mode 100644 cli/tests/bundle_tests.rs create mode 100644 cli/tests/cache_tests.rs create mode 100644 cli/tests/cert_tests.rs create mode 100644 cli/tests/check_tests.rs create mode 100644 cli/tests/compile_tests.rs create mode 100644 cli/tests/coverage_tests.rs create mode 100644 cli/tests/doc_tests.rs create mode 100644 cli/tests/eval_tests.rs create mode 100644 cli/tests/flags_tests.rs create mode 100644 cli/tests/fmt_tests.rs create mode 100644 cli/tests/info_tests.rs create mode 100644 cli/tests/init_tests.rs create mode 100644 cli/tests/inspector_tests.rs create mode 100644 cli/tests/install_tests.rs delete mode 100644 cli/tests/integration/bench_tests.rs delete mode 100644 cli/tests/integration/bundle_tests.rs delete mode 100644 cli/tests/integration/cache_tests.rs delete mode 100644 cli/tests/integration/cert_tests.rs delete mode 100644 cli/tests/integration/check_tests.rs delete mode 100644 cli/tests/integration/compile_tests.rs delete mode 100644 cli/tests/integration/coverage_tests.rs delete mode 100644 cli/tests/integration/doc_tests.rs delete mode 100644 cli/tests/integration/eval_tests.rs delete mode 100644 cli/tests/integration/flags_tests.rs delete mode 100644 cli/tests/integration/fmt_tests.rs delete mode 100644 cli/tests/integration/info_tests.rs delete mode 100644 cli/tests/integration/init_tests.rs delete mode 100644 cli/tests/integration/inspector_tests.rs delete mode 100644 cli/tests/integration/install_tests.rs delete mode 100644 cli/tests/integration/lint_tests.rs delete mode 100644 cli/tests/integration/lsp_tests.rs delete mode 100644 cli/tests/integration/npm_tests.rs delete mode 100644 cli/tests/integration/repl_tests.rs delete mode 100644 cli/tests/integration/run_tests.rs delete mode 100644 cli/tests/integration/task_tests.rs delete mode 100644 cli/tests/integration/test_tests.rs delete mode 100644 cli/tests/integration/upgrade_tests.rs delete mode 100644 cli/tests/integration/vendor_tests.rs delete mode 100644 cli/tests/integration/watcher_tests.rs delete mode 100644 cli/tests/integration/worker_tests.rs delete mode 100644 cli/tests/integration_tests.rs create mode 100644 cli/tests/js_unit_tests.rs create mode 100644 cli/tests/lint_tests.rs create mode 100644 cli/tests/lsp_tests.rs create mode 100644 cli/tests/npm_tests.rs create mode 100644 cli/tests/repl_tests.rs create mode 100644 cli/tests/run_tests.rs create mode 100644 cli/tests/task_tests.rs create mode 100644 cli/tests/test_tests.rs create mode 100644 cli/tests/upgrade_tests.rs create mode 100644 cli/tests/vendor_tests.rs create mode 100644 cli/tests/watcher_tests.rs create mode 100644 cli/tests/worker_tests.rs diff --git a/cli/tests/bench_tests.rs b/cli/tests/bench_tests.rs new file mode 100644 index 0000000000..77c1e2a7ef --- /dev/null +++ b/cli/tests/bench_tests.rs @@ -0,0 +1,200 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +use deno_core::url::Url; +use test_util as util; + +mod bench { + use super::*; + + itest!(overloads { + args: "bench bench/overloads.ts", + exit_code: 0, + output: "bench/overloads.out", + }); + + itest!(meta { + args: "bench bench/meta.ts", + exit_code: 0, + output: "bench/meta.out", + }); + + itest!(pass { + args: "bench bench/pass.ts", + exit_code: 0, + output: "bench/pass.out", + }); + + itest!(ignore { + args: "bench bench/ignore.ts", + exit_code: 0, + output: "bench/ignore.out", + }); + + itest!(ignore_permissions { + args: "bench bench/ignore_permissions.ts", + exit_code: 0, + output: "bench/ignore_permissions.out", + }); + + itest!(fail { + args: "bench bench/fail.ts", + exit_code: 1, + output: "bench/fail.out", + }); + + itest!(collect { + args: "bench --ignore=bench/collect/ignore bench/collect", + exit_code: 0, + output: "bench/collect.out", + }); + + itest!(load_unload { + args: "bench bench/load_unload.ts", + exit_code: 0, + output: "bench/load_unload.out", + }); + + itest!(interval { + args: "bench bench/interval.ts", + exit_code: 0, + output: "bench/interval.out", + }); + + itest!(quiet { + args: "bench --quiet bench/quiet.ts", + exit_code: 0, + output: "bench/quiet.out", + }); + + itest!(only { + args: "bench bench/only.ts", + exit_code: 1, + output: "bench/only.out", + }); + + itest!(multifile_summary { + args: "bench bench/group_baseline.ts bench/pass.ts bench/group_baseline.ts", + exit_code: 0, + output: "bench/multifile_summary.out", + }); + + itest!(no_check { + args: "bench --no-check bench/no_check.ts", + exit_code: 1, + output: "bench/no_check.out", + }); + + itest!(allow_all { + args: "bench --allow-all bench/allow_all.ts", + exit_code: 0, + output: "bench/allow_all.out", + }); + + itest!(allow_none { + args: "bench bench/allow_none.ts", + exit_code: 1, + output: "bench/allow_none.out", + }); + + itest!(exit_sanitizer { + args: "bench bench/exit_sanitizer.ts", + output: "bench/exit_sanitizer.out", + exit_code: 1, + }); + + itest!(clear_timeout { + args: "bench bench/clear_timeout.ts", + exit_code: 0, + output: "bench/clear_timeout.out", + }); + + itest!(finally_timeout { + args: "bench bench/finally_timeout.ts", + exit_code: 1, + output: "bench/finally_timeout.out", + }); + + itest!(group_baseline { + args: "bench bench/group_baseline.ts", + exit_code: 0, + output: "bench/group_baseline.out", + }); + + itest!(unresolved_promise { + args: "bench bench/unresolved_promise.ts", + exit_code: 1, + output: "bench/unresolved_promise.out", + }); + + itest!(unhandled_rejection { + args: "bench bench/unhandled_rejection.ts", + exit_code: 1, + output: "bench/unhandled_rejection.out", + }); + + itest!(filter { + args: "bench --filter=foo bench/filter", + exit_code: 0, + output: "bench/filter.out", + }); + + itest!(no_prompt_by_default { + args: "bench --quiet bench/no_prompt_by_default.ts", + exit_code: 1, + output: "bench/no_prompt_by_default.out", + }); + + itest!(no_prompt_with_denied_perms { + args: "bench --quiet --allow-read bench/no_prompt_with_denied_perms.ts", + exit_code: 1, + output: "bench/no_prompt_with_denied_perms.out", + }); + + itest!(check_local_by_default { + args: "bench --quiet bench/check_local_by_default.ts", + output: "bench/check_local_by_default.out", + http_server: true, + }); + + itest!(check_local_by_default2 { + args: "bench --quiet bench/check_local_by_default2.ts", + output: "bench/check_local_by_default2.out", + http_server: true, + exit_code: 1, + }); + + #[test] + fn recursive_permissions_pledge() { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bench") + .arg("bench/recursive_permissions_pledge.js") + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + assert!(String::from_utf8(output.stderr).unwrap().contains( + "pledge test permissions called before restoring previous pledge" + )); + } + + #[test] + fn file_protocol() { + let file_url = + Url::from_file_path(util::testdata_path().join("bench/file_protocol.ts")) + .unwrap() + .to_string(); + + (util::CheckOutputIntegrationTest { + args_vec: vec!["bench", &file_url], + exit_code: 0, + output: "bench/file_protocol.out", + ..Default::default() + }) + .run(); + } +} diff --git a/cli/tests/bundle_tests.rs b/cli/tests/bundle_tests.rs new file mode 100644 index 0000000000..c3c9c396a0 --- /dev/null +++ b/cli/tests/bundle_tests.rs @@ -0,0 +1,473 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +use test_util as util; +use test_util::assert_contains; +use test_util::assert_ends_with; +use test_util::TempDir; + +mod bundle { + use super::*; + + #[test] + fn bundle_exports() { + // First we have to generate a bundle of some module that has exports. + let mod1 = util::testdata_path().join("subdir/mod1.ts"); + assert!(mod1.is_file()); + let t = TempDir::new(); + let bundle = t.path().join("mod1.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bundle") + .arg(mod1) + .arg(&bundle) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + assert!(bundle.is_file()); + + // Now we try to use that bundle from another module. + let test = t.path().join("test.js"); + std::fs::write( + &test, + " + import { printHello3 } from \"./mod1.bundle.js\"; + printHello3(); ", + ) + .unwrap(); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg(&test) + .output() + .unwrap(); + // check the output of the test.ts program. + assert_ends_with!( + std::str::from_utf8(&output.stdout).unwrap().trim(), + "Hello", + ); + assert_eq!(output.stderr, b""); + } + + #[test] + fn bundle_exports_no_check() { + // First we have to generate a bundle of some module that has exports. + let mod1 = util::testdata_path().join("subdir/mod1.ts"); + assert!(mod1.is_file()); + let t = TempDir::new(); + let bundle = t.path().join("mod1.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bundle") + .arg(mod1) + .arg(&bundle) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + assert!(bundle.is_file()); + + // Now we try to use that bundle from another module. + let test = t.path().join("test.js"); + std::fs::write( + &test, + " + import { printHello3 } from \"./mod1.bundle.js\"; + printHello3(); ", + ) + .unwrap(); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg(&test) + .output() + .unwrap(); + // check the output of the test.ts program. + assert_ends_with!( + std::str::from_utf8(&output.stdout).unwrap().trim(), + "Hello", + ); + assert_eq!(output.stderr, b""); + } + + #[test] + fn bundle_circular() { + // First we have to generate a bundle of some module that has exports. + let circular1_path = util::testdata_path().join("subdir/circular1.ts"); + assert!(circular1_path.is_file()); + let t = TempDir::new(); + let bundle_path = t.path().join("circular1.bundle.js"); + + // run this twice to ensure it works even when cached + for _ in 0..2 { + let mut deno = util::deno_cmd_with_deno_dir(&t) + .current_dir(util::testdata_path()) + .arg("bundle") + .arg(&circular1_path) + .arg(&bundle_path) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + assert!(bundle_path.is_file()); + } + + let output = util::deno_cmd_with_deno_dir(&t) + .current_dir(util::testdata_path()) + .arg("run") + .arg(&bundle_path) + .output() + .unwrap(); + // check the output of the the bundle program. + assert_ends_with!( + std::str::from_utf8(&output.stdout).unwrap().trim(), + "f2\nf1", + ); + assert_eq!(output.stderr, b""); + } + + #[test] + fn bundle_single_module() { + // First we have to generate a bundle of some module that has exports. + let single_module = util::testdata_path().join("subdir/single_module.ts"); + assert!(single_module.is_file()); + let t = TempDir::new(); + let bundle = t.path().join("single_module.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bundle") + .arg(single_module) + .arg(&bundle) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + assert!(bundle.is_file()); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg(&bundle) + .output() + .unwrap(); + // check the output of the the bundle program. + assert_ends_with!( + std::str::from_utf8(&output.stdout).unwrap().trim(), + "Hello world!", + ); + assert_eq!(output.stderr, b""); + } + + #[test] + fn bundle_tla() { + // First we have to generate a bundle of some module that has exports. + let tla_import = util::testdata_path().join("subdir/tla.ts"); + assert!(tla_import.is_file()); + let t = TempDir::new(); + let bundle = t.path().join("tla.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bundle") + .arg(tla_import) + .arg(&bundle) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + assert!(bundle.is_file()); + + // Now we try to use that bundle from another module. + let test = t.path().join("test.js"); + std::fs::write( + &test, + " + import { foo } from \"./tla.bundle.js\"; + console.log(foo); ", + ) + .unwrap(); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg(&test) + .output() + .unwrap(); + // check the output of the test.ts program. + assert_ends_with!( + std::str::from_utf8(&output.stdout).unwrap().trim(), + "Hello", + ); + assert_eq!(output.stderr, b""); + } + + #[test] + fn bundle_js() { + // First we have to generate a bundle of some module that has exports. + let mod6 = util::testdata_path().join("subdir/mod6.js"); + assert!(mod6.is_file()); + let t = TempDir::new(); + let bundle = t.path().join("mod6.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bundle") + .arg(mod6) + .arg(&bundle) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + assert!(bundle.is_file()); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg(&bundle) + .output() + .unwrap(); + // check that nothing went to stderr + assert_eq!(output.stderr, b""); + } + + #[test] + fn bundle_dynamic_import() { + let _g = util::http_server(); + let dynamic_import = util::testdata_path().join("bundle/dynamic_import.ts"); + assert!(dynamic_import.is_file()); + let t = TempDir::new(); + let output_path = t.path().join("bundle_dynamic_import.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bundle") + .arg(dynamic_import) + .arg(&output_path) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + assert!(output_path.is_file()); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-net") + .arg("--quiet") + .arg(&output_path) + .output() + .unwrap(); + // check the output of the test.ts program. + assert_ends_with!( + std::str::from_utf8(&output.stdout).unwrap().trim(), + "Hello", + ); + assert_eq!(output.stderr, b""); + } + + #[test] + fn bundle_import_map() { + let import = util::testdata_path().join("bundle/import_map/main.ts"); + let import_map_path = + util::testdata_path().join("bundle/import_map/import_map.json"); + assert!(import.is_file()); + let t = TempDir::new(); + let output_path = t.path().join("import_map.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bundle") + .arg("--import-map") + .arg(import_map_path) + .arg(import) + .arg(&output_path) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + assert!(output_path.is_file()); + + // Now we try to use that bundle from another module. + let test = t.path().join("test.js"); + std::fs::write( + &test, + " + import { printHello3 } from \"./import_map.bundle.js\"; + printHello3(); ", + ) + .unwrap(); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--check") + .arg(&test) + .output() + .unwrap(); + // check the output of the test.ts program. + assert_ends_with!( + std::str::from_utf8(&output.stdout).unwrap().trim(), + "Hello", + ); + assert_eq!(output.stderr, b""); + } + + #[test] + fn bundle_import_map_no_check() { + let import = util::testdata_path().join("bundle/import_map/main.ts"); + let import_map_path = + util::testdata_path().join("bundle/import_map/import_map.json"); + assert!(import.is_file()); + let t = TempDir::new(); + let output_path = t.path().join("import_map.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bundle") + .arg("--import-map") + .arg(import_map_path) + .arg(import) + .arg(&output_path) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + assert!(output_path.is_file()); + + // Now we try to use that bundle from another module. + let test = t.path().join("test.js"); + std::fs::write( + &test, + " + import { printHello3 } from \"./import_map.bundle.js\"; + printHello3(); ", + ) + .unwrap(); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg(&test) + .output() + .unwrap(); + // check the output of the test.ts program. + assert_ends_with!( + std::str::from_utf8(&output.stdout).unwrap().trim(), + "Hello", + ); + assert_eq!(output.stderr, b""); + } + + #[test] + fn bundle_json_module() { + // First we have to generate a bundle of some module that has exports. + let mod7 = util::testdata_path().join("subdir/mod7.js"); + assert!(mod7.is_file()); + let t = TempDir::new(); + let bundle = t.path().join("mod7.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bundle") + .arg(mod7) + .arg(&bundle) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + assert!(bundle.is_file()); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg(&bundle) + .output() + .unwrap(); + // check that nothing went to stderr + assert_eq!(output.stderr, b""); + // ensure the output looks right + assert_contains!(String::from_utf8(output.stdout).unwrap(), "with space",); + } + + #[test] + fn bundle_json_module_escape_sub() { + // First we have to generate a bundle of some module that has exports. + let mod8 = util::testdata_path().join("subdir/mod8.js"); + assert!(mod8.is_file()); + let t = TempDir::new(); + let bundle = t.path().join("mod8.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bundle") + .arg(mod8) + .arg(&bundle) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + assert!(bundle.is_file()); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg(&bundle) + .output() + .unwrap(); + // check that nothing went to stderr + assert_eq!(output.stderr, b""); + // make sure the output looks right and the escapes were effective + assert_contains!( + String::from_utf8(output.stdout).unwrap(), + "${globalThis}`and string literal`", + ); + } + + itest!(lockfile_check_error { + args: "bundle --lock=bundle/lockfile/check_error.json http://127.0.0.1:4545/subdir/mod1.ts", + output: "bundle/lockfile/check_error.out", + exit_code: 10, + http_server: true, +}); + + itest!(bundle { + args: "bundle subdir/mod1.ts", + output: "bundle/bundle.test.out", + }); + + itest!(bundle_jsx { + args: "bundle run/jsx_import_from_ts.ts", + output: "bundle/jsx.out", + }); + + itest!(error_bundle_with_bare_import { + args: "bundle bundle/bare_imports/error_with_bare_import.ts", + output: "bundle/bare_imports/error_with_bare_import.ts.out", + exit_code: 1, + }); + + itest!(ts_decorators_bundle { + args: "bundle bundle/decorators/ts_decorators.ts", + output: "bundle/decorators/ts_decorators.out", + }); + + itest!(bundle_export_specifier_with_alias { + args: "bundle bundle/file_tests-fixture16.ts", + output: "bundle/fixture16.out", + }); + + itest!(bundle_ignore_directives { + args: "bundle subdir/mod1.ts", + output: "bundle/ignore_directives.test.out", + }); + + itest!(check_local_by_default_no_errors { + args: "bundle --quiet bundle/check_local_by_default/no_errors.ts", + output: "bundle/check_local_by_default/no_errors.out", + http_server: true, + }); + + itest!(check_local_by_default_type_error { + args: "bundle --quiet bundle/check_local_by_default/type_error.ts", + output: "bundle/check_local_by_default/type_error.out", + http_server: true, + exit_code: 1, + }); +} diff --git a/cli/tests/cache_tests.rs b/cli/tests/cache_tests.rs new file mode 100644 index 0000000000..c23ee8e5c8 --- /dev/null +++ b/cli/tests/cache_tests.rs @@ -0,0 +1,102 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +mod cache { + use super::*; + itest!(_036_import_map_fetch { + args: + "cache --quiet --reload --import-map=import_maps/import_map.json import_maps/test.ts", + output: "cache/036_import_map_fetch.out", + }); + + itest!(_037_fetch_multiple { + args: "cache --reload --check=all run/fetch/test.ts run/fetch/other.ts", + http_server: true, + output: "cache/037_fetch_multiple.out", + }); + + itest!(_095_cache_with_bare_import { + args: "cache cache/095_cache_with_bare_import.ts", + output: "cache/095_cache_with_bare_import.ts.out", + exit_code: 1, + }); + + itest!(cache_extensionless { + args: "cache --reload --check=all http://localhost:4545/subdir/no_js_ext", + output: "cache/cache_extensionless.out", + http_server: true, + }); + + itest!(cache_random_extension { + args: + "cache --reload --check=all http://localhost:4545/subdir/no_js_ext@1.0.0", + output: "cache/cache_random_extension.out", + http_server: true, + }); + + itest!(performance_stats { + args: "cache --reload --check=all --log-level debug run/002_hello.ts", + output: "cache/performance_stats.out", + }); + + itest!(redirect_cache { + http_server: true, + args: + "cache --reload --check=all http://localhost:4548/subdir/redirects/a.ts", + output: "cache/redirect_cache.out", + }); + + itest!(ignore_require { + args: "cache --reload --no-check cache/ignore_require.js", + output_str: Some(""), + exit_code: 0, + }); + + // This test only runs on linux, because it hardcodes the XDG_CACHE_HOME env var + // which is only used on linux. + #[cfg(target_os = "linux")] + #[test] + fn relative_home_dir() { + use test_util as util; + use test_util::TempDir; + + let deno_dir = TempDir::new_in(&util::testdata_path()); + let path = deno_dir.path().strip_prefix(util::testdata_path()).unwrap(); + + let mut deno_cmd = util::deno_cmd(); + let output = deno_cmd + .current_dir(util::testdata_path()) + .env("XDG_CACHE_HOME", path) + .env_remove("HOME") + .env_remove("DENO_DIR") + .arg("cache") + .arg("--reload") + .arg("--no-check") + .arg("run/002_hello.ts") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b""); + } + + itest!(check_local_by_default { + args: "cache --quiet cache/check_local_by_default.ts", + output: "cache/check_local_by_default.out", + http_server: true, + }); + + itest!(check_local_by_default2 { + args: "cache --quiet cache/check_local_by_default2.ts", + output: "cache/check_local_by_default2.out", + http_server: true, + }); + + itest!(json_import { + // should not error + args: "cache --quiet cache/json_import/main.ts", + }); +} diff --git a/cli/tests/cert_tests.rs b/cli/tests/cert_tests.rs new file mode 100644 index 0000000000..e3c17b8908 --- /dev/null +++ b/cli/tests/cert_tests.rs @@ -0,0 +1,321 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +use deno_runtime::deno_net::ops_tls::TlsStream; +use deno_runtime::deno_tls::rustls; +use deno_runtime::deno_tls::rustls_pemfile; +use std::io::BufReader; +use std::io::Cursor; +use std::io::Read; +use std::process::Command; +use std::sync::Arc; +use test_util as util; +use test_util::TempDir; +use tokio::task::LocalSet; + +mod cert { + use super::*; + itest_flaky!(cafile_url_imports { + args: + "run --quiet --reload --cert tls/RootCA.pem cert/cafile_url_imports.ts", + output: "cert/cafile_url_imports.ts.out", + http_server: true, + }); + + itest_flaky!(cafile_ts_fetch { + args: + "run --quiet --reload --allow-net --cert tls/RootCA.pem cert/cafile_ts_fetch.ts", + output: "cert/cafile_ts_fetch.ts.out", + http_server: true, + }); + + itest_flaky!(cafile_eval { + args: "eval --cert tls/RootCA.pem fetch('https://localhost:5545/cert/cafile_ts_fetch.ts.out').then(r=>r.text()).then(t=>console.log(t.trimEnd()))", + output: "cert/cafile_ts_fetch.ts.out", + http_server: true, + }); + + itest_flaky!(cafile_info { + args: + "info --quiet --cert tls/RootCA.pem https://localhost:5545/cert/cafile_info.ts", + output: "cert/cafile_info.ts.out", + http_server: true, + }); + + itest_flaky!(cafile_url_imports_unsafe_ssl { + args: "run --quiet --reload --unsafely-ignore-certificate-errors=localhost cert/cafile_url_imports.ts", + output: "cert/cafile_url_imports_unsafe_ssl.ts.out", + http_server: true, + }); + + itest_flaky!(cafile_ts_fetch_unsafe_ssl { + args: + "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors cert/cafile_ts_fetch.ts", + output: "cert/cafile_ts_fetch_unsafe_ssl.ts.out", + http_server: true, + }); + + itest!(deno_land_unsafe_ssl { + args: + "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=deno.land cert/deno_land_unsafe_ssl.ts", + output: "cert/deno_land_unsafe_ssl.ts.out", + }); + + itest!(ip_address_unsafe_ssl { + args: + "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=1.1.1.1 cert/ip_address_unsafe_ssl.ts", + output: "cert/ip_address_unsafe_ssl.ts.out", + }); + + itest!(localhost_unsafe_ssl { + args: + "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=deno.land cert/cafile_url_imports.ts", + output: "cert/localhost_unsafe_ssl.ts.out", + http_server: true, + exit_code: 1, + }); + + #[flaky_test::flaky_test] + fn cafile_env_fetch() { + use deno_core::url::Url; + let _g = util::http_server(); + let deno_dir = TempDir::new(); + let module_url = + Url::parse("https://localhost:5545/cert/cafile_url_imports.ts").unwrap(); + let cafile = util::testdata_path().join("tls/RootCA.pem"); + let output = Command::new(util::deno_exe_path()) + .env("DENO_DIR", deno_dir.path()) + .env("DENO_CERT", cafile) + .current_dir(util::testdata_path()) + .arg("cache") + .arg(module_url.to_string()) + .output() + .expect("Failed to spawn script"); + assert!(output.status.success()); + } + + #[flaky_test::flaky_test] + fn cafile_fetch() { + use deno_core::url::Url; + let _g = util::http_server(); + let deno_dir = TempDir::new(); + let module_url = + Url::parse("http://localhost:4545/cert/cafile_url_imports.ts").unwrap(); + let cafile = util::testdata_path().join("tls/RootCA.pem"); + let output = Command::new(util::deno_exe_path()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("cache") + .arg("--cert") + .arg(cafile) + .arg(module_url.to_string()) + .output() + .expect("Failed to spawn script"); + assert!(output.status.success()); + let out = std::str::from_utf8(&output.stdout).unwrap(); + assert_eq!(out, ""); + } + + #[flaky_test::flaky_test] + fn cafile_install_remote_module() { + let _g = util::http_server(); + let temp_dir = TempDir::new(); + let bin_dir = temp_dir.path().join("bin"); + std::fs::create_dir(&bin_dir).unwrap(); + let deno_dir = TempDir::new(); + let cafile = util::testdata_path().join("tls/RootCA.pem"); + + let install_output = Command::new(util::deno_exe_path()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("install") + .arg("--cert") + .arg(cafile) + .arg("--root") + .arg(temp_dir.path()) + .arg("-n") + .arg("echo_test") + .arg("https://localhost:5545/echo.ts") + .output() + .expect("Failed to spawn script"); + println!("{}", std::str::from_utf8(&install_output.stdout).unwrap()); + eprintln!("{}", std::str::from_utf8(&install_output.stderr).unwrap()); + assert!(install_output.status.success()); + + let mut echo_test_path = bin_dir.join("echo_test"); + if cfg!(windows) { + echo_test_path = echo_test_path.with_extension("cmd"); + } + assert!(echo_test_path.exists()); + + let output = Command::new(echo_test_path) + .current_dir(temp_dir.path()) + .arg("foo") + .env("PATH", util::target_dir()) + .output() + .expect("failed to spawn script"); + let stdout = std::str::from_utf8(&output.stdout).unwrap().trim(); + assert!(stdout.ends_with("foo")); + } + + #[flaky_test::flaky_test] + fn cafile_bundle_remote_exports() { + let _g = util::http_server(); + + // First we have to generate a bundle of some remote module that has exports. + let mod1 = "https://localhost:5545/subdir/mod1.ts"; + let cafile = util::testdata_path().join("tls/RootCA.pem"); + let t = TempDir::new(); + let bundle = t.path().join("mod1.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("bundle") + .arg("--cert") + .arg(cafile) + .arg(mod1) + .arg(&bundle) + .spawn() + .expect("failed to spawn script"); + let status = deno.wait().expect("failed to wait for the child process"); + assert!(status.success()); + assert!(bundle.is_file()); + + // Now we try to use that bundle from another module. + let test = t.path().join("test.js"); + std::fs::write( + &test, + " + import { printHello3 } from \"./mod1.bundle.js\"; + printHello3(); ", + ) + .expect("error writing file"); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--check") + .arg(&test) + .output() + .expect("failed to spawn script"); + // check the output of the test.ts program. + assert!(std::str::from_utf8(&output.stdout) + .unwrap() + .trim() + .ends_with("Hello")); + assert_eq!(output.stderr, b""); + } + + #[tokio::test] + async fn listen_tls_alpn() { + // TLS streams require the presence of an ambient local task set to gracefully + // close dropped connections in the background. + LocalSet::new() + .run_until(async { + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--unstable") + .arg("--quiet") + .arg("--allow-net") + .arg("--allow-read") + .arg("./cert/listen_tls_alpn.ts") + .arg("4504") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let stdout = child.stdout.as_mut().unwrap(); + let mut msg = [0; 5]; + let read = stdout.read(&mut msg).unwrap(); + assert_eq!(read, 5); + assert_eq!(&msg, b"READY"); + + let mut reader = &mut BufReader::new(Cursor::new(include_bytes!( + "./testdata/tls/RootCA.crt" + ))); + let certs = rustls_pemfile::certs(&mut reader).unwrap(); + let mut root_store = rustls::RootCertStore::empty(); + root_store.add_parsable_certificates(&certs); + let mut cfg = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_no_client_auth(); + cfg.alpn_protocols.push(b"foobar".to_vec()); + let cfg = Arc::new(cfg); + + let hostname = rustls::ServerName::try_from("localhost").unwrap(); + + let tcp_stream = tokio::net::TcpStream::connect("localhost:4504") + .await + .unwrap(); + let mut tls_stream = + TlsStream::new_client_side(tcp_stream, cfg, hostname); + + tls_stream.handshake().await.unwrap(); + + let (_, rustls_connection) = tls_stream.get_ref(); + let alpn = rustls_connection.alpn_protocol().unwrap(); + assert_eq!(alpn, b"foobar"); + + let status = child.wait().unwrap(); + assert!(status.success()); + }) + .await; + } + + #[tokio::test] + async fn listen_tls_alpn_fail() { + // TLS streams require the presence of an ambient local task set to gracefully + // close dropped connections in the background. + LocalSet::new() + .run_until(async { + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--unstable") + .arg("--quiet") + .arg("--allow-net") + .arg("--allow-read") + .arg("./cert/listen_tls_alpn_fail.ts") + .arg("4505") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let stdout = child.stdout.as_mut().unwrap(); + let mut msg = [0; 5]; + let read = stdout.read(&mut msg).unwrap(); + assert_eq!(read, 5); + assert_eq!(&msg, b"READY"); + + let mut reader = &mut BufReader::new(Cursor::new(include_bytes!( + "./testdata/tls/RootCA.crt" + ))); + let certs = rustls_pemfile::certs(&mut reader).unwrap(); + let mut root_store = rustls::RootCertStore::empty(); + root_store.add_parsable_certificates(&certs); + let mut cfg = rustls::ClientConfig::builder() + .with_safe_defaults() + .with_root_certificates(root_store) + .with_no_client_auth(); + cfg.alpn_protocols.push(b"boofar".to_vec()); + let cfg = Arc::new(cfg); + + let hostname = rustls::ServerName::try_from("localhost").unwrap(); + + let tcp_stream = tokio::net::TcpStream::connect("localhost:4505") + .await + .unwrap(); + let mut tls_stream = + TlsStream::new_client_side(tcp_stream, cfg, hostname); + + tls_stream.handshake().await.unwrap_err(); + + let (_, rustls_connection) = tls_stream.get_ref(); + assert!(rustls_connection.alpn_protocol().is_none()); + + let status = child.wait().unwrap(); + assert!(status.success()); + }) + .await; + } +} diff --git a/cli/tests/check_tests.rs b/cli/tests/check_tests.rs new file mode 100644 index 0000000000..3a57c3f55e --- /dev/null +++ b/cli/tests/check_tests.rs @@ -0,0 +1,212 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +use std::process::Command; +use std::process::Stdio; +use test_util as util; +use util::TempDir; + +mod check { + use super::*; + itest!(_095_check_with_bare_import { + args: "check cache/095_cache_with_bare_import.ts", + output: "cache/095_cache_with_bare_import.ts.out", + exit_code: 1, + }); + + itest!(check_extensionless { + args: "check --reload http://localhost:4545/subdir/no_js_ext", + output: "cache/cache_extensionless.out", + http_server: true, + }); + + itest!(check_random_extension { + args: "check --reload http://localhost:4545/subdir/no_js_ext@1.0.0", + output: "cache/cache_random_extension.out", + http_server: true, + }); + + itest!(check_all { + args: "check --quiet --remote check/check_all.ts", + output: "check/check_all.out", + http_server: true, + exit_code: 1, + }); + + itest!(check_all_local { + args: "check --quiet check/check_all.ts", + output_str: Some(""), + http_server: true, + }); + + itest!(module_detection_force { + args: "check --quiet check/module_detection_force/main.ts", + output_str: Some(""), + }); + + // Regression test for https://github.com/denoland/deno/issues/14937. + itest!(declaration_header_file_with_no_exports { + args: "check --quiet check/declaration_header_file_with_no_exports.ts", + output_str: Some(""), + }); + + itest!(check_npm_install_diagnostics { + args: "check --quiet check/npm_install_diagnostics/main.ts", + output: "check/npm_install_diagnostics/main.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + exit_code: 1, + }); + + itest!(check_export_equals_declaration_file { + args: "check --quiet check/export_equals_declaration_file/main.ts", + exit_code: 0, + }); + + #[test] + fn cache_switching_config_then_no_config() { + let deno_dir = util::new_deno_dir(); + assert!(does_type_checking(&deno_dir, true)); + assert!(does_type_checking(&deno_dir, false)); + + // should now not do type checking even when it changes + // configs because it previously did + assert!(!does_type_checking(&deno_dir, true)); + assert!(!does_type_checking(&deno_dir, false)); + + fn does_type_checking(deno_dir: &util::TempDir, with_config: bool) -> bool { + let mut cmd = util::deno_cmd_with_deno_dir(deno_dir); + cmd + .current_dir(util::testdata_path()) + .stderr(Stdio::piped()) + .arg("check") + .arg("check/cache_config_on_off/main.ts"); + if with_config { + cmd + .arg("--config") + .arg("check/cache_config_on_off/deno.json"); + } + let output = cmd.spawn().unwrap().wait_with_output().unwrap(); + assert!(output.status.success()); + + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + stderr.contains("Check") + } + } + + #[test] + fn reload_flag() { + // should do type checking whenever someone specifies --reload + let deno_dir = util::new_deno_dir(); + assert!(does_type_checking(&deno_dir, false)); + assert!(!does_type_checking(&deno_dir, false)); + assert!(does_type_checking(&deno_dir, true)); + assert!(does_type_checking(&deno_dir, true)); + assert!(!does_type_checking(&deno_dir, false)); + + fn does_type_checking(deno_dir: &util::TempDir, reload: bool) -> bool { + let mut cmd = util::deno_cmd_with_deno_dir(deno_dir); + cmd + .current_dir(util::testdata_path()) + .stderr(Stdio::piped()) + .arg("check") + .arg("check/cache_config_on_off/main.ts"); + if reload { + cmd.arg("--reload"); + } + let output = cmd.spawn().unwrap().wait_with_output().unwrap(); + assert!(output.status.success()); + + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + stderr.contains("Check") + } + } + + #[test] + fn typecheck_declarations_ns() { + let output = util::deno_cmd() + .arg("test") + .arg("--doc") + .arg(util::root_path().join("cli/tsc/dts/lib.deno.ns.d.ts")) + .output() + .unwrap(); + println!("stdout: {}", String::from_utf8(output.stdout).unwrap()); + println!("stderr: {}", String::from_utf8(output.stderr).unwrap()); + assert!(output.status.success()); + } + + #[test] + fn typecheck_declarations_unstable() { + let output = util::deno_cmd() + .arg("test") + .arg("--doc") + .arg("--unstable") + .arg(util::root_path().join("cli/tsc/dts/lib.deno.unstable.d.ts")) + .output() + .unwrap(); + println!("stdout: {}", String::from_utf8(output.stdout).unwrap()); + println!("stderr: {}", String::from_utf8(output.stderr).unwrap()); + assert!(output.status.success()); + } + + #[test] + fn typecheck_core() { + let deno_dir = TempDir::new(); + let test_file = deno_dir.path().join("test_deno_core_types.ts"); + std::fs::write( + &test_file, + format!( + "import \"{}\";", + deno_core::resolve_path( + util::root_path() + .join("core/lib.deno_core.d.ts") + .to_str() + .unwrap() + ) + .unwrap() + ), + ) + .unwrap(); + let output = util::deno_cmd_with_deno_dir(&deno_dir) + .arg("run") + .arg(test_file.to_str().unwrap()) + .output() + .unwrap(); + println!("stdout: {}", String::from_utf8(output.stdout).unwrap()); + println!("stderr: {}", String::from_utf8(output.stderr).unwrap()); + assert!(output.status.success()); + } + + #[test] + fn ts_no_recheck_on_redirect() { + let deno_dir = util::new_deno_dir(); + let e = util::deno_exe_path(); + + let redirect_ts = util::testdata_path().join("run/017_import_redirect.ts"); + assert!(redirect_ts.is_file()); + let mut cmd = Command::new(e.clone()); + cmd.env("DENO_DIR", deno_dir.path()); + let mut initial = cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--check") + .arg(redirect_ts.clone()) + .spawn() + .expect("failed to span script"); + let status_initial = + initial.wait().expect("failed to wait for child process"); + assert!(status_initial.success()); + + let mut cmd = Command::new(e); + cmd.env("DENO_DIR", deno_dir.path()); + let output = cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--check") + .arg(redirect_ts) + .output() + .expect("failed to spawn script"); + + assert!(std::str::from_utf8(&output.stderr).unwrap().is_empty()); + } +} diff --git a/cli/tests/compile_tests.rs b/cli/tests/compile_tests.rs new file mode 100644 index 0000000000..869a8780ed --- /dev/null +++ b/cli/tests/compile_tests.rs @@ -0,0 +1,536 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use std::fs::File; +use std::process::Command; +use test_util as util; +use test_util::TempDir; + +mod compile { + use super::*; + #[test] + fn compile() { + let dir = TempDir::new(); + let exe = if cfg!(windows) { + dir.path().join("welcome.exe") + } else { + dir.path().join("welcome") + }; + // try this twice to ensure it works with the cache + for _ in 0..2 { + let output = util::deno_cmd_with_deno_dir(&dir) + .current_dir(util::root_path()) + .arg("compile") + .arg("--unstable") + .arg("--output") + .arg(&exe) + .arg("./test_util/std/examples/welcome.ts") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let output = Command::new(&exe) + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, "Welcome to Deno!\n".as_bytes()); + } + } + + #[test] + fn standalone_args() { + let dir = TempDir::new(); + let exe = if cfg!(windows) { + dir.path().join("args.exe") + } else { + dir.path().join("args") + }; + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("compile") + .arg("--unstable") + .arg("--output") + .arg(&exe) + .arg("./compile/args.ts") + .arg("a") + .arg("b") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let output = Command::new(exe) + .arg("foo") + .arg("--bar") + .arg("--unstable") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"a\nb\nfoo\n--bar\n--unstable\n"); + } + + #[test] + fn standalone_error() { + let dir = TempDir::new(); + let exe = if cfg!(windows) { + dir.path().join("error.exe") + } else { + dir.path().join("error") + }; + let testdata_path = util::testdata_path(); + let output = util::deno_cmd() + .current_dir(&testdata_path) + .arg("compile") + .arg("--unstable") + .arg("--output") + .arg(&exe) + .arg("./compile/standalone_error.ts") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let output = Command::new(exe) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + assert_eq!(output.stdout, b""); + let stderr = String::from_utf8(output.stderr).unwrap(); + let stderr = util::strip_ansi_codes(&stderr).to_string(); + // On Windows, we cannot assert the file path (because '\'). + // Instead we just check for relevant output. + assert!(stderr.contains("error: Uncaught Error: boom!")); + assert!(stderr.contains("throw new Error(\"boom!\");")); + assert!(stderr.contains("\n at boom (file://")); + assert!(stderr.contains("standalone_error.ts:2:11")); + assert!(stderr.contains("at foo (file://")); + assert!(stderr.contains("standalone_error.ts:5:5")); + assert!(stderr.contains("standalone_error.ts:7:1")); + } + + #[test] + fn standalone_error_module_with_imports() { + let dir = TempDir::new(); + let exe = if cfg!(windows) { + dir.path().join("error.exe") + } else { + dir.path().join("error") + }; + let testdata_path = util::testdata_path(); + let output = util::deno_cmd() + .current_dir(&testdata_path) + .arg("compile") + .arg("--unstable") + .arg("--output") + .arg(&exe) + .arg("./compile/standalone_error_module_with_imports_1.ts") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let output = Command::new(exe) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + println!("{:#?}", &output); + assert_eq!(output.stdout, b"hello\n"); + let stderr = String::from_utf8(output.stderr).unwrap(); + let stderr = util::strip_ansi_codes(&stderr).to_string(); + // On Windows, we cannot assert the file path (because '\'). + // Instead we just check for relevant output. + assert!(stderr.contains("error: Uncaught Error: boom!")); + assert!(stderr.contains("throw new Error(\"boom!\");")); + assert!(stderr.contains("\n at file://")); + assert!(stderr.contains("standalone_error_module_with_imports_2.ts:2:7")); + } + + #[test] + fn standalone_load_datauri() { + let dir = TempDir::new(); + let exe = if cfg!(windows) { + dir.path().join("load_datauri.exe") + } else { + dir.path().join("load_datauri") + }; + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("compile") + .arg("--unstable") + .arg("--output") + .arg(&exe) + .arg("./compile/standalone_import_datauri.ts") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let output = Command::new(exe) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Hello Deno!\n"); + } + + // https://github.com/denoland/deno/issues/13704 + #[test] + fn standalone_follow_redirects() { + let dir = TempDir::new(); + let exe = if cfg!(windows) { + dir.path().join("follow_redirects.exe") + } else { + dir.path().join("follow_redirects") + }; + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("compile") + .arg("--unstable") + .arg("--output") + .arg(&exe) + .arg("./compile/standalone_follow_redirects.ts") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let output = Command::new(exe) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Hello\n"); + } + + #[test] + fn compile_with_file_exists_error() { + let dir = TempDir::new(); + let output_path = if cfg!(windows) { + dir.path().join(r"args\") + } else { + dir.path().join("args/") + }; + let file_path = dir.path().join("args"); + File::create(&file_path).unwrap(); + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("compile") + .arg("--unstable") + .arg("--output") + .arg(&output_path) + .arg("./compile/args.ts") + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + let expected_stderr = format!( + concat!( + "Could not compile to file '{}' because its parent directory ", + "is an existing file. You can use the `--output ` flag to ", + "provide an alternative name.\n", + ), + file_path.display(), + ); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stderr.contains(&expected_stderr)); + } + + #[test] + fn compile_with_directory_exists_error() { + let dir = TempDir::new(); + let exe = if cfg!(windows) { + dir.path().join("args.exe") + } else { + dir.path().join("args") + }; + std::fs::create_dir(&exe).unwrap(); + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("compile") + .arg("--unstable") + .arg("--output") + .arg(&exe) + .arg("./compile/args.ts") + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + let expected_stderr = format!( + concat!( + "Could not compile to file '{}' because a directory exists with ", + "the same name. You can use the `--output ` flag to ", + "provide an alternative name." + ), + exe.display() + ); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stderr.contains(&expected_stderr)); + } + + #[test] + fn compile_with_conflict_file_exists_error() { + let dir = TempDir::new(); + let exe = if cfg!(windows) { + dir.path().join("args.exe") + } else { + dir.path().join("args") + }; + std::fs::write(&exe, b"SHOULD NOT BE OVERWRITTEN").unwrap(); + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("compile") + .arg("--unstable") + .arg("--output") + .arg(&exe) + .arg("./compile/args.ts") + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + let expected_stderr = format!( + concat!( + "Could not compile to file '{}' because the file already exists ", + "and cannot be overwritten. Please delete the existing file or ", + "use the `--output 1); + } + + itest!(eval_basic { + args: "eval console.log(\"hello\")", + output_str: Some("hello\n"), + }); + + // Ugly parentheses due to whitespace delimiting problem. + itest!(eval_ts { + args: "eval --quiet --ext=ts console.log((123)as(number))", // 'as' is a TS keyword only + output_str: Some("123\n"), + }); + + itest!(dyn_import_eval { + args: "eval import('./subdir/mod4.js').then(console.log)", + output: "eval/dyn_import_eval.out", + }); + + // Cannot write the expression to evaluate as "console.log(typeof gc)" + // because itest! splits args on whitespace. + itest!(v8_flags_eval { + args: "eval --v8-flags=--expose-gc console.log(typeof(gc))", + output: "run/v8_flags.js.out", + }); + + itest!(check_local_by_default { + args: "eval --quiet import('http://localhost:4545/subdir/type_error.ts').then(console.log);", + output: "eval/check_local_by_default.out", + http_server: true, + }); + + itest!(check_local_by_default2 { + args: "eval --quiet import('./eval/check_local_by_default2.ts').then(console.log);", + output: "eval/check_local_by_default2.out", + http_server: true, + }); +} diff --git a/cli/tests/flags_tests.rs b/cli/tests/flags_tests.rs new file mode 100644 index 0000000000..a3ea8f7dd8 --- /dev/null +++ b/cli/tests/flags_tests.rs @@ -0,0 +1,50 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +use test_util as util; + +mod flags { + use super::*; + + #[test] + fn help_flag() { + let status = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("--help") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + + #[test] + fn version_short_flag() { + let status = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("-V") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + + #[test] + fn version_long_flag() { + let status = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("--version") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + + itest!(types { + args: "types", + output: "types/types.out", + }); +} diff --git a/cli/tests/fmt_tests.rs b/cli/tests/fmt_tests.rs new file mode 100644 index 0000000000..6ace4ce5ba --- /dev/null +++ b/cli/tests/fmt_tests.rs @@ -0,0 +1,257 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +use test_util as util; +use test_util::TempDir; + +mod fmt { + use super::*; + + #[test] + fn fmt_test() { + let t = TempDir::new(); + let testdata_fmt_dir = util::testdata_path().join("fmt"); + let fixed_js = testdata_fmt_dir.join("badly_formatted_fixed.js"); + let badly_formatted_original_js = + testdata_fmt_dir.join("badly_formatted.mjs"); + let badly_formatted_js = t.path().join("badly_formatted.js"); + let badly_formatted_js_str = badly_formatted_js.to_str().unwrap(); + std::fs::copy(&badly_formatted_original_js, &badly_formatted_js).unwrap(); + + let fixed_md = testdata_fmt_dir.join("badly_formatted_fixed.md"); + let badly_formatted_original_md = + testdata_fmt_dir.join("badly_formatted.md"); + let badly_formatted_md = t.path().join("badly_formatted.md"); + let badly_formatted_md_str = badly_formatted_md.to_str().unwrap(); + std::fs::copy(&badly_formatted_original_md, &badly_formatted_md).unwrap(); + + let fixed_json = testdata_fmt_dir.join("badly_formatted_fixed.json"); + let badly_formatted_original_json = + testdata_fmt_dir.join("badly_formatted.json"); + let badly_formatted_json = t.path().join("badly_formatted.json"); + let badly_formatted_json_str = badly_formatted_json.to_str().unwrap(); + std::fs::copy(&badly_formatted_original_json, &badly_formatted_json) + .unwrap(); + // First, check formatting by ignoring the badly formatted file. + let status = util::deno_cmd() + .current_dir(&testdata_fmt_dir) + .arg("fmt") + .arg(format!( + "--ignore={},{},{}", + badly_formatted_js_str, + badly_formatted_md_str, + badly_formatted_json_str + )) + .arg("--check") + .arg(badly_formatted_js_str) + .arg(badly_formatted_md_str) + .arg(badly_formatted_json_str) + .spawn() + .unwrap() + .wait() + .unwrap(); + // No target files found + assert!(!status.success()); + + // Check without ignore. + let status = util::deno_cmd() + .current_dir(&testdata_fmt_dir) + .arg("fmt") + .arg("--check") + .arg(badly_formatted_js_str) + .arg(badly_formatted_md_str) + .arg(badly_formatted_json_str) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(!status.success()); + + // Format the source file. + let status = util::deno_cmd() + .current_dir(&testdata_fmt_dir) + .arg("fmt") + .arg(badly_formatted_js_str) + .arg(badly_formatted_md_str) + .arg(badly_formatted_json_str) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + let expected_js = std::fs::read_to_string(fixed_js).unwrap(); + let expected_md = std::fs::read_to_string(fixed_md).unwrap(); + let expected_json = std::fs::read_to_string(fixed_json).unwrap(); + let actual_js = std::fs::read_to_string(badly_formatted_js).unwrap(); + let actual_md = std::fs::read_to_string(badly_formatted_md).unwrap(); + let actual_json = std::fs::read_to_string(badly_formatted_json).unwrap(); + assert_eq!(expected_js, actual_js); + assert_eq!(expected_md, actual_md); + assert_eq!(expected_json, actual_json); + } + + #[test] + fn fmt_stdin_error() { + use std::io::Write; + let mut deno = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("fmt") + .arg("-") + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let stdin = deno.stdin.as_mut().unwrap(); + let invalid_js = b"import { example }"; + stdin.write_all(invalid_js).unwrap(); + let output = deno.wait_with_output().unwrap(); + // Error message might change. Just check stdout empty, stderr not. + assert!(output.stdout.is_empty()); + assert!(!output.stderr.is_empty()); + assert!(!output.status.success()); + } + + #[test] + fn fmt_ignore_unexplicit_files() { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .env("NO_COLOR", "1") + .arg("fmt") + .arg("--check") + .arg("--ignore=./") + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + assert_eq!( + String::from_utf8_lossy(&output.stderr), + "error: No target files found.\n" + ); + } + + #[test] + fn fmt_auto_ignore_git_and_node_modules() { + use std::fs::{create_dir_all, File}; + use std::io::Write; + use std::path::PathBuf; + fn create_bad_json(t: PathBuf) { + let bad_json_path = t.join("bad.json"); + let mut bad_json_file = File::create(bad_json_path).unwrap(); + writeln!(bad_json_file, "bad json").unwrap(); + } + let temp_dir = TempDir::new(); + let t = temp_dir.path().join("target"); + let nest_git = t.join("nest").join(".git"); + let git_dir = t.join(".git"); + let nest_node_modules = t.join("nest").join("node_modules"); + let node_modules_dir = t.join("node_modules"); + create_dir_all(&nest_git).unwrap(); + create_dir_all(&git_dir).unwrap(); + create_dir_all(&nest_node_modules).unwrap(); + create_dir_all(&node_modules_dir).unwrap(); + create_bad_json(nest_git); + create_bad_json(git_dir); + create_bad_json(nest_node_modules); + create_bad_json(node_modules_dir); + let output = util::deno_cmd() + .current_dir(t) + .env("NO_COLOR", "1") + .arg("fmt") + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + assert_eq!( + String::from_utf8_lossy(&output.stderr), + "error: No target files found.\n" + ); + } + + itest!(fmt_quiet_check_fmt_dir { + args: "fmt --check --quiet fmt/regular/", + output_str: Some(""), + exit_code: 0, + }); + + itest!(fmt_check_formatted_files { + args: "fmt --check fmt/regular/formatted1.js fmt/regular/formatted2.ts fmt/regular/formatted3.markdown fmt/regular/formatted4.jsonc", + output: "fmt/expected_fmt_check_formatted_files.out", + exit_code: 0, + }); + + itest!(fmt_check_ignore { + args: "fmt --check --ignore=fmt/regular/formatted1.js fmt/regular/", + output: "fmt/expected_fmt_check_ignore.out", + exit_code: 0, + }); + + itest!(fmt_check_parse_error { + args: "fmt --check fmt/parse_error/parse_error.ts", + output: "fmt/fmt_check_parse_error.out", + exit_code: 1, + }); + + itest!(fmt_stdin { + args: "fmt -", + input: Some("const a = 1\n"), + output_str: Some("const a = 1;\n"), + }); + + itest!(fmt_stdin_markdown { + args: "fmt --ext=md -", + input: Some("# Hello Markdown\n```ts\nconsole.log( \"text\")\n```\n\n```cts\nconsole.log( 5 )\n```"), + output_str: Some("# Hello Markdown\n\n```ts\nconsole.log(\"text\");\n```\n\n```cts\nconsole.log(5);\n```\n"), +}); + + itest!(fmt_stdin_json { + args: "fmt --ext=json -", + input: Some("{ \"key\": \"value\"}"), + output_str: Some("{ \"key\": \"value\" }\n"), + }); + + itest!(fmt_stdin_check_formatted { + args: "fmt --check -", + input: Some("const a = 1;\n"), + output_str: Some(""), + }); + + itest!(fmt_stdin_check_not_formatted { + args: "fmt --check -", + input: Some("const a = 1\n"), + output_str: Some("Not formatted stdin\n"), + }); + + itest!(fmt_with_config { + args: "fmt --config fmt/with_config/deno.jsonc fmt/with_config/subdir", + output: "fmt/fmt_with_config.out", + }); + + itest!(fmt_with_config_default { + args: "fmt fmt/with_config/subdir", + output: "fmt/fmt_with_config.out", + }); + + // Check if CLI flags take precedence + itest!(fmt_with_config_and_flags { + args: "fmt --config fmt/with_config/deno.jsonc --ignore=fmt/with_config/subdir/a.ts,fmt/with_config/subdir/b.ts", + output: "fmt/fmt_with_config_and_flags.out", + }); + + itest!(fmt_with_malformed_config { + args: "fmt --config fmt/deno.malformed.jsonc", + output: "fmt/fmt_with_malformed_config.out", + exit_code: 1, + }); + + itest!(fmt_with_malformed_config2 { + args: "fmt --config fmt/deno.malformed2.jsonc", + output: "fmt/fmt_with_malformed_config2.out", + exit_code: 1, + }); +} diff --git a/cli/tests/info_tests.rs b/cli/tests/info_tests.rs new file mode 100644 index 0000000000..5818fe3be4 --- /dev/null +++ b/cli/tests/info_tests.rs @@ -0,0 +1,136 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +use test_util as util; +use test_util::TempDir; + +mod init { + use super::*; + + #[test] + fn info_with_compiled_source() { + let _g = util::http_server(); + let module_path = "http://127.0.0.1:4545/run/048_media_types_jsx.ts"; + let t = TempDir::new(); + + let mut deno = util::deno_cmd() + .env("DENO_DIR", t.path()) + .current_dir(util::testdata_path()) + .arg("cache") + .arg(module_path) + .spawn() + .unwrap(); + let status = deno.wait().unwrap(); + assert!(status.success()); + + let output = util::deno_cmd() + .env("DENO_DIR", t.path()) + .env("NO_COLOR", "1") + .current_dir(util::testdata_path()) + .arg("info") + .arg(module_path) + .output() + .unwrap(); + + let str_output = std::str::from_utf8(&output.stdout).unwrap().trim(); + eprintln!("{}", str_output); + // check the output of the test.ts program. + assert!(str_output.contains("emit: ")); + assert_eq!(output.stderr, b""); + } + + itest!(multiple_imports { + args: "info http://127.0.0.1:4545/run/019_media_types.ts", + output: "info/multiple_imports.out", + http_server: true, + }); + + itest!(info_ts_error { + args: "info info/031_info_ts_error.ts", + output: "info/031_info_ts_error.out", + }); + + itest!(info_flag { + args: "info", + output: "info/041_info_flag.out", + }); + + itest!(info_flag_location { + args: "info --location https://deno.land", + output: "info/041_info_flag_location.out", + }); + + itest!(info_json { + args: "info --json --unstable", + output: "info/info_json.out", + }); + + itest!(info_json_location { + args: "info --json --unstable --location https://deno.land", + output: "info/info_json_location.out", + }); + + itest!(info_flag_script_jsx { + args: "info http://127.0.0.1:4545/run/048_media_types_jsx.ts", + output: "info/049_info_flag_script_jsx.out", + http_server: true, + }); + + itest!(json_file { + args: "info --quiet --json --unstable info/json_output/main.ts", + output: "info/json_output/main.out", + exit_code: 0, + }); + + itest!(import_map_info { + args: + "info --quiet --import-map=import_maps/import_map.json import_maps/test.ts", + output: "info/065_import_map_info.out", + }); + + itest!(info_json_deps_order { + args: "info --unstable --json info/076_info_json_deps_order.ts", + output: "info/076_info_json_deps_order.out", + }); + + itest!(info_missing_module { + args: "info info/error_009_missing_js_module.js", + output: "info/info_missing_module.out", + }); + + itest!(info_recursive_modules { + args: "info --quiet info/info_recursive_imports_test.ts", + output: "info/info_recursive_imports_test.out", + exit_code: 0, + }); + + itest!(info_type_import { + args: "info info/info_type_import.ts", + output: "info/info_type_import.out", + }); + + itest!(_054_info_local_imports { + args: "info --quiet run/005_more_imports.ts", + output: "info/054_info_local_imports.out", + exit_code: 0, + }); + + // Tests for AssertionError where "data" is unexpectedly null when + // a file contains only triple slash references (#11196) + itest!(data_null_error { + args: "info info/data_null_error/mod.ts", + output: "info/data_null_error/data_null_error.out", + }); + + itest!(types_header_direct { + args: "info --reload run/type_directives_01.ts", + output: "info/types_header.out", + http_server: true, + }); + + itest!(with_config_override { + args: "info info/with_config/test.ts --config info/with_config/deno-override.json --import-map info/with_config/import_map.json", + output: "info/with_config/with_config.out", + }); +} diff --git a/cli/tests/init_tests.rs b/cli/tests/init_tests.rs new file mode 100644 index 0000000000..40c3c1b9ad --- /dev/null +++ b/cli/tests/init_tests.rs @@ -0,0 +1,163 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use std::process::Stdio; +use test_util as util; +use test_util::TempDir; +use util::assert_contains; + +mod init { + use super::*; + + #[test] + fn init_subcommand_without_dir() { + let temp_dir = TempDir::new(); + let cwd = temp_dir.path(); + let deno_dir = util::new_deno_dir(); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(cwd) + .arg("init") + .stderr(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert_contains!(stderr, "Project initialized"); + assert!(!stderr.contains("cd")); + assert_contains!(stderr, "deno run main.ts"); + assert_contains!(stderr, "deno test"); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(cwd) + .env("NO_COLOR", "1") + .arg("run") + .arg("main.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Add 2 + 3 = 5\n"); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(cwd) + .env("NO_COLOR", "1") + .arg("test") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let stdout = String::from_utf8(output.stdout).unwrap(); + assert_contains!(stdout, "1 passed"); + } + + #[test] + fn init_subcommand_with_dir_arg() { + let temp_dir = TempDir::new(); + let cwd = temp_dir.path(); + let deno_dir = util::new_deno_dir(); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(cwd) + .arg("init") + .arg("my_dir") + .stderr(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert_contains!(stderr, "Project initialized"); + assert_contains!(stderr, "cd my_dir"); + assert_contains!(stderr, "deno run main.ts"); + assert_contains!(stderr, "deno test"); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(cwd) + .env("NO_COLOR", "1") + .arg("run") + .arg("my_dir/main.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Add 2 + 3 = 5\n"); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(cwd) + .env("NO_COLOR", "1") + .arg("test") + .arg("my_dir/main_test.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let stdout = String::from_utf8(output.stdout).unwrap(); + assert_contains!(stdout, "1 passed"); + } + + #[test] + fn init_subcommand_with_quiet_arg() { + let temp_dir = TempDir::new(); + let cwd = temp_dir.path(); + let deno_dir = util::new_deno_dir(); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(cwd) + .arg("init") + .arg("--quiet") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let stdout = String::from_utf8(output.stdout).unwrap(); + assert_eq!(stdout, ""); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(cwd) + .env("NO_COLOR", "1") + .arg("run") + .arg("main.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Add 2 + 3 = 5\n"); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(cwd) + .env("NO_COLOR", "1") + .arg("test") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let stdout = String::from_utf8(output.stdout).unwrap(); + assert_contains!(stdout, "1 passed"); + } +} diff --git a/cli/tests/inspector_tests.rs b/cli/tests/inspector_tests.rs new file mode 100644 index 0000000000..febff7c28f --- /dev/null +++ b/cli/tests/inspector_tests.rs @@ -0,0 +1,1123 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use deno_core::futures; +use deno_core::futures::prelude::*; +use deno_core::futures::stream::SplitSink; +use deno_core::serde_json; +use deno_core::url; +use deno_runtime::deno_fetch::reqwest; +use deno_runtime::deno_websocket::tokio_tungstenite; +use deno_runtime::deno_websocket::tokio_tungstenite::tungstenite; +use std::io::BufRead; +use std::pin::Pin; +use test_util as util; +use tokio::net::TcpStream; + +mod inspector { + use super::*; + + macro_rules! assert_starts_with { + ($string:expr, $($test:expr),+) => { + let string = $string; // This might be a function call or something + if !($(string.starts_with($test))||+) { + panic!("{:?} does not start with {:?}", string, [$($test),+]); + } + } +} + + fn inspect_flag_with_unique_port(flag_prefix: &str) -> String { + use std::sync::atomic::{AtomicU16, Ordering}; + static PORT: AtomicU16 = AtomicU16::new(9229); + let port = PORT.fetch_add(1, Ordering::Relaxed); + format!("{}=127.0.0.1:{}", flag_prefix, port) + } + + fn extract_ws_url_from_stderr( + stderr_lines: &mut impl std::iter::Iterator, + ) -> url::Url { + let stderr_first_line = skip_check_line(stderr_lines); + assert_starts_with!(&stderr_first_line, "Debugger listening on "); + let v: Vec<_> = stderr_first_line.match_indices("ws:").collect(); + assert_eq!(v.len(), 1); + let ws_url_index = v[0].0; + let ws_url = &stderr_first_line[ws_url_index..]; + url::Url::parse(ws_url).unwrap() + } + + fn skip_check_line( + stderr_lines: &mut impl std::iter::Iterator, + ) -> String { + loop { + let mut line = stderr_lines.next().unwrap(); + line = util::strip_ansi_codes(&line).to_string(); + + if line.starts_with("Check") { + continue; + } + + return line; + } + } + + fn assert_stderr( + stderr_lines: &mut impl std::iter::Iterator, + expected_lines: &[&str], + ) { + let mut expected_index = 0; + + loop { + let line = skip_check_line(stderr_lines); + + assert_eq!(line, expected_lines[expected_index]); + expected_index += 1; + + if expected_index >= expected_lines.len() { + break; + } + } + } + + fn assert_stderr_for_inspect( + stderr_lines: &mut impl std::iter::Iterator, + ) { + assert_stderr( + stderr_lines, + &["Visit chrome://inspect to connect to the debugger."], + ); + } + + fn assert_stderr_for_inspect_brk( + stderr_lines: &mut impl std::iter::Iterator, + ) { + assert_stderr( + stderr_lines, + &[ + "Visit chrome://inspect to connect to the debugger.", + "Deno is waiting for debugger to connect.", + ], + ); + } + + async fn assert_inspector_messages( + socket_tx: &mut SplitSink< + tokio_tungstenite::WebSocketStream< + tokio_tungstenite::MaybeTlsStream, + >, + tungstenite::Message, + >, + messages: &[&str], + socket_rx: &mut Pin>>, + responses: &[&str], + notifications: &[&str], + ) { + for msg in messages { + socket_tx.send(msg.to_string().into()).await.unwrap(); + } + + let expected_messages = responses.len() + notifications.len(); + let mut responses_idx = 0; + let mut notifications_idx = 0; + + for _ in 0..expected_messages { + let msg = socket_rx.next().await.unwrap(); + + if msg.starts_with(r#"{"id":"#) { + assert!( + msg.starts_with(responses[responses_idx]), + "Doesn't start with {}, instead received {}", + responses[responses_idx], + msg + ); + responses_idx += 1; + } else { + assert!( + msg.starts_with(notifications[notifications_idx]), + "Doesn't start with {}, instead received {}", + notifications[notifications_idx], + msg + ); + notifications_idx += 1; + } + } + } + + #[tokio::test] + async fn inspector_connect() { + let script = util::testdata_path().join("inspector/inspector1.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect")) + .arg(script) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + + // We use tokio_tungstenite as a websocket client because warp (which is + // a dependency of Deno) uses it. + let (_socket, response) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!("101 Switching Protocols", response.status().to_string()); + child.kill().unwrap(); + child.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_break_on_first_line() { + let script = util::testdata_path().join("inspector/inspector2.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + + let (socket, response) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut stdout_lines = + std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":4,"method":"Runtime.evaluate","params":{"expression":"Deno.core.print(\"hello from the inspector\\n\")","contextId":1,"includeCommandLineAPI":true,"silent":false,"returnByValue":true}}"#, + ], + &mut socket_rx, + &[r#"{"id":4,"result":{"result":{"type":"undefined"}}}"#], + &[], + ) + .await; + + assert_eq!(&stdout_lines.next().unwrap(), "hello from the inspector"); + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":5,"method":"Debugger.resume"}"#], + &mut socket_rx, + &[r#"{"id":5,"result":{}}"#], + &[], + ) + .await; + + assert_eq!(&stdout_lines.next().unwrap(), "hello from the script"); + + child.kill().unwrap(); + child.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_pause() { + let script = util::testdata_path().join("inspector/inspector1.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect")) + .arg(script) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + + // We use tokio_tungstenite as a websocket client because warp (which is + // a dependency of Deno) uses it. + let (mut socket, _) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + + /// Returns the next websocket message as a string ignoring + /// Debugger.scriptParsed messages. + async fn ws_read_msg( + socket: &mut tokio_tungstenite::WebSocketStream< + tokio_tungstenite::MaybeTlsStream, + >, + ) -> String { + use deno_core::futures::stream::StreamExt; + while let Some(msg) = socket.next().await { + let msg = msg.unwrap().to_string(); + // FIXME(bartlomieju): fails because there's a file loaded + // called 150_errors.js + // assert!(!msg.contains("error")); + if !msg.contains("Debugger.scriptParsed") { + return msg; + } + } + unreachable!() + } + + socket + .send(r#"{"id":6,"method":"Debugger.enable"}"#.into()) + .await + .unwrap(); + + let msg = ws_read_msg(&mut socket).await; + println!("response msg 1 {}", msg); + assert_starts_with!(msg, r#"{"id":6,"result":{"debuggerId":"#); + + socket + .send(r#"{"id":31,"method":"Debugger.pause"}"#.into()) + .await + .unwrap(); + + let msg = ws_read_msg(&mut socket).await; + println!("response msg 2 {}", msg); + assert_eq!(msg, r#"{"id":31,"result":{}}"#); + + child.kill().unwrap(); + } + + #[tokio::test] + async fn inspector_port_collision() { + // Skip this test on WSL, which allows multiple processes to listen on the + // same port, rather than making `bind()` fail with `EADDRINUSE`. + if cfg!(target_os = "linux") + && std::env::var_os("WSL_DISTRO_NAME").is_some() + { + return; + } + + let script = util::testdata_path().join("inspector/inspector1.js"); + let inspect_flag = inspect_flag_with_unique_port("--inspect"); + + let mut child1 = util::deno_cmd() + .arg("run") + .arg(&inspect_flag) + .arg(script.clone()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr_1 = child1.stderr.as_mut().unwrap(); + let mut stderr_1_lines = std::io::BufReader::new(stderr_1) + .lines() + .map(|r| r.unwrap()); + let _ = extract_ws_url_from_stderr(&mut stderr_1_lines); + + let mut child2 = util::deno_cmd() + .arg("run") + .arg(&inspect_flag) + .arg(script) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr_2 = child2.stderr.as_mut().unwrap(); + let stderr_2_error_message = std::io::BufReader::new(stderr_2) + .lines() + .map(|r| r.unwrap()) + .inspect(|line| assert!(!line.contains("Debugger listening"))) + .find(|line| line.contains("Cannot start inspector server")); + assert!(stderr_2_error_message.is_some()); + + child1.kill().unwrap(); + child1.wait().unwrap(); + child2.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_does_not_hang() { + let script = util::testdata_path().join("inspector/inspector3.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .env("NO_COLOR", "1") + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + + let (socket, response) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut stdout_lines = + std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"# + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"# + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":4,"method":"Debugger.resume"}"#], + &mut socket_rx, + &[r#"{"id":4,"result":{}}"#], + &[r#"{"method":"Debugger.resumed","params":{}}"#], + ) + .await; + + for i in 0..128u32 { + let request_id = i + 10; + // Expect the number {i} on stdout. + let s = i.to_string(); + assert_eq!(stdout_lines.next().unwrap(), s); + + assert_inspector_messages( + &mut socket_tx, + &[], + &mut socket_rx, + &[], + &[ + r#"{"method":"Runtime.consoleAPICalled","#, + r#"{"method":"Debugger.paused","#, + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[&format!( + r#"{{"id":{},"method":"Debugger.resume"}}"#, + request_id + )], + &mut socket_rx, + &[&format!(r#"{{"id":{},"result":{{}}}}"#, request_id)], + &[r#"{"method":"Debugger.resumed","params":{}}"#], + ) + .await; + } + + // Check that we can gracefully close the websocket connection. + socket_tx.close().await.unwrap(); + socket_rx.for_each(|_| async {}).await; + + assert_eq!(&stdout_lines.next().unwrap(), "done"); + assert!(child.wait().unwrap().success()); + } + + #[tokio::test] + async fn inspector_without_brk_runs_code() { + let script = util::testdata_path().join("inspector/inspector4.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect")) + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let _ = extract_ws_url_from_stderr(&mut stderr_lines); + + // Check that inspector actually runs code without waiting for inspector + // connection. + let stdout = child.stdout.as_mut().unwrap(); + let mut stdout_lines = + std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); + let stdout_first_line = stdout_lines.next().unwrap(); + assert_eq!(stdout_first_line, "hello"); + + child.kill().unwrap(); + child.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_runtime_evaluate_does_not_crash() { + let mut child = util::deno_cmd() + .arg("repl") + .arg(inspect_flag_with_unique_port("--inspect")) + .stdin(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = std::io::BufReader::new(stderr) + .lines() + .map(|r| r.unwrap()) + .filter(|s| s.as_str() != "Debugger session started."); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + + let (socket, response) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + let stdin = child.stdin.take().unwrap(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut stdout_lines = std::io::BufReader::new(stdout) + .lines() + .map(|r| r.unwrap()) + .filter(|s| !s.starts_with("Deno ")); + + assert_stderr_for_inspect(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + assert_eq!( + &stdout_lines.next().unwrap(), + "exit using ctrl+d, ctrl+c, or close()" + ); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":3,"method":"Runtime.compileScript","params":{"expression":"Deno.cwd()","sourceURL":"","persistScript":false,"executionContextId":1}}"#, + ], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#], &[] + ).await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":4,"method":"Runtime.evaluate","params":{"expression":"Deno.cwd()","objectGroup":"console","includeCommandLineAPI":true,"silent":false,"contextId":1,"returnByValue":true,"generatePreview":true,"userGesture":true,"awaitPromise":false,"replMode":true}}"#, + ], + &mut socket_rx, + &[r#"{"id":4,"result":{"result":{"type":"string","value":""#], + &[], + ).await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":5,"method":"Runtime.evaluate","params":{"expression":"console.error('done');","objectGroup":"console","includeCommandLineAPI":true,"silent":false,"contextId":1,"returnByValue":true,"generatePreview":true,"userGesture":true,"awaitPromise":false,"replMode":true}}"#, + ], + &mut socket_rx, + &[r#"{"id":5,"result":{"result":{"type":"undefined"}}}"#], + &[r#"{"method":"Runtime.consoleAPICalled"#], + ).await; + + assert_eq!(&stderr_lines.next().unwrap(), "done"); + + drop(stdin); + child.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_json() { + let script = util::testdata_path().join("inspector/inspector1.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect")) + .arg(script) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + let mut url = ws_url.clone(); + let _ = url.set_scheme("http"); + url.set_path("/json"); + let resp = reqwest::get(url).await.unwrap(); + assert_eq!(resp.status(), reqwest::StatusCode::OK); + let endpoint_list: Vec = + serde_json::from_str(&resp.text().await.unwrap()).unwrap(); + let matching_endpoint = endpoint_list + .iter() + .find(|e| e["webSocketDebuggerUrl"] == ws_url.as_str()); + assert!(matching_endpoint.is_some()); + child.kill().unwrap(); + } + + #[tokio::test] + async fn inspector_json_list() { + let script = util::testdata_path().join("inspector/inspector1.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect")) + .arg(script) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + let mut url = ws_url.clone(); + let _ = url.set_scheme("http"); + url.set_path("/json/list"); + let resp = reqwest::get(url).await.unwrap(); + assert_eq!(resp.status(), reqwest::StatusCode::OK); + let endpoint_list: Vec = + serde_json::from_str(&resp.text().await.unwrap()).unwrap(); + let matching_endpoint = endpoint_list + .iter() + .find(|e| e["webSocketDebuggerUrl"] == ws_url.as_str()); + assert!(matching_endpoint.is_some()); + child.kill().unwrap(); + } + + #[tokio::test] + async fn inspector_connect_non_ws() { + // https://github.com/denoland/deno/issues/11449 + // Verify we don't panic if non-WS connection is being established + let script = util::testdata_path().join("inspector/inspector1.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect")) + .arg(script) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let mut ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + // Change scheme to URL and try send a request. We're not interested + // in the request result, just that the process doesn't panic. + ws_url.set_scheme("http").unwrap(); + let resp = reqwest::get(ws_url).await.unwrap(); + assert_eq!("400 Bad Request", resp.status().to_string()); + child.kill().unwrap(); + child.wait().unwrap(); + } + + #[tokio::test] + #[ignore] // https://github.com/denoland/deno/issues/13491 + async fn inspector_break_on_first_line_in_test() { + let script = util::testdata_path().join("inspector/inspector_test.js"); + let mut child = util::deno_cmd() + .arg("test") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + + let (socket, response) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut stdout_lines = + std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":4,"method":"Runtime.evaluate","params":{"expression":"Deno.core.print(\"hello from the inspector\\n\")","contextId":1,"includeCommandLineAPI":true,"silent":false,"returnByValue":true}}"#, + ], + &mut socket_rx, + &[r#"{"id":4,"result":{"result":{"type":"undefined"}}}"#], + &[], + ) + .await; + + assert_eq!(&stdout_lines.next().unwrap(), "hello from the inspector"); + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":5,"method":"Debugger.resume"}"#], + &mut socket_rx, + &[r#"{"id":5,"result":{}}"#], + &[], + ) + .await; + + assert_starts_with!(&stdout_lines.next().unwrap(), "running 1 test from"); + assert!(&stdout_lines + .next() + .unwrap() + .contains("test has finished running")); + + child.kill().unwrap(); + child.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_with_ts_files() { + let script = util::testdata_path().join("inspector/test.ts"); + let mut child = util::deno_cmd() + .arg("run") + .arg("--check") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + + let (socket, response) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = (msg.starts_with(r#"{"method":"Debugger.scriptParsed","#) + && msg.contains("testdata/inspector")) + || !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut stdout_lines = + std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + // receive messages with sources from this test + let script1 = socket_rx.next().await.unwrap(); + assert!(script1.contains("testdata/inspector/test.ts")); + let script1_id = { + let v: serde_json::Value = serde_json::from_str(&script1).unwrap(); + v["params"]["scriptId"].as_str().unwrap().to_string() + }; + let script2 = socket_rx.next().await.unwrap(); + assert!(script2.contains("testdata/inspector/foo.ts")); + let script2_id = { + let v: serde_json::Value = serde_json::from_str(&script2).unwrap(); + v["params"]["scriptId"].as_str().unwrap().to_string() + }; + let script3 = socket_rx.next().await.unwrap(); + assert!(script3.contains("testdata/inspector/bar.js")); + let script3_id = { + let v: serde_json::Value = serde_json::from_str(&script3).unwrap(); + v["params"]["scriptId"].as_str().unwrap().to_string() + }; + + assert_inspector_messages( + &mut socket_tx, + &[], + &mut socket_rx, + &[r#"{"id":2,"result":{"debuggerId":"#], + &[], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + &format!(r#"{{"id":4,"method":"Debugger.getScriptSource","params":{{"scriptId":"{}"}}}}"#, script1_id), + &format!(r#"{{"id":5,"method":"Debugger.getScriptSource","params":{{"scriptId":"{}"}}}}"#, script2_id), + &format!(r#"{{"id":6,"method":"Debugger.getScriptSource","params":{{"scriptId":"{}"}}}}"#, script3_id), + ], + &mut socket_rx, + &[ + r#"{"id":4,"result":{"scriptSource":"import { foo } from \"./foo.ts\";\nimport { bar } from \"./bar.js\";\nconsole.log(foo());\nconsole.log(bar());\n//# sourceMappingURL=data:application/json;base64,"#, + r#"{"id":5,"result":{"scriptSource":"class Foo {\n hello() {\n return \"hello\";\n }\n}\nexport function foo() {\n const f = new Foo();\n return f.hello();\n}\n//# sourceMappingURL=data:application/json;base64,"#, + r#"{"id":6,"result":{"scriptSource":"export function bar() {\n return \"world\";\n}\n"#, + ], + &[], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[r#"{"id":7,"method":"Debugger.resume"}"#], + &mut socket_rx, + &[r#"{"id":7,"result":{}}"#], + &[], + ) + .await; + + assert_eq!(&stdout_lines.next().unwrap(), "hello"); + assert_eq!(&stdout_lines.next().unwrap(), "world"); + + child.kill().unwrap(); + child.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_memory() { + let script = util::testdata_path().join("inspector/memory.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + + let (socket, response) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#, + r#"{"id":4,"method":"HeapProfiler.enable"}"#, + ], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#, r#"{"id":4,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + socket_tx + .send( + r#"{"id":5,"method":"Runtime.getHeapUsage", "params": {}}"# + .to_string() + .into(), + ) + .await + .unwrap(); + let msg = socket_rx.next().await.unwrap(); + let json_msg: serde_json::Value = serde_json::from_str(&msg).unwrap(); + assert_eq!(json_msg["id"].as_i64().unwrap(), 5); + let result = &json_msg["result"]; + assert!( + result["usedSize"].as_i64().unwrap() + <= result["totalSize"].as_i64().unwrap() + ); + + socket_tx.send( + r#"{"id":6,"method":"HeapProfiler.takeHeapSnapshot","params": {"reportProgress": true, "treatGlobalObjectsAsRoots": true, "captureNumberValue": false}}"# + .to_string().into() + ).await.unwrap(); + + let mut progress_report_completed = false; + loop { + let msg = socket_rx.next().await.unwrap(); + + if !progress_report_completed + && msg.starts_with( + r#"{"method":"HeapProfiler.reportHeapSnapshotProgress","params""#, + ) + { + let json_msg: serde_json::Value = serde_json::from_str(&msg).unwrap(); + if let Some(finished) = json_msg["params"].get("finished") { + progress_report_completed = finished.as_bool().unwrap(); + } + continue; + } + + if msg.starts_with(r#"{"method":"HeapProfiler.reportHeapSnapshotProgress","params":{"done":"#,) { + continue; + } + + if msg.starts_with(r#"{"id":6,"result":{}}"#) { + assert!(progress_report_completed); + break; + } + } + + child.kill().unwrap(); + child.wait().unwrap(); + } + + #[tokio::test] + async fn inspector_profile() { + let script = util::testdata_path().join("inspector/memory.js"); + let mut child = util::deno_cmd() + .arg("run") + .arg(inspect_flag_with_unique_port("--inspect-brk")) + .arg(script) + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stderr = child.stderr.as_mut().unwrap(); + let mut stderr_lines = + std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); + let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); + + let (socket, response) = + tokio_tungstenite::connect_async(ws_url).await.unwrap(); + assert_eq!(response.status(), 101); // Switching protocols. + + let (mut socket_tx, socket_rx) = socket.split(); + let mut socket_rx = socket_rx + .map(|msg| msg.unwrap().to_string()) + .filter(|msg| { + let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); + futures::future::ready(pass) + }) + .boxed_local(); + + assert_stderr_for_inspect_brk(&mut stderr_lines); + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":1,"method":"Runtime.enable"}"#, + r#"{"id":2,"method":"Debugger.enable"}"#, + + ], + &mut socket_rx, + &[ + r#"{"id":1,"result":{}}"#, + r#"{"id":2,"result":{"debuggerId":"#, + ], + &[ + r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, + ], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#, + r#"{"id":4,"method":"Profiler.enable"}"#, + ], + &mut socket_rx, + &[r#"{"id":3,"result":{}}"#, r#"{"id":4,"result":{}}"#], + &[r#"{"method":"Debugger.paused","#], + ) + .await; + + assert_inspector_messages( + &mut socket_tx, + &[ + r#"{"id":5,"method":"Profiler.setSamplingInterval","params":{"interval": 100}}"#, + r#"{"id":6,"method":"Profiler.start","params":{}}"#, + ], + &mut socket_rx, + &[r#"{"id":5,"result":{}}"#, r#"{"id":6,"result":{}}"#], + &[], + ) + .await; + + tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; + + socket_tx + .send( + r#"{"id":7,"method":"Profiler.stop", "params": {}}"#.to_string().into(), + ) + .await + .unwrap(); + let msg = socket_rx.next().await.unwrap(); + let json_msg: serde_json::Value = serde_json::from_str(&msg).unwrap(); + assert_eq!(json_msg["id"].as_i64().unwrap(), 7); + let result = &json_msg["result"]; + let profile = &result["profile"]; + assert!( + profile["startTime"].as_i64().unwrap() + < profile["endTime"].as_i64().unwrap() + ); + profile["samples"].as_array().unwrap(); + profile["nodes"].as_array().unwrap(); + + child.kill().unwrap(); + child.wait().unwrap(); + } +} diff --git a/cli/tests/install_tests.rs b/cli/tests/install_tests.rs new file mode 100644 index 0000000000..4016b63733 --- /dev/null +++ b/cli/tests/install_tests.rs @@ -0,0 +1,220 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use std::fs; +use std::process::Command; +use test_util as util; +use test_util::assert_contains; +use test_util::assert_ends_with; +use test_util::TempDir; + +mod install { + use super::*; + + #[test] + fn install_basic() { + let _guard = util::http_server(); + let temp_dir = TempDir::new(); + let temp_dir_str = temp_dir.path().to_string_lossy().to_string(); + + let status = util::deno_cmd() + .current_dir(temp_dir.path()) + .arg("install") + .arg("--check") + .arg("--name") + .arg("echo_test") + .arg("http://localhost:4545/echo.ts") + .envs([ + ("HOME", temp_dir_str.as_str()), + ("USERPROFILE", temp_dir_str.as_str()), + ("DENO_INSTALL_ROOT", ""), + ]) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + + let mut file_path = temp_dir.path().join(".deno/bin/echo_test"); + assert!(file_path.exists()); + + if cfg!(windows) { + file_path = file_path.with_extension("cmd"); + } + + let content = fs::read_to_string(file_path).unwrap(); + // ensure there's a trailing newline so the shell script can be + // more versatile. + assert_eq!(content.chars().last().unwrap(), '\n'); + + if cfg!(windows) { + assert_contains!( + content, + r#""run" "--check" "http://localhost:4545/echo.ts""# + ); + } else { + assert_contains!( + content, + r#"run --check 'http://localhost:4545/echo.ts'"# + ); + } + } + + #[test] + fn install_custom_dir_env_var() { + let _guard = util::http_server(); + let temp_dir = TempDir::new(); + let temp_dir_str = temp_dir.path().to_string_lossy().to_string(); + + let status = util::deno_cmd() + .current_dir(util::root_path()) // different cwd + .arg("install") + .arg("--check") + .arg("--name") + .arg("echo_test") + .arg("http://localhost:4545/echo.ts") + .envs([ + ("HOME", temp_dir_str.as_str()), + ("USERPROFILE", temp_dir_str.as_str()), + ("DENO_INSTALL_ROOT", temp_dir_str.as_str()), + ]) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + + let mut file_path = temp_dir.path().join("bin/echo_test"); + assert!(file_path.exists()); + + if cfg!(windows) { + file_path = file_path.with_extension("cmd"); + } + + let content = fs::read_to_string(file_path).unwrap(); + if cfg!(windows) { + assert_contains!( + content, + r#""run" "--check" "http://localhost:4545/echo.ts""# + ); + } else { + assert_contains!( + content, + r#"run --check 'http://localhost:4545/echo.ts'"# + ); + } + } + + #[test] + fn installer_test_local_module_run() { + let temp_dir = TempDir::new(); + let bin_dir = temp_dir.path().join("bin"); + std::fs::create_dir(&bin_dir).unwrap(); + let status = util::deno_cmd() + .current_dir(util::root_path()) + .arg("install") + .arg("--name") + .arg("echo_test") + .arg("--root") + .arg(temp_dir.path()) + .arg(util::testdata_path().join("echo.ts")) + .arg("hello") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + let mut file_path = bin_dir.join("echo_test"); + if cfg!(windows) { + file_path = file_path.with_extension("cmd"); + } + assert!(file_path.exists()); + // NOTE: using file_path here instead of exec_name, because tests + // shouldn't mess with user's PATH env variable + let output = Command::new(file_path) + .current_dir(temp_dir.path()) + .arg("foo") + .env("PATH", util::target_dir()) + .output() + .unwrap(); + let stdout_str = std::str::from_utf8(&output.stdout).unwrap().trim(); + assert_ends_with!(stdout_str, "hello, foo"); + } + + #[test] + fn installer_test_remote_module_run() { + let _g = util::http_server(); + let temp_dir = TempDir::new(); + let bin_dir = temp_dir.path().join("bin"); + std::fs::create_dir(&bin_dir).unwrap(); + let status = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("install") + .arg("--name") + .arg("echo_test") + .arg("--root") + .arg(temp_dir.path()) + .arg("http://localhost:4545/echo.ts") + .arg("hello") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + let mut file_path = bin_dir.join("echo_test"); + if cfg!(windows) { + file_path = file_path.with_extension("cmd"); + } + assert!(file_path.exists()); + let output = Command::new(file_path) + .current_dir(temp_dir.path()) + .arg("foo") + .env("PATH", util::target_dir()) + .output() + .unwrap(); + assert_ends_with!( + std::str::from_utf8(&output.stdout).unwrap().trim(), + "hello, foo", + ); + } + + #[test] + fn check_local_by_default() { + let _guard = util::http_server(); + let temp_dir = TempDir::new(); + let temp_dir_str = temp_dir.path().to_string_lossy().to_string(); + + let status = util::deno_cmd() + .current_dir(temp_dir.path()) + .arg("install") + .arg(util::testdata_path().join("./install/check_local_by_default.ts")) + .envs([ + ("HOME", temp_dir_str.as_str()), + ("USERPROFILE", temp_dir_str.as_str()), + ("DENO_INSTALL_ROOT", ""), + ]) + .status() + .unwrap(); + assert!(status.success()); + } + + #[test] + fn check_local_by_default2() { + let _guard = util::http_server(); + let temp_dir = TempDir::new(); + let temp_dir_str = temp_dir.path().to_string_lossy().to_string(); + + let status = util::deno_cmd() + .current_dir(temp_dir.path()) + .arg("install") + .arg(util::testdata_path().join("./install/check_local_by_default2.ts")) + .envs([ + ("HOME", temp_dir_str.as_str()), + ("NO_COLOR", "1"), + ("USERPROFILE", temp_dir_str.as_str()), + ("DENO_INSTALL_ROOT", ""), + ]) + .status() + .unwrap(); + assert!(status.success()); + } +} diff --git a/cli/tests/integration/bench_tests.rs b/cli/tests/integration/bench_tests.rs deleted file mode 100644 index d3fa5791a7..0000000000 --- a/cli/tests/integration/bench_tests.rs +++ /dev/null @@ -1,195 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::itest; -use deno_core::url::Url; -use test_util as util; - -itest!(overloads { - args: "bench bench/overloads.ts", - exit_code: 0, - output: "bench/overloads.out", -}); - -itest!(meta { - args: "bench bench/meta.ts", - exit_code: 0, - output: "bench/meta.out", -}); - -itest!(pass { - args: "bench bench/pass.ts", - exit_code: 0, - output: "bench/pass.out", -}); - -itest!(ignore { - args: "bench bench/ignore.ts", - exit_code: 0, - output: "bench/ignore.out", -}); - -itest!(ignore_permissions { - args: "bench bench/ignore_permissions.ts", - exit_code: 0, - output: "bench/ignore_permissions.out", -}); - -itest!(fail { - args: "bench bench/fail.ts", - exit_code: 1, - output: "bench/fail.out", -}); - -itest!(collect { - args: "bench --ignore=bench/collect/ignore bench/collect", - exit_code: 0, - output: "bench/collect.out", -}); - -itest!(load_unload { - args: "bench bench/load_unload.ts", - exit_code: 0, - output: "bench/load_unload.out", -}); - -itest!(interval { - args: "bench bench/interval.ts", - exit_code: 0, - output: "bench/interval.out", -}); - -itest!(quiet { - args: "bench --quiet bench/quiet.ts", - exit_code: 0, - output: "bench/quiet.out", -}); - -itest!(only { - args: "bench bench/only.ts", - exit_code: 1, - output: "bench/only.out", -}); - -itest!(multifile_summary { - args: "bench bench/group_baseline.ts bench/pass.ts bench/group_baseline.ts", - exit_code: 0, - output: "bench/multifile_summary.out", -}); - -itest!(no_check { - args: "bench --no-check bench/no_check.ts", - exit_code: 1, - output: "bench/no_check.out", -}); - -itest!(allow_all { - args: "bench --allow-all bench/allow_all.ts", - exit_code: 0, - output: "bench/allow_all.out", -}); - -itest!(allow_none { - args: "bench bench/allow_none.ts", - exit_code: 1, - output: "bench/allow_none.out", -}); - -itest!(exit_sanitizer { - args: "bench bench/exit_sanitizer.ts", - output: "bench/exit_sanitizer.out", - exit_code: 1, -}); - -itest!(clear_timeout { - args: "bench bench/clear_timeout.ts", - exit_code: 0, - output: "bench/clear_timeout.out", -}); - -itest!(finally_timeout { - args: "bench bench/finally_timeout.ts", - exit_code: 1, - output: "bench/finally_timeout.out", -}); - -itest!(group_baseline { - args: "bench bench/group_baseline.ts", - exit_code: 0, - output: "bench/group_baseline.out", -}); - -itest!(unresolved_promise { - args: "bench bench/unresolved_promise.ts", - exit_code: 1, - output: "bench/unresolved_promise.out", -}); - -itest!(unhandled_rejection { - args: "bench bench/unhandled_rejection.ts", - exit_code: 1, - output: "bench/unhandled_rejection.out", -}); - -itest!(filter { - args: "bench --filter=foo bench/filter", - exit_code: 0, - output: "bench/filter.out", -}); - -itest!(no_prompt_by_default { - args: "bench --quiet bench/no_prompt_by_default.ts", - exit_code: 1, - output: "bench/no_prompt_by_default.out", -}); - -itest!(no_prompt_with_denied_perms { - args: "bench --quiet --allow-read bench/no_prompt_with_denied_perms.ts", - exit_code: 1, - output: "bench/no_prompt_with_denied_perms.out", -}); - -itest!(check_local_by_default { - args: "bench --quiet bench/check_local_by_default.ts", - output: "bench/check_local_by_default.out", - http_server: true, -}); - -itest!(check_local_by_default2 { - args: "bench --quiet bench/check_local_by_default2.ts", - output: "bench/check_local_by_default2.out", - http_server: true, - exit_code: 1, -}); - -#[test] -fn recursive_permissions_pledge() { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bench") - .arg("bench/recursive_permissions_pledge.js") - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - assert!(String::from_utf8(output.stderr).unwrap().contains( - "pledge test permissions called before restoring previous pledge" - )); -} - -#[test] -fn file_protocol() { - let file_url = - Url::from_file_path(util::testdata_path().join("bench/file_protocol.ts")) - .unwrap() - .to_string(); - - (util::CheckOutputIntegrationTest { - args_vec: vec!["bench", &file_url], - exit_code: 0, - output: "bench/file_protocol.out", - ..Default::default() - }) - .run(); -} diff --git a/cli/tests/integration/bundle_tests.rs b/cli/tests/integration/bundle_tests.rs deleted file mode 100644 index ff5a51e981..0000000000 --- a/cli/tests/integration/bundle_tests.rs +++ /dev/null @@ -1,468 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::itest; -use test_util as util; -use test_util::assert_contains; -use test_util::assert_ends_with; -use test_util::TempDir; - -#[test] -fn bundle_exports() { - // First we have to generate a bundle of some module that has exports. - let mod1 = util::testdata_path().join("subdir/mod1.ts"); - assert!(mod1.is_file()); - let t = TempDir::new(); - let bundle = t.path().join("mod1.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bundle") - .arg(mod1) - .arg(&bundle) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - assert!(bundle.is_file()); - - // Now we try to use that bundle from another module. - let test = t.path().join("test.js"); - std::fs::write( - &test, - " - import { printHello3 } from \"./mod1.bundle.js\"; - printHello3(); ", - ) - .unwrap(); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg(&test) - .output() - .unwrap(); - // check the output of the test.ts program. - assert_ends_with!( - std::str::from_utf8(&output.stdout).unwrap().trim(), - "Hello", - ); - assert_eq!(output.stderr, b""); -} - -#[test] -fn bundle_exports_no_check() { - // First we have to generate a bundle of some module that has exports. - let mod1 = util::testdata_path().join("subdir/mod1.ts"); - assert!(mod1.is_file()); - let t = TempDir::new(); - let bundle = t.path().join("mod1.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bundle") - .arg(mod1) - .arg(&bundle) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - assert!(bundle.is_file()); - - // Now we try to use that bundle from another module. - let test = t.path().join("test.js"); - std::fs::write( - &test, - " - import { printHello3 } from \"./mod1.bundle.js\"; - printHello3(); ", - ) - .unwrap(); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg(&test) - .output() - .unwrap(); - // check the output of the test.ts program. - assert_ends_with!( - std::str::from_utf8(&output.stdout).unwrap().trim(), - "Hello", - ); - assert_eq!(output.stderr, b""); -} - -#[test] -fn bundle_circular() { - // First we have to generate a bundle of some module that has exports. - let circular1_path = util::testdata_path().join("subdir/circular1.ts"); - assert!(circular1_path.is_file()); - let t = TempDir::new(); - let bundle_path = t.path().join("circular1.bundle.js"); - - // run this twice to ensure it works even when cached - for _ in 0..2 { - let mut deno = util::deno_cmd_with_deno_dir(&t) - .current_dir(util::testdata_path()) - .arg("bundle") - .arg(&circular1_path) - .arg(&bundle_path) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - assert!(bundle_path.is_file()); - } - - let output = util::deno_cmd_with_deno_dir(&t) - .current_dir(util::testdata_path()) - .arg("run") - .arg(&bundle_path) - .output() - .unwrap(); - // check the output of the the bundle program. - assert_ends_with!( - std::str::from_utf8(&output.stdout).unwrap().trim(), - "f2\nf1", - ); - assert_eq!(output.stderr, b""); -} - -#[test] -fn bundle_single_module() { - // First we have to generate a bundle of some module that has exports. - let single_module = util::testdata_path().join("subdir/single_module.ts"); - assert!(single_module.is_file()); - let t = TempDir::new(); - let bundle = t.path().join("single_module.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bundle") - .arg(single_module) - .arg(&bundle) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - assert!(bundle.is_file()); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg(&bundle) - .output() - .unwrap(); - // check the output of the the bundle program. - assert_ends_with!( - std::str::from_utf8(&output.stdout).unwrap().trim(), - "Hello world!", - ); - assert_eq!(output.stderr, b""); -} - -#[test] -fn bundle_tla() { - // First we have to generate a bundle of some module that has exports. - let tla_import = util::testdata_path().join("subdir/tla.ts"); - assert!(tla_import.is_file()); - let t = TempDir::new(); - let bundle = t.path().join("tla.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bundle") - .arg(tla_import) - .arg(&bundle) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - assert!(bundle.is_file()); - - // Now we try to use that bundle from another module. - let test = t.path().join("test.js"); - std::fs::write( - &test, - " - import { foo } from \"./tla.bundle.js\"; - console.log(foo); ", - ) - .unwrap(); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg(&test) - .output() - .unwrap(); - // check the output of the test.ts program. - assert_ends_with!( - std::str::from_utf8(&output.stdout).unwrap().trim(), - "Hello", - ); - assert_eq!(output.stderr, b""); -} - -#[test] -fn bundle_js() { - // First we have to generate a bundle of some module that has exports. - let mod6 = util::testdata_path().join("subdir/mod6.js"); - assert!(mod6.is_file()); - let t = TempDir::new(); - let bundle = t.path().join("mod6.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bundle") - .arg(mod6) - .arg(&bundle) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - assert!(bundle.is_file()); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg(&bundle) - .output() - .unwrap(); - // check that nothing went to stderr - assert_eq!(output.stderr, b""); -} - -#[test] -fn bundle_dynamic_import() { - let _g = util::http_server(); - let dynamic_import = util::testdata_path().join("bundle/dynamic_import.ts"); - assert!(dynamic_import.is_file()); - let t = TempDir::new(); - let output_path = t.path().join("bundle_dynamic_import.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bundle") - .arg(dynamic_import) - .arg(&output_path) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - assert!(output_path.is_file()); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-net") - .arg("--quiet") - .arg(&output_path) - .output() - .unwrap(); - // check the output of the test.ts program. - assert_ends_with!( - std::str::from_utf8(&output.stdout).unwrap().trim(), - "Hello", - ); - assert_eq!(output.stderr, b""); -} - -#[test] -fn bundle_import_map() { - let import = util::testdata_path().join("bundle/import_map/main.ts"); - let import_map_path = - util::testdata_path().join("bundle/import_map/import_map.json"); - assert!(import.is_file()); - let t = TempDir::new(); - let output_path = t.path().join("import_map.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bundle") - .arg("--import-map") - .arg(import_map_path) - .arg(import) - .arg(&output_path) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - assert!(output_path.is_file()); - - // Now we try to use that bundle from another module. - let test = t.path().join("test.js"); - std::fs::write( - &test, - " - import { printHello3 } from \"./import_map.bundle.js\"; - printHello3(); ", - ) - .unwrap(); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--check") - .arg(&test) - .output() - .unwrap(); - // check the output of the test.ts program. - assert_ends_with!( - std::str::from_utf8(&output.stdout).unwrap().trim(), - "Hello", - ); - assert_eq!(output.stderr, b""); -} - -#[test] -fn bundle_import_map_no_check() { - let import = util::testdata_path().join("bundle/import_map/main.ts"); - let import_map_path = - util::testdata_path().join("bundle/import_map/import_map.json"); - assert!(import.is_file()); - let t = TempDir::new(); - let output_path = t.path().join("import_map.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bundle") - .arg("--import-map") - .arg(import_map_path) - .arg(import) - .arg(&output_path) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - assert!(output_path.is_file()); - - // Now we try to use that bundle from another module. - let test = t.path().join("test.js"); - std::fs::write( - &test, - " - import { printHello3 } from \"./import_map.bundle.js\"; - printHello3(); ", - ) - .unwrap(); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg(&test) - .output() - .unwrap(); - // check the output of the test.ts program. - assert_ends_with!( - std::str::from_utf8(&output.stdout).unwrap().trim(), - "Hello", - ); - assert_eq!(output.stderr, b""); -} - -#[test] -fn bundle_json_module() { - // First we have to generate a bundle of some module that has exports. - let mod7 = util::testdata_path().join("subdir/mod7.js"); - assert!(mod7.is_file()); - let t = TempDir::new(); - let bundle = t.path().join("mod7.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bundle") - .arg(mod7) - .arg(&bundle) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - assert!(bundle.is_file()); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg(&bundle) - .output() - .unwrap(); - // check that nothing went to stderr - assert_eq!(output.stderr, b""); - // ensure the output looks right - assert_contains!(String::from_utf8(output.stdout).unwrap(), "with space",); -} - -#[test] -fn bundle_json_module_escape_sub() { - // First we have to generate a bundle of some module that has exports. - let mod8 = util::testdata_path().join("subdir/mod8.js"); - assert!(mod8.is_file()); - let t = TempDir::new(); - let bundle = t.path().join("mod8.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bundle") - .arg(mod8) - .arg(&bundle) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - assert!(bundle.is_file()); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg(&bundle) - .output() - .unwrap(); - // check that nothing went to stderr - assert_eq!(output.stderr, b""); - // make sure the output looks right and the escapes were effective - assert_contains!( - String::from_utf8(output.stdout).unwrap(), - "${globalThis}`and string literal`", - ); -} - -itest!(lockfile_check_error { - args: "bundle --lock=bundle/lockfile/check_error.json http://127.0.0.1:4545/subdir/mod1.ts", - output: "bundle/lockfile/check_error.out", - exit_code: 10, - http_server: true, -}); - -itest!(bundle { - args: "bundle subdir/mod1.ts", - output: "bundle/bundle.test.out", -}); - -itest!(bundle_jsx { - args: "bundle run/jsx_import_from_ts.ts", - output: "bundle/jsx.out", -}); - -itest!(error_bundle_with_bare_import { - args: "bundle bundle/bare_imports/error_with_bare_import.ts", - output: "bundle/bare_imports/error_with_bare_import.ts.out", - exit_code: 1, -}); - -itest!(ts_decorators_bundle { - args: "bundle bundle/decorators/ts_decorators.ts", - output: "bundle/decorators/ts_decorators.out", -}); - -itest!(bundle_export_specifier_with_alias { - args: "bundle bundle/file_tests-fixture16.ts", - output: "bundle/fixture16.out", -}); - -itest!(bundle_ignore_directives { - args: "bundle subdir/mod1.ts", - output: "bundle/ignore_directives.test.out", -}); - -itest!(check_local_by_default_no_errors { - args: "bundle --quiet bundle/check_local_by_default/no_errors.ts", - output: "bundle/check_local_by_default/no_errors.out", - http_server: true, -}); - -itest!(check_local_by_default_type_error { - args: "bundle --quiet bundle/check_local_by_default/type_error.ts", - output: "bundle/check_local_by_default/type_error.out", - http_server: true, - exit_code: 1, -}); diff --git a/cli/tests/integration/cache_tests.rs b/cli/tests/integration/cache_tests.rs deleted file mode 100644 index 3fca335a97..0000000000 --- a/cli/tests/integration/cache_tests.rs +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::itest; - -itest!(_036_import_map_fetch { - args: - "cache --quiet --reload --import-map=import_maps/import_map.json import_maps/test.ts", - output: "cache/036_import_map_fetch.out", -}); - -itest!(_037_fetch_multiple { - args: "cache --reload --check=all run/fetch/test.ts run/fetch/other.ts", - http_server: true, - output: "cache/037_fetch_multiple.out", -}); - -itest!(_095_cache_with_bare_import { - args: "cache cache/095_cache_with_bare_import.ts", - output: "cache/095_cache_with_bare_import.ts.out", - exit_code: 1, -}); - -itest!(cache_extensionless { - args: "cache --reload --check=all http://localhost:4545/subdir/no_js_ext", - output: "cache/cache_extensionless.out", - http_server: true, -}); - -itest!(cache_random_extension { - args: - "cache --reload --check=all http://localhost:4545/subdir/no_js_ext@1.0.0", - output: "cache/cache_random_extension.out", - http_server: true, -}); - -itest!(performance_stats { - args: "cache --reload --check=all --log-level debug run/002_hello.ts", - output: "cache/performance_stats.out", -}); - -itest!(redirect_cache { - http_server: true, - args: - "cache --reload --check=all http://localhost:4548/subdir/redirects/a.ts", - output: "cache/redirect_cache.out", -}); - -itest!(ignore_require { - args: "cache --reload --no-check cache/ignore_require.js", - output_str: Some(""), - exit_code: 0, -}); - -// This test only runs on linux, because it hardcodes the XDG_CACHE_HOME env var -// which is only used on linux. -#[cfg(target_os = "linux")] -#[test] -fn relative_home_dir() { - use test_util as util; - use test_util::TempDir; - - let deno_dir = TempDir::new_in(&util::testdata_path()); - let path = deno_dir.path().strip_prefix(util::testdata_path()).unwrap(); - - let mut deno_cmd = util::deno_cmd(); - let output = deno_cmd - .current_dir(util::testdata_path()) - .env("XDG_CACHE_HOME", path) - .env_remove("HOME") - .env_remove("DENO_DIR") - .arg("cache") - .arg("--reload") - .arg("--no-check") - .arg("run/002_hello.ts") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b""); -} - -itest!(check_local_by_default { - args: "cache --quiet cache/check_local_by_default.ts", - output: "cache/check_local_by_default.out", - http_server: true, -}); - -itest!(check_local_by_default2 { - args: "cache --quiet cache/check_local_by_default2.ts", - output: "cache/check_local_by_default2.out", - http_server: true, -}); - -itest!(json_import { - // should not error - args: "cache --quiet cache/json_import/main.ts", -}); diff --git a/cli/tests/integration/cert_tests.rs b/cli/tests/integration/cert_tests.rs deleted file mode 100644 index fd19c1cc18..0000000000 --- a/cli/tests/integration/cert_tests.rs +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::itest; -use deno_runtime::deno_net::ops_tls::TlsStream; -use deno_runtime::deno_tls::rustls; -use deno_runtime::deno_tls::rustls_pemfile; -use std::io::BufReader; -use std::io::Cursor; -use std::io::Read; -use std::process::Command; -use std::sync::Arc; -use test_util as util; -use test_util::TempDir; -use tokio::task::LocalSet; - -itest_flaky!(cafile_url_imports { - args: "run --quiet --reload --cert tls/RootCA.pem cert/cafile_url_imports.ts", - output: "cert/cafile_url_imports.ts.out", - http_server: true, -}); - -itest_flaky!(cafile_ts_fetch { - args: - "run --quiet --reload --allow-net --cert tls/RootCA.pem cert/cafile_ts_fetch.ts", - output: "cert/cafile_ts_fetch.ts.out", - http_server: true, -}); - -itest_flaky!(cafile_eval { - args: "eval --cert tls/RootCA.pem fetch('https://localhost:5545/cert/cafile_ts_fetch.ts.out').then(r=>r.text()).then(t=>console.log(t.trimEnd()))", - output: "cert/cafile_ts_fetch.ts.out", - http_server: true, -}); - -itest_flaky!(cafile_info { - args: - "info --quiet --cert tls/RootCA.pem https://localhost:5545/cert/cafile_info.ts", - output: "cert/cafile_info.ts.out", - http_server: true, -}); - -itest_flaky!(cafile_url_imports_unsafe_ssl { - args: "run --quiet --reload --unsafely-ignore-certificate-errors=localhost cert/cafile_url_imports.ts", - output: "cert/cafile_url_imports_unsafe_ssl.ts.out", - http_server: true, -}); - -itest_flaky!(cafile_ts_fetch_unsafe_ssl { - args: - "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors cert/cafile_ts_fetch.ts", - output: "cert/cafile_ts_fetch_unsafe_ssl.ts.out", - http_server: true, -}); - -itest!(deno_land_unsafe_ssl { - args: - "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=deno.land cert/deno_land_unsafe_ssl.ts", - output: "cert/deno_land_unsafe_ssl.ts.out", -}); - -itest!(ip_address_unsafe_ssl { - args: - "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=1.1.1.1 cert/ip_address_unsafe_ssl.ts", - output: "cert/ip_address_unsafe_ssl.ts.out", -}); - -itest!(localhost_unsafe_ssl { - args: - "run --quiet --reload --allow-net --unsafely-ignore-certificate-errors=deno.land cert/cafile_url_imports.ts", - output: "cert/localhost_unsafe_ssl.ts.out", - http_server: true, - exit_code: 1, -}); - -#[flaky_test::flaky_test] -fn cafile_env_fetch() { - use deno_core::url::Url; - let _g = util::http_server(); - let deno_dir = TempDir::new(); - let module_url = - Url::parse("https://localhost:5545/cert/cafile_url_imports.ts").unwrap(); - let cafile = util::testdata_path().join("tls/RootCA.pem"); - let output = Command::new(util::deno_exe_path()) - .env("DENO_DIR", deno_dir.path()) - .env("DENO_CERT", cafile) - .current_dir(util::testdata_path()) - .arg("cache") - .arg(module_url.to_string()) - .output() - .expect("Failed to spawn script"); - assert!(output.status.success()); -} - -#[flaky_test::flaky_test] -fn cafile_fetch() { - use deno_core::url::Url; - let _g = util::http_server(); - let deno_dir = TempDir::new(); - let module_url = - Url::parse("http://localhost:4545/cert/cafile_url_imports.ts").unwrap(); - let cafile = util::testdata_path().join("tls/RootCA.pem"); - let output = Command::new(util::deno_exe_path()) - .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) - .arg("cache") - .arg("--cert") - .arg(cafile) - .arg(module_url.to_string()) - .output() - .expect("Failed to spawn script"); - assert!(output.status.success()); - let out = std::str::from_utf8(&output.stdout).unwrap(); - assert_eq!(out, ""); -} - -#[flaky_test::flaky_test] -fn cafile_install_remote_module() { - let _g = util::http_server(); - let temp_dir = TempDir::new(); - let bin_dir = temp_dir.path().join("bin"); - std::fs::create_dir(&bin_dir).unwrap(); - let deno_dir = TempDir::new(); - let cafile = util::testdata_path().join("tls/RootCA.pem"); - - let install_output = Command::new(util::deno_exe_path()) - .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) - .arg("install") - .arg("--cert") - .arg(cafile) - .arg("--root") - .arg(temp_dir.path()) - .arg("-n") - .arg("echo_test") - .arg("https://localhost:5545/echo.ts") - .output() - .expect("Failed to spawn script"); - println!("{}", std::str::from_utf8(&install_output.stdout).unwrap()); - eprintln!("{}", std::str::from_utf8(&install_output.stderr).unwrap()); - assert!(install_output.status.success()); - - let mut echo_test_path = bin_dir.join("echo_test"); - if cfg!(windows) { - echo_test_path = echo_test_path.with_extension("cmd"); - } - assert!(echo_test_path.exists()); - - let output = Command::new(echo_test_path) - .current_dir(temp_dir.path()) - .arg("foo") - .env("PATH", util::target_dir()) - .output() - .expect("failed to spawn script"); - let stdout = std::str::from_utf8(&output.stdout).unwrap().trim(); - assert!(stdout.ends_with("foo")); -} - -#[flaky_test::flaky_test] -fn cafile_bundle_remote_exports() { - let _g = util::http_server(); - - // First we have to generate a bundle of some remote module that has exports. - let mod1 = "https://localhost:5545/subdir/mod1.ts"; - let cafile = util::testdata_path().join("tls/RootCA.pem"); - let t = TempDir::new(); - let bundle = t.path().join("mod1.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("bundle") - .arg("--cert") - .arg(cafile) - .arg(mod1) - .arg(&bundle) - .spawn() - .expect("failed to spawn script"); - let status = deno.wait().expect("failed to wait for the child process"); - assert!(status.success()); - assert!(bundle.is_file()); - - // Now we try to use that bundle from another module. - let test = t.path().join("test.js"); - std::fs::write( - &test, - " - import { printHello3 } from \"./mod1.bundle.js\"; - printHello3(); ", - ) - .expect("error writing file"); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--check") - .arg(&test) - .output() - .expect("failed to spawn script"); - // check the output of the test.ts program. - assert!(std::str::from_utf8(&output.stdout) - .unwrap() - .trim() - .ends_with("Hello")); - assert_eq!(output.stderr, b""); -} - -#[tokio::test] -async fn listen_tls_alpn() { - // TLS streams require the presence of an ambient local task set to gracefully - // close dropped connections in the background. - LocalSet::new() - .run_until(async { - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--unstable") - .arg("--quiet") - .arg("--allow-net") - .arg("--allow-read") - .arg("./cert/listen_tls_alpn.ts") - .arg("4504") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let stdout = child.stdout.as_mut().unwrap(); - let mut msg = [0; 5]; - let read = stdout.read(&mut msg).unwrap(); - assert_eq!(read, 5); - assert_eq!(&msg, b"READY"); - - let mut reader = &mut BufReader::new(Cursor::new(include_bytes!( - "../testdata/tls/RootCA.crt" - ))); - let certs = rustls_pemfile::certs(&mut reader).unwrap(); - let mut root_store = rustls::RootCertStore::empty(); - root_store.add_parsable_certificates(&certs); - let mut cfg = rustls::ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(root_store) - .with_no_client_auth(); - cfg.alpn_protocols.push(b"foobar".to_vec()); - let cfg = Arc::new(cfg); - - let hostname = rustls::ServerName::try_from("localhost").unwrap(); - - let tcp_stream = tokio::net::TcpStream::connect("localhost:4504") - .await - .unwrap(); - let mut tls_stream = - TlsStream::new_client_side(tcp_stream, cfg, hostname); - - tls_stream.handshake().await.unwrap(); - - let (_, rustls_connection) = tls_stream.get_ref(); - let alpn = rustls_connection.alpn_protocol().unwrap(); - assert_eq!(alpn, b"foobar"); - - let status = child.wait().unwrap(); - assert!(status.success()); - }) - .await; -} - -#[tokio::test] -async fn listen_tls_alpn_fail() { - // TLS streams require the presence of an ambient local task set to gracefully - // close dropped connections in the background. - LocalSet::new() - .run_until(async { - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--unstable") - .arg("--quiet") - .arg("--allow-net") - .arg("--allow-read") - .arg("./cert/listen_tls_alpn_fail.ts") - .arg("4505") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let stdout = child.stdout.as_mut().unwrap(); - let mut msg = [0; 5]; - let read = stdout.read(&mut msg).unwrap(); - assert_eq!(read, 5); - assert_eq!(&msg, b"READY"); - - let mut reader = &mut BufReader::new(Cursor::new(include_bytes!( - "../testdata/tls/RootCA.crt" - ))); - let certs = rustls_pemfile::certs(&mut reader).unwrap(); - let mut root_store = rustls::RootCertStore::empty(); - root_store.add_parsable_certificates(&certs); - let mut cfg = rustls::ClientConfig::builder() - .with_safe_defaults() - .with_root_certificates(root_store) - .with_no_client_auth(); - cfg.alpn_protocols.push(b"boofar".to_vec()); - let cfg = Arc::new(cfg); - - let hostname = rustls::ServerName::try_from("localhost").unwrap(); - - let tcp_stream = tokio::net::TcpStream::connect("localhost:4505") - .await - .unwrap(); - let mut tls_stream = - TlsStream::new_client_side(tcp_stream, cfg, hostname); - - tls_stream.handshake().await.unwrap_err(); - - let (_, rustls_connection) = tls_stream.get_ref(); - assert!(rustls_connection.alpn_protocol().is_none()); - - let status = child.wait().unwrap(); - assert!(status.success()); - }) - .await; -} diff --git a/cli/tests/integration/check_tests.rs b/cli/tests/integration/check_tests.rs deleted file mode 100644 index 4b3e512c46..0000000000 --- a/cli/tests/integration/check_tests.rs +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use std::process::Command; -use std::process::Stdio; - -use crate::itest; - -use test_util as util; -use util::TempDir; - -itest!(_095_check_with_bare_import { - args: "check cache/095_cache_with_bare_import.ts", - output: "cache/095_cache_with_bare_import.ts.out", - exit_code: 1, -}); - -itest!(check_extensionless { - args: "check --reload http://localhost:4545/subdir/no_js_ext", - output: "cache/cache_extensionless.out", - http_server: true, -}); - -itest!(check_random_extension { - args: "check --reload http://localhost:4545/subdir/no_js_ext@1.0.0", - output: "cache/cache_random_extension.out", - http_server: true, -}); - -itest!(check_all { - args: "check --quiet --remote check/check_all.ts", - output: "check/check_all.out", - http_server: true, - exit_code: 1, -}); - -itest!(check_all_local { - args: "check --quiet check/check_all.ts", - output_str: Some(""), - http_server: true, -}); - -itest!(module_detection_force { - args: "check --quiet check/module_detection_force/main.ts", - output_str: Some(""), -}); - -// Regression test for https://github.com/denoland/deno/issues/14937. -itest!(declaration_header_file_with_no_exports { - args: "check --quiet check/declaration_header_file_with_no_exports.ts", - output_str: Some(""), -}); - -itest!(check_npm_install_diagnostics { - args: "check --quiet check/npm_install_diagnostics/main.ts", - output: "check/npm_install_diagnostics/main.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], - exit_code: 1, -}); - -itest!(check_export_equals_declaration_file { - args: "check --quiet check/export_equals_declaration_file/main.ts", - exit_code: 0, -}); - -#[test] -fn cache_switching_config_then_no_config() { - let deno_dir = util::new_deno_dir(); - assert!(does_type_checking(&deno_dir, true)); - assert!(does_type_checking(&deno_dir, false)); - - // should now not do type checking even when it changes - // configs because it previously did - assert!(!does_type_checking(&deno_dir, true)); - assert!(!does_type_checking(&deno_dir, false)); - - fn does_type_checking(deno_dir: &util::TempDir, with_config: bool) -> bool { - let mut cmd = util::deno_cmd_with_deno_dir(deno_dir); - cmd - .current_dir(util::testdata_path()) - .stderr(Stdio::piped()) - .arg("check") - .arg("check/cache_config_on_off/main.ts"); - if with_config { - cmd - .arg("--config") - .arg("check/cache_config_on_off/deno.json"); - } - let output = cmd.spawn().unwrap().wait_with_output().unwrap(); - assert!(output.status.success()); - - let stderr = std::str::from_utf8(&output.stderr).unwrap(); - stderr.contains("Check") - } -} - -#[test] -fn reload_flag() { - // should do type checking whenever someone specifies --reload - let deno_dir = util::new_deno_dir(); - assert!(does_type_checking(&deno_dir, false)); - assert!(!does_type_checking(&deno_dir, false)); - assert!(does_type_checking(&deno_dir, true)); - assert!(does_type_checking(&deno_dir, true)); - assert!(!does_type_checking(&deno_dir, false)); - - fn does_type_checking(deno_dir: &util::TempDir, reload: bool) -> bool { - let mut cmd = util::deno_cmd_with_deno_dir(deno_dir); - cmd - .current_dir(util::testdata_path()) - .stderr(Stdio::piped()) - .arg("check") - .arg("check/cache_config_on_off/main.ts"); - if reload { - cmd.arg("--reload"); - } - let output = cmd.spawn().unwrap().wait_with_output().unwrap(); - assert!(output.status.success()); - - let stderr = std::str::from_utf8(&output.stderr).unwrap(); - stderr.contains("Check") - } -} - -#[test] -fn typecheck_declarations_ns() { - let output = util::deno_cmd() - .arg("test") - .arg("--doc") - .arg(util::root_path().join("cli/tsc/dts/lib.deno.ns.d.ts")) - .output() - .unwrap(); - println!("stdout: {}", String::from_utf8(output.stdout).unwrap()); - println!("stderr: {}", String::from_utf8(output.stderr).unwrap()); - assert!(output.status.success()); -} - -#[test] -fn typecheck_declarations_unstable() { - let output = util::deno_cmd() - .arg("test") - .arg("--doc") - .arg("--unstable") - .arg(util::root_path().join("cli/tsc/dts/lib.deno.unstable.d.ts")) - .output() - .unwrap(); - println!("stdout: {}", String::from_utf8(output.stdout).unwrap()); - println!("stderr: {}", String::from_utf8(output.stderr).unwrap()); - assert!(output.status.success()); -} - -#[test] -fn typecheck_core() { - let deno_dir = TempDir::new(); - let test_file = deno_dir.path().join("test_deno_core_types.ts"); - std::fs::write( - &test_file, - format!( - "import \"{}\";", - deno_core::resolve_path( - util::root_path() - .join("core/lib.deno_core.d.ts") - .to_str() - .unwrap() - ) - .unwrap() - ), - ) - .unwrap(); - let output = util::deno_cmd_with_deno_dir(&deno_dir) - .arg("run") - .arg(test_file.to_str().unwrap()) - .output() - .unwrap(); - println!("stdout: {}", String::from_utf8(output.stdout).unwrap()); - println!("stderr: {}", String::from_utf8(output.stderr).unwrap()); - assert!(output.status.success()); -} - -#[test] -fn ts_no_recheck_on_redirect() { - let deno_dir = util::new_deno_dir(); - let e = util::deno_exe_path(); - - let redirect_ts = util::testdata_path().join("run/017_import_redirect.ts"); - assert!(redirect_ts.is_file()); - let mut cmd = Command::new(e.clone()); - cmd.env("DENO_DIR", deno_dir.path()); - let mut initial = cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--check") - .arg(redirect_ts.clone()) - .spawn() - .expect("failed to span script"); - let status_initial = - initial.wait().expect("failed to wait for child process"); - assert!(status_initial.success()); - - let mut cmd = Command::new(e); - cmd.env("DENO_DIR", deno_dir.path()); - let output = cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--check") - .arg(redirect_ts) - .output() - .expect("failed to spawn script"); - - assert!(std::str::from_utf8(&output.stderr).unwrap().is_empty()); -} diff --git a/cli/tests/integration/compile_tests.rs b/cli/tests/integration/compile_tests.rs deleted file mode 100644 index dea17e5986..0000000000 --- a/cli/tests/integration/compile_tests.rs +++ /dev/null @@ -1,533 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use std::fs::File; -use std::process::Command; -use test_util as util; -use test_util::TempDir; - -#[test] -fn compile() { - let dir = TempDir::new(); - let exe = if cfg!(windows) { - dir.path().join("welcome.exe") - } else { - dir.path().join("welcome") - }; - // try this twice to ensure it works with the cache - for _ in 0..2 { - let output = util::deno_cmd_with_deno_dir(&dir) - .current_dir(util::root_path()) - .arg("compile") - .arg("--unstable") - .arg("--output") - .arg(&exe) - .arg("./test_util/std/examples/welcome.ts") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let output = Command::new(&exe) - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, "Welcome to Deno!\n".as_bytes()); - } -} - -#[test] -fn standalone_args() { - let dir = TempDir::new(); - let exe = if cfg!(windows) { - dir.path().join("args.exe") - } else { - dir.path().join("args") - }; - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("compile") - .arg("--unstable") - .arg("--output") - .arg(&exe) - .arg("./compile/args.ts") - .arg("a") - .arg("b") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let output = Command::new(exe) - .arg("foo") - .arg("--bar") - .arg("--unstable") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"a\nb\nfoo\n--bar\n--unstable\n"); -} - -#[test] -fn standalone_error() { - let dir = TempDir::new(); - let exe = if cfg!(windows) { - dir.path().join("error.exe") - } else { - dir.path().join("error") - }; - let testdata_path = util::testdata_path(); - let output = util::deno_cmd() - .current_dir(&testdata_path) - .arg("compile") - .arg("--unstable") - .arg("--output") - .arg(&exe) - .arg("./compile/standalone_error.ts") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let output = Command::new(exe) - .env("NO_COLOR", "1") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - assert_eq!(output.stdout, b""); - let stderr = String::from_utf8(output.stderr).unwrap(); - let stderr = util::strip_ansi_codes(&stderr).to_string(); - // On Windows, we cannot assert the file path (because '\'). - // Instead we just check for relevant output. - assert!(stderr.contains("error: Uncaught Error: boom!")); - assert!(stderr.contains("throw new Error(\"boom!\");")); - assert!(stderr.contains("\n at boom (file://")); - assert!(stderr.contains("standalone_error.ts:2:11")); - assert!(stderr.contains("at foo (file://")); - assert!(stderr.contains("standalone_error.ts:5:5")); - assert!(stderr.contains("standalone_error.ts:7:1")); -} - -#[test] -fn standalone_error_module_with_imports() { - let dir = TempDir::new(); - let exe = if cfg!(windows) { - dir.path().join("error.exe") - } else { - dir.path().join("error") - }; - let testdata_path = util::testdata_path(); - let output = util::deno_cmd() - .current_dir(&testdata_path) - .arg("compile") - .arg("--unstable") - .arg("--output") - .arg(&exe) - .arg("./compile/standalone_error_module_with_imports_1.ts") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let output = Command::new(exe) - .env("NO_COLOR", "1") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - println!("{:#?}", &output); - assert_eq!(output.stdout, b"hello\n"); - let stderr = String::from_utf8(output.stderr).unwrap(); - let stderr = util::strip_ansi_codes(&stderr).to_string(); - // On Windows, we cannot assert the file path (because '\'). - // Instead we just check for relevant output. - assert!(stderr.contains("error: Uncaught Error: boom!")); - assert!(stderr.contains("throw new Error(\"boom!\");")); - assert!(stderr.contains("\n at file://")); - assert!(stderr.contains("standalone_error_module_with_imports_2.ts:2:7")); -} - -#[test] -fn standalone_load_datauri() { - let dir = TempDir::new(); - let exe = if cfg!(windows) { - dir.path().join("load_datauri.exe") - } else { - dir.path().join("load_datauri") - }; - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("compile") - .arg("--unstable") - .arg("--output") - .arg(&exe) - .arg("./compile/standalone_import_datauri.ts") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let output = Command::new(exe) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Hello Deno!\n"); -} - -// https://github.com/denoland/deno/issues/13704 -#[test] -fn standalone_follow_redirects() { - let dir = TempDir::new(); - let exe = if cfg!(windows) { - dir.path().join("follow_redirects.exe") - } else { - dir.path().join("follow_redirects") - }; - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("compile") - .arg("--unstable") - .arg("--output") - .arg(&exe) - .arg("./compile/standalone_follow_redirects.ts") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let output = Command::new(exe) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Hello\n"); -} - -#[test] -fn compile_with_file_exists_error() { - let dir = TempDir::new(); - let output_path = if cfg!(windows) { - dir.path().join(r"args\") - } else { - dir.path().join("args/") - }; - let file_path = dir.path().join("args"); - File::create(&file_path).unwrap(); - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("compile") - .arg("--unstable") - .arg("--output") - .arg(&output_path) - .arg("./compile/args.ts") - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - let expected_stderr = format!( - concat!( - "Could not compile to file '{}' because its parent directory ", - "is an existing file. You can use the `--output ` flag to ", - "provide an alternative name.\n", - ), - file_path.display(), - ); - let stderr = String::from_utf8(output.stderr).unwrap(); - assert!(stderr.contains(&expected_stderr)); -} - -#[test] -fn compile_with_directory_exists_error() { - let dir = TempDir::new(); - let exe = if cfg!(windows) { - dir.path().join("args.exe") - } else { - dir.path().join("args") - }; - std::fs::create_dir(&exe).unwrap(); - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("compile") - .arg("--unstable") - .arg("--output") - .arg(&exe) - .arg("./compile/args.ts") - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - let expected_stderr = format!( - concat!( - "Could not compile to file '{}' because a directory exists with ", - "the same name. You can use the `--output ` flag to ", - "provide an alternative name." - ), - exe.display() - ); - let stderr = String::from_utf8(output.stderr).unwrap(); - assert!(stderr.contains(&expected_stderr)); -} - -#[test] -fn compile_with_conflict_file_exists_error() { - let dir = TempDir::new(); - let exe = if cfg!(windows) { - dir.path().join("args.exe") - } else { - dir.path().join("args") - }; - std::fs::write(&exe, b"SHOULD NOT BE OVERWRITTEN").unwrap(); - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("compile") - .arg("--unstable") - .arg("--output") - .arg(&exe) - .arg("./compile/args.ts") - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - let expected_stderr = format!( - concat!( - "Could not compile to file '{}' because the file already exists ", - "and cannot be overwritten. Please delete the existing file or ", - "use the `--output 1); -} - -itest!(eval_basic { - args: "eval console.log(\"hello\")", - output_str: Some("hello\n"), -}); - -// Ugly parentheses due to whitespace delimiting problem. -itest!(eval_ts { - args: "eval --quiet --ext=ts console.log((123)as(number))", // 'as' is a TS keyword only - output_str: Some("123\n"), -}); - -itest!(dyn_import_eval { - args: "eval import('./subdir/mod4.js').then(console.log)", - output: "eval/dyn_import_eval.out", -}); - -// Cannot write the expression to evaluate as "console.log(typeof gc)" -// because itest! splits args on whitespace. -itest!(v8_flags_eval { - args: "eval --v8-flags=--expose-gc console.log(typeof(gc))", - output: "run/v8_flags.js.out", -}); - -itest!(check_local_by_default { - args: "eval --quiet import('http://localhost:4545/subdir/type_error.ts').then(console.log);", - output: "eval/check_local_by_default.out", - http_server: true, -}); - -itest!(check_local_by_default2 { - args: "eval --quiet import('./eval/check_local_by_default2.ts').then(console.log);", - output: "eval/check_local_by_default2.out", - http_server: true, -}); diff --git a/cli/tests/integration/flags_tests.rs b/cli/tests/integration/flags_tests.rs deleted file mode 100644 index 5e3814e223..0000000000 --- a/cli/tests/integration/flags_tests.rs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::itest; -use test_util as util; - -#[test] -fn help_flag() { - let status = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("--help") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); -} - -#[test] -fn version_short_flag() { - let status = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("-V") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); -} - -#[test] -fn version_long_flag() { - let status = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("--version") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); -} - -itest!(types { - args: "types", - output: "types/types.out", -}); diff --git a/cli/tests/integration/fmt_tests.rs b/cli/tests/integration/fmt_tests.rs deleted file mode 100644 index 53b45de199..0000000000 --- a/cli/tests/integration/fmt_tests.rs +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::itest; -use test_util as util; -use test_util::TempDir; - -#[test] -fn fmt_test() { - let t = TempDir::new(); - let testdata_fmt_dir = util::testdata_path().join("fmt"); - let fixed_js = testdata_fmt_dir.join("badly_formatted_fixed.js"); - let badly_formatted_original_js = - testdata_fmt_dir.join("badly_formatted.mjs"); - let badly_formatted_js = t.path().join("badly_formatted.js"); - let badly_formatted_js_str = badly_formatted_js.to_str().unwrap(); - std::fs::copy(&badly_formatted_original_js, &badly_formatted_js).unwrap(); - - let fixed_md = testdata_fmt_dir.join("badly_formatted_fixed.md"); - let badly_formatted_original_md = testdata_fmt_dir.join("badly_formatted.md"); - let badly_formatted_md = t.path().join("badly_formatted.md"); - let badly_formatted_md_str = badly_formatted_md.to_str().unwrap(); - std::fs::copy(&badly_formatted_original_md, &badly_formatted_md).unwrap(); - - let fixed_json = testdata_fmt_dir.join("badly_formatted_fixed.json"); - let badly_formatted_original_json = - testdata_fmt_dir.join("badly_formatted.json"); - let badly_formatted_json = t.path().join("badly_formatted.json"); - let badly_formatted_json_str = badly_formatted_json.to_str().unwrap(); - std::fs::copy(&badly_formatted_original_json, &badly_formatted_json).unwrap(); - // First, check formatting by ignoring the badly formatted file. - let status = util::deno_cmd() - .current_dir(&testdata_fmt_dir) - .arg("fmt") - .arg(format!( - "--ignore={},{},{}", - badly_formatted_js_str, badly_formatted_md_str, badly_formatted_json_str - )) - .arg("--check") - .arg(badly_formatted_js_str) - .arg(badly_formatted_md_str) - .arg(badly_formatted_json_str) - .spawn() - .unwrap() - .wait() - .unwrap(); - // No target files found - assert!(!status.success()); - - // Check without ignore. - let status = util::deno_cmd() - .current_dir(&testdata_fmt_dir) - .arg("fmt") - .arg("--check") - .arg(badly_formatted_js_str) - .arg(badly_formatted_md_str) - .arg(badly_formatted_json_str) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(!status.success()); - - // Format the source file. - let status = util::deno_cmd() - .current_dir(&testdata_fmt_dir) - .arg("fmt") - .arg(badly_formatted_js_str) - .arg(badly_formatted_md_str) - .arg(badly_formatted_json_str) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - let expected_js = std::fs::read_to_string(fixed_js).unwrap(); - let expected_md = std::fs::read_to_string(fixed_md).unwrap(); - let expected_json = std::fs::read_to_string(fixed_json).unwrap(); - let actual_js = std::fs::read_to_string(badly_formatted_js).unwrap(); - let actual_md = std::fs::read_to_string(badly_formatted_md).unwrap(); - let actual_json = std::fs::read_to_string(badly_formatted_json).unwrap(); - assert_eq!(expected_js, actual_js); - assert_eq!(expected_md, actual_md); - assert_eq!(expected_json, actual_json); -} - -#[test] -fn fmt_stdin_error() { - use std::io::Write; - let mut deno = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("fmt") - .arg("-") - .stdin(std::process::Stdio::piped()) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let stdin = deno.stdin.as_mut().unwrap(); - let invalid_js = b"import { example }"; - stdin.write_all(invalid_js).unwrap(); - let output = deno.wait_with_output().unwrap(); - // Error message might change. Just check stdout empty, stderr not. - assert!(output.stdout.is_empty()); - assert!(!output.stderr.is_empty()); - assert!(!output.status.success()); -} - -#[test] -fn fmt_ignore_unexplicit_files() { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .env("NO_COLOR", "1") - .arg("fmt") - .arg("--check") - .arg("--ignore=./") - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - assert_eq!( - String::from_utf8_lossy(&output.stderr), - "error: No target files found.\n" - ); -} - -#[test] -fn fmt_auto_ignore_git_and_node_modules() { - use std::fs::{create_dir_all, File}; - use std::io::Write; - use std::path::PathBuf; - fn create_bad_json(t: PathBuf) { - let bad_json_path = t.join("bad.json"); - let mut bad_json_file = File::create(bad_json_path).unwrap(); - writeln!(bad_json_file, "bad json").unwrap(); - } - let temp_dir = TempDir::new(); - let t = temp_dir.path().join("target"); - let nest_git = t.join("nest").join(".git"); - let git_dir = t.join(".git"); - let nest_node_modules = t.join("nest").join("node_modules"); - let node_modules_dir = t.join("node_modules"); - create_dir_all(&nest_git).unwrap(); - create_dir_all(&git_dir).unwrap(); - create_dir_all(&nest_node_modules).unwrap(); - create_dir_all(&node_modules_dir).unwrap(); - create_bad_json(nest_git); - create_bad_json(git_dir); - create_bad_json(nest_node_modules); - create_bad_json(node_modules_dir); - let output = util::deno_cmd() - .current_dir(t) - .env("NO_COLOR", "1") - .arg("fmt") - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - assert_eq!( - String::from_utf8_lossy(&output.stderr), - "error: No target files found.\n" - ); -} - -itest!(fmt_quiet_check_fmt_dir { - args: "fmt --check --quiet fmt/regular/", - output_str: Some(""), - exit_code: 0, -}); - -itest!(fmt_check_formatted_files { - args: "fmt --check fmt/regular/formatted1.js fmt/regular/formatted2.ts fmt/regular/formatted3.markdown fmt/regular/formatted4.jsonc", - output: "fmt/expected_fmt_check_formatted_files.out", - exit_code: 0, - }); - -itest!(fmt_check_ignore { - args: "fmt --check --ignore=fmt/regular/formatted1.js fmt/regular/", - output: "fmt/expected_fmt_check_ignore.out", - exit_code: 0, -}); - -itest!(fmt_check_parse_error { - args: "fmt --check fmt/parse_error/parse_error.ts", - output: "fmt/fmt_check_parse_error.out", - exit_code: 1, -}); - -itest!(fmt_stdin { - args: "fmt -", - input: Some("const a = 1\n"), - output_str: Some("const a = 1;\n"), -}); - -itest!(fmt_stdin_markdown { - args: "fmt --ext=md -", - input: Some("# Hello Markdown\n```ts\nconsole.log( \"text\")\n```\n\n```cts\nconsole.log( 5 )\n```"), - output_str: Some("# Hello Markdown\n\n```ts\nconsole.log(\"text\");\n```\n\n```cts\nconsole.log(5);\n```\n"), -}); - -itest!(fmt_stdin_json { - args: "fmt --ext=json -", - input: Some("{ \"key\": \"value\"}"), - output_str: Some("{ \"key\": \"value\" }\n"), -}); - -itest!(fmt_stdin_check_formatted { - args: "fmt --check -", - input: Some("const a = 1;\n"), - output_str: Some(""), -}); - -itest!(fmt_stdin_check_not_formatted { - args: "fmt --check -", - input: Some("const a = 1\n"), - output_str: Some("Not formatted stdin\n"), -}); - -itest!(fmt_with_config { - args: "fmt --config fmt/with_config/deno.jsonc fmt/with_config/subdir", - output: "fmt/fmt_with_config.out", -}); - -itest!(fmt_with_config_default { - args: "fmt fmt/with_config/subdir", - output: "fmt/fmt_with_config.out", -}); - -// Check if CLI flags take precedence -itest!(fmt_with_config_and_flags { - args: "fmt --config fmt/with_config/deno.jsonc --ignore=fmt/with_config/subdir/a.ts,fmt/with_config/subdir/b.ts", - output: "fmt/fmt_with_config_and_flags.out", -}); - -itest!(fmt_with_malformed_config { - args: "fmt --config fmt/deno.malformed.jsonc", - output: "fmt/fmt_with_malformed_config.out", - exit_code: 1, -}); - -itest!(fmt_with_malformed_config2 { - args: "fmt --config fmt/deno.malformed2.jsonc", - output: "fmt/fmt_with_malformed_config2.out", - exit_code: 1, -}); diff --git a/cli/tests/integration/info_tests.rs b/cli/tests/integration/info_tests.rs deleted file mode 100644 index 0d2d81cc08..0000000000 --- a/cli/tests/integration/info_tests.rs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::itest; - -use test_util as util; -use test_util::TempDir; - -#[test] -fn info_with_compiled_source() { - let _g = util::http_server(); - let module_path = "http://127.0.0.1:4545/run/048_media_types_jsx.ts"; - let t = TempDir::new(); - - let mut deno = util::deno_cmd() - .env("DENO_DIR", t.path()) - .current_dir(util::testdata_path()) - .arg("cache") - .arg(module_path) - .spawn() - .unwrap(); - let status = deno.wait().unwrap(); - assert!(status.success()); - - let output = util::deno_cmd() - .env("DENO_DIR", t.path()) - .env("NO_COLOR", "1") - .current_dir(util::testdata_path()) - .arg("info") - .arg(module_path) - .output() - .unwrap(); - - let str_output = std::str::from_utf8(&output.stdout).unwrap().trim(); - eprintln!("{}", str_output); - // check the output of the test.ts program. - assert!(str_output.contains("emit: ")); - assert_eq!(output.stderr, b""); -} - -itest!(multiple_imports { - args: "info http://127.0.0.1:4545/run/019_media_types.ts", - output: "info/multiple_imports.out", - http_server: true, -}); - -itest!(info_ts_error { - args: "info info/031_info_ts_error.ts", - output: "info/031_info_ts_error.out", -}); - -itest!(info_flag { - args: "info", - output: "info/041_info_flag.out", -}); - -itest!(info_flag_location { - args: "info --location https://deno.land", - output: "info/041_info_flag_location.out", -}); - -itest!(info_json { - args: "info --json --unstable", - output: "info/info_json.out", -}); - -itest!(info_json_location { - args: "info --json --unstable --location https://deno.land", - output: "info/info_json_location.out", -}); - -itest!(info_flag_script_jsx { - args: "info http://127.0.0.1:4545/run/048_media_types_jsx.ts", - output: "info/049_info_flag_script_jsx.out", - http_server: true, -}); - -itest!(json_file { - args: "info --quiet --json --unstable info/json_output/main.ts", - output: "info/json_output/main.out", - exit_code: 0, -}); - -itest!(import_map_info { - args: - "info --quiet --import-map=import_maps/import_map.json import_maps/test.ts", - output: "info/065_import_map_info.out", -}); - -itest!(info_json_deps_order { - args: "info --unstable --json info/076_info_json_deps_order.ts", - output: "info/076_info_json_deps_order.out", -}); - -itest!(info_missing_module { - args: "info info/error_009_missing_js_module.js", - output: "info/info_missing_module.out", -}); - -itest!(info_recursive_modules { - args: "info --quiet info/info_recursive_imports_test.ts", - output: "info/info_recursive_imports_test.out", - exit_code: 0, -}); - -itest!(info_type_import { - args: "info info/info_type_import.ts", - output: "info/info_type_import.out", -}); - -itest!(_054_info_local_imports { - args: "info --quiet run/005_more_imports.ts", - output: "info/054_info_local_imports.out", - exit_code: 0, -}); - -// Tests for AssertionError where "data" is unexpectedly null when -// a file contains only triple slash references (#11196) -itest!(data_null_error { - args: "info info/data_null_error/mod.ts", - output: "info/data_null_error/data_null_error.out", -}); - -itest!(types_header_direct { - args: "info --reload run/type_directives_01.ts", - output: "info/types_header.out", - http_server: true, -}); - -itest!(with_config_override { - args: "info info/with_config/test.ts --config info/with_config/deno-override.json --import-map info/with_config/import_map.json", - output: "info/with_config/with_config.out", -}); diff --git a/cli/tests/integration/init_tests.rs b/cli/tests/integration/init_tests.rs deleted file mode 100644 index e597e117c0..0000000000 --- a/cli/tests/integration/init_tests.rs +++ /dev/null @@ -1,159 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use std::process::Stdio; -use test_util as util; -use test_util::TempDir; -use util::assert_contains; - -#[test] -fn init_subcommand_without_dir() { - let temp_dir = TempDir::new(); - let cwd = temp_dir.path(); - let deno_dir = util::new_deno_dir(); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(cwd) - .arg("init") - .stderr(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let stderr = String::from_utf8(output.stderr).unwrap(); - assert_contains!(stderr, "Project initialized"); - assert!(!stderr.contains("cd")); - assert_contains!(stderr, "deno run main.ts"); - assert_contains!(stderr, "deno test"); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(cwd) - .env("NO_COLOR", "1") - .arg("run") - .arg("main.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Add 2 + 3 = 5\n"); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(cwd) - .env("NO_COLOR", "1") - .arg("test") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let stdout = String::from_utf8(output.stdout).unwrap(); - assert_contains!(stdout, "1 passed"); -} - -#[test] -fn init_subcommand_with_dir_arg() { - let temp_dir = TempDir::new(); - let cwd = temp_dir.path(); - let deno_dir = util::new_deno_dir(); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(cwd) - .arg("init") - .arg("my_dir") - .stderr(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let stderr = String::from_utf8(output.stderr).unwrap(); - assert_contains!(stderr, "Project initialized"); - assert_contains!(stderr, "cd my_dir"); - assert_contains!(stderr, "deno run main.ts"); - assert_contains!(stderr, "deno test"); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(cwd) - .env("NO_COLOR", "1") - .arg("run") - .arg("my_dir/main.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Add 2 + 3 = 5\n"); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(cwd) - .env("NO_COLOR", "1") - .arg("test") - .arg("my_dir/main_test.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let stdout = String::from_utf8(output.stdout).unwrap(); - assert_contains!(stdout, "1 passed"); -} - -#[test] -fn init_subcommand_with_quiet_arg() { - let temp_dir = TempDir::new(); - let cwd = temp_dir.path(); - let deno_dir = util::new_deno_dir(); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(cwd) - .arg("init") - .arg("--quiet") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let stdout = String::from_utf8(output.stdout).unwrap(); - assert_eq!(stdout, ""); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(cwd) - .env("NO_COLOR", "1") - .arg("run") - .arg("main.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Add 2 + 3 = 5\n"); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(cwd) - .env("NO_COLOR", "1") - .arg("test") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let stdout = String::from_utf8(output.stdout).unwrap(); - assert_contains!(stdout, "1 passed"); -} diff --git a/cli/tests/integration/inspector_tests.rs b/cli/tests/integration/inspector_tests.rs deleted file mode 100644 index f18b009da4..0000000000 --- a/cli/tests/integration/inspector_tests.rs +++ /dev/null @@ -1,1117 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use deno_core::futures; -use deno_core::futures::prelude::*; -use deno_core::futures::stream::SplitSink; -use deno_core::serde_json; -use deno_core::url; -use deno_runtime::deno_fetch::reqwest; -use deno_runtime::deno_websocket::tokio_tungstenite; -use deno_runtime::deno_websocket::tokio_tungstenite::tungstenite; -use std::io::BufRead; -use std::pin::Pin; -use test_util as util; -use tokio::net::TcpStream; - -macro_rules! assert_starts_with { - ($string:expr, $($test:expr),+) => { - let string = $string; // This might be a function call or something - if !($(string.starts_with($test))||+) { - panic!("{:?} does not start with {:?}", string, [$($test),+]); - } - } -} - -fn inspect_flag_with_unique_port(flag_prefix: &str) -> String { - use std::sync::atomic::{AtomicU16, Ordering}; - static PORT: AtomicU16 = AtomicU16::new(9229); - let port = PORT.fetch_add(1, Ordering::Relaxed); - format!("{}=127.0.0.1:{}", flag_prefix, port) -} - -fn extract_ws_url_from_stderr( - stderr_lines: &mut impl std::iter::Iterator, -) -> url::Url { - let stderr_first_line = skip_check_line(stderr_lines); - assert_starts_with!(&stderr_first_line, "Debugger listening on "); - let v: Vec<_> = stderr_first_line.match_indices("ws:").collect(); - assert_eq!(v.len(), 1); - let ws_url_index = v[0].0; - let ws_url = &stderr_first_line[ws_url_index..]; - url::Url::parse(ws_url).unwrap() -} - -fn skip_check_line( - stderr_lines: &mut impl std::iter::Iterator, -) -> String { - loop { - let mut line = stderr_lines.next().unwrap(); - line = util::strip_ansi_codes(&line).to_string(); - - if line.starts_with("Check") { - continue; - } - - return line; - } -} - -fn assert_stderr( - stderr_lines: &mut impl std::iter::Iterator, - expected_lines: &[&str], -) { - let mut expected_index = 0; - - loop { - let line = skip_check_line(stderr_lines); - - assert_eq!(line, expected_lines[expected_index]); - expected_index += 1; - - if expected_index >= expected_lines.len() { - break; - } - } -} - -fn assert_stderr_for_inspect( - stderr_lines: &mut impl std::iter::Iterator, -) { - assert_stderr( - stderr_lines, - &["Visit chrome://inspect to connect to the debugger."], - ); -} - -fn assert_stderr_for_inspect_brk( - stderr_lines: &mut impl std::iter::Iterator, -) { - assert_stderr( - stderr_lines, - &[ - "Visit chrome://inspect to connect to the debugger.", - "Deno is waiting for debugger to connect.", - ], - ); -} - -async fn assert_inspector_messages( - socket_tx: &mut SplitSink< - tokio_tungstenite::WebSocketStream< - tokio_tungstenite::MaybeTlsStream, - >, - tungstenite::Message, - >, - messages: &[&str], - socket_rx: &mut Pin>>, - responses: &[&str], - notifications: &[&str], -) { - for msg in messages { - socket_tx.send(msg.to_string().into()).await.unwrap(); - } - - let expected_messages = responses.len() + notifications.len(); - let mut responses_idx = 0; - let mut notifications_idx = 0; - - for _ in 0..expected_messages { - let msg = socket_rx.next().await.unwrap(); - - if msg.starts_with(r#"{"id":"#) { - assert!( - msg.starts_with(responses[responses_idx]), - "Doesn't start with {}, instead received {}", - responses[responses_idx], - msg - ); - responses_idx += 1; - } else { - assert!( - msg.starts_with(notifications[notifications_idx]), - "Doesn't start with {}, instead received {}", - notifications[notifications_idx], - msg - ); - notifications_idx += 1; - } - } -} - -#[tokio::test] -async fn inspector_connect() { - let script = util::testdata_path().join("inspector/inspector1.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - - // We use tokio_tungstenite as a websocket client because warp (which is - // a dependency of Deno) uses it. - let (_socket, response) = - tokio_tungstenite::connect_async(ws_url).await.unwrap(); - assert_eq!("101 Switching Protocols", response.status().to_string()); - child.kill().unwrap(); - child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_break_on_first_line() { - let script = util::testdata_path().join("inspector/inspector2.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .arg(script) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - - let (socket, response) = - tokio_tungstenite::connect_async(ws_url).await.unwrap(); - assert_eq!(response.status(), 101); // Switching protocols. - - let (mut socket_tx, socket_rx) = socket.split(); - let mut socket_rx = socket_rx - .map(|msg| msg.unwrap().to_string()) - .filter(|msg| { - let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); - futures::future::ready(pass) - }) - .boxed_local(); - - let stdout = child.stdout.as_mut().unwrap(); - let mut stdout_lines = - std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); - - assert_stderr_for_inspect_brk(&mut stderr_lines); - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":1,"method":"Runtime.enable"}"#, - r#"{"id":2,"method":"Debugger.enable"}"#, - ], - &mut socket_rx, - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], - &mut socket_rx, - &[r#"{"id":3,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":4,"method":"Runtime.evaluate","params":{"expression":"Deno.core.print(\"hello from the inspector\\n\")","contextId":1,"includeCommandLineAPI":true,"silent":false,"returnByValue":true}}"#, - ], - &mut socket_rx, - &[r#"{"id":4,"result":{"result":{"type":"undefined"}}}"#], - &[], - ) - .await; - - assert_eq!(&stdout_lines.next().unwrap(), "hello from the inspector"); - - assert_inspector_messages( - &mut socket_tx, - &[r#"{"id":5,"method":"Debugger.resume"}"#], - &mut socket_rx, - &[r#"{"id":5,"result":{}}"#], - &[], - ) - .await; - - assert_eq!(&stdout_lines.next().unwrap(), "hello from the script"); - - child.kill().unwrap(); - child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_pause() { - let script = util::testdata_path().join("inspector/inspector1.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - - // We use tokio_tungstenite as a websocket client because warp (which is - // a dependency of Deno) uses it. - let (mut socket, _) = tokio_tungstenite::connect_async(ws_url).await.unwrap(); - - /// Returns the next websocket message as a string ignoring - /// Debugger.scriptParsed messages. - async fn ws_read_msg( - socket: &mut tokio_tungstenite::WebSocketStream< - tokio_tungstenite::MaybeTlsStream, - >, - ) -> String { - use deno_core::futures::stream::StreamExt; - while let Some(msg) = socket.next().await { - let msg = msg.unwrap().to_string(); - // FIXME(bartlomieju): fails because there's a file loaded - // called 150_errors.js - // assert!(!msg.contains("error")); - if !msg.contains("Debugger.scriptParsed") { - return msg; - } - } - unreachable!() - } - - socket - .send(r#"{"id":6,"method":"Debugger.enable"}"#.into()) - .await - .unwrap(); - - let msg = ws_read_msg(&mut socket).await; - println!("response msg 1 {}", msg); - assert_starts_with!(msg, r#"{"id":6,"result":{"debuggerId":"#); - - socket - .send(r#"{"id":31,"method":"Debugger.pause"}"#.into()) - .await - .unwrap(); - - let msg = ws_read_msg(&mut socket).await; - println!("response msg 2 {}", msg); - assert_eq!(msg, r#"{"id":31,"result":{}}"#); - - child.kill().unwrap(); -} - -#[tokio::test] -async fn inspector_port_collision() { - // Skip this test on WSL, which allows multiple processes to listen on the - // same port, rather than making `bind()` fail with `EADDRINUSE`. - if cfg!(target_os = "linux") && std::env::var_os("WSL_DISTRO_NAME").is_some() - { - return; - } - - let script = util::testdata_path().join("inspector/inspector1.js"); - let inspect_flag = inspect_flag_with_unique_port("--inspect"); - - let mut child1 = util::deno_cmd() - .arg("run") - .arg(&inspect_flag) - .arg(script.clone()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr_1 = child1.stderr.as_mut().unwrap(); - let mut stderr_1_lines = std::io::BufReader::new(stderr_1) - .lines() - .map(|r| r.unwrap()); - let _ = extract_ws_url_from_stderr(&mut stderr_1_lines); - - let mut child2 = util::deno_cmd() - .arg("run") - .arg(&inspect_flag) - .arg(script) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr_2 = child2.stderr.as_mut().unwrap(); - let stderr_2_error_message = std::io::BufReader::new(stderr_2) - .lines() - .map(|r| r.unwrap()) - .inspect(|line| assert!(!line.contains("Debugger listening"))) - .find(|line| line.contains("Cannot start inspector server")); - assert!(stderr_2_error_message.is_some()); - - child1.kill().unwrap(); - child1.wait().unwrap(); - child2.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_does_not_hang() { - let script = util::testdata_path().join("inspector/inspector3.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .env("NO_COLOR", "1") - .arg(script) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - - let (socket, response) = - tokio_tungstenite::connect_async(ws_url).await.unwrap(); - assert_eq!(response.status(), 101); // Switching protocols. - - let (mut socket_tx, socket_rx) = socket.split(); - let mut socket_rx = socket_rx - .map(|msg| msg.unwrap().to_string()) - .filter(|msg| { - let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); - futures::future::ready(pass) - }) - .boxed_local(); - - let stdout = child.stdout.as_mut().unwrap(); - let mut stdout_lines = - std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); - - assert_stderr_for_inspect_brk(&mut stderr_lines); - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":1,"method":"Runtime.enable"}"#, - r#"{"id":2,"method":"Debugger.enable"}"#, - ], - &mut socket_rx, - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"# - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"# - ], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], - &mut socket_rx, - &[r#"{"id":3,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[r#"{"id":4,"method":"Debugger.resume"}"#], - &mut socket_rx, - &[r#"{"id":4,"result":{}}"#], - &[r#"{"method":"Debugger.resumed","params":{}}"#], - ) - .await; - - for i in 0..128u32 { - let request_id = i + 10; - // Expect the number {i} on stdout. - let s = i.to_string(); - assert_eq!(stdout_lines.next().unwrap(), s); - - assert_inspector_messages( - &mut socket_tx, - &[], - &mut socket_rx, - &[], - &[ - r#"{"method":"Runtime.consoleAPICalled","#, - r#"{"method":"Debugger.paused","#, - ], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[&format!( - r#"{{"id":{},"method":"Debugger.resume"}}"#, - request_id - )], - &mut socket_rx, - &[&format!(r#"{{"id":{},"result":{{}}}}"#, request_id)], - &[r#"{"method":"Debugger.resumed","params":{}}"#], - ) - .await; - } - - // Check that we can gracefully close the websocket connection. - socket_tx.close().await.unwrap(); - socket_rx.for_each(|_| async {}).await; - - assert_eq!(&stdout_lines.next().unwrap(), "done"); - assert!(child.wait().unwrap().success()); -} - -#[tokio::test] -async fn inspector_without_brk_runs_code() { - let script = util::testdata_path().join("inspector/inspector4.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let _ = extract_ws_url_from_stderr(&mut stderr_lines); - - // Check that inspector actually runs code without waiting for inspector - // connection. - let stdout = child.stdout.as_mut().unwrap(); - let mut stdout_lines = - std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); - let stdout_first_line = stdout_lines.next().unwrap(); - assert_eq!(stdout_first_line, "hello"); - - child.kill().unwrap(); - child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_runtime_evaluate_does_not_crash() { - let mut child = util::deno_cmd() - .arg("repl") - .arg(inspect_flag_with_unique_port("--inspect")) - .stdin(std::process::Stdio::piped()) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = std::io::BufReader::new(stderr) - .lines() - .map(|r| r.unwrap()) - .filter(|s| s.as_str() != "Debugger session started."); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - - let (socket, response) = - tokio_tungstenite::connect_async(ws_url).await.unwrap(); - assert_eq!(response.status(), 101); // Switching protocols. - - let (mut socket_tx, socket_rx) = socket.split(); - let mut socket_rx = socket_rx - .map(|msg| msg.unwrap().to_string()) - .filter(|msg| { - let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); - futures::future::ready(pass) - }) - .boxed_local(); - - let stdin = child.stdin.take().unwrap(); - - let stdout = child.stdout.as_mut().unwrap(); - let mut stdout_lines = std::io::BufReader::new(stdout) - .lines() - .map(|r| r.unwrap()) - .filter(|s| !s.starts_with("Deno ")); - - assert_stderr_for_inspect(&mut stderr_lines); - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":1,"method":"Runtime.enable"}"#, - r#"{"id":2,"method":"Debugger.enable"}"#, - ], - &mut socket_rx, - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - assert_eq!( - &stdout_lines.next().unwrap(), - "exit using ctrl+d, ctrl+c, or close()" - ); - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":3,"method":"Runtime.compileScript","params":{"expression":"Deno.cwd()","sourceURL":"","persistScript":false,"executionContextId":1}}"#, - ], - &mut socket_rx, - &[r#"{"id":3,"result":{}}"#], &[] - ).await; - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":4,"method":"Runtime.evaluate","params":{"expression":"Deno.cwd()","objectGroup":"console","includeCommandLineAPI":true,"silent":false,"contextId":1,"returnByValue":true,"generatePreview":true,"userGesture":true,"awaitPromise":false,"replMode":true}}"#, - ], - &mut socket_rx, - &[r#"{"id":4,"result":{"result":{"type":"string","value":""#], - &[], - ).await; - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":5,"method":"Runtime.evaluate","params":{"expression":"console.error('done');","objectGroup":"console","includeCommandLineAPI":true,"silent":false,"contextId":1,"returnByValue":true,"generatePreview":true,"userGesture":true,"awaitPromise":false,"replMode":true}}"#, - ], - &mut socket_rx, - &[r#"{"id":5,"result":{"result":{"type":"undefined"}}}"#], - &[r#"{"method":"Runtime.consoleAPICalled"#], - ).await; - - assert_eq!(&stderr_lines.next().unwrap(), "done"); - - drop(stdin); - child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_json() { - let script = util::testdata_path().join("inspector/inspector1.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - let mut url = ws_url.clone(); - let _ = url.set_scheme("http"); - url.set_path("/json"); - let resp = reqwest::get(url).await.unwrap(); - assert_eq!(resp.status(), reqwest::StatusCode::OK); - let endpoint_list: Vec = - serde_json::from_str(&resp.text().await.unwrap()).unwrap(); - let matching_endpoint = endpoint_list - .iter() - .find(|e| e["webSocketDebuggerUrl"] == ws_url.as_str()); - assert!(matching_endpoint.is_some()); - child.kill().unwrap(); -} - -#[tokio::test] -async fn inspector_json_list() { - let script = util::testdata_path().join("inspector/inspector1.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - let mut url = ws_url.clone(); - let _ = url.set_scheme("http"); - url.set_path("/json/list"); - let resp = reqwest::get(url).await.unwrap(); - assert_eq!(resp.status(), reqwest::StatusCode::OK); - let endpoint_list: Vec = - serde_json::from_str(&resp.text().await.unwrap()).unwrap(); - let matching_endpoint = endpoint_list - .iter() - .find(|e| e["webSocketDebuggerUrl"] == ws_url.as_str()); - assert!(matching_endpoint.is_some()); - child.kill().unwrap(); -} - -#[tokio::test] -async fn inspector_connect_non_ws() { - // https://github.com/denoland/deno/issues/11449 - // Verify we don't panic if non-WS connection is being established - let script = util::testdata_path().join("inspector/inspector1.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect")) - .arg(script) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let mut ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - // Change scheme to URL and try send a request. We're not interested - // in the request result, just that the process doesn't panic. - ws_url.set_scheme("http").unwrap(); - let resp = reqwest::get(ws_url).await.unwrap(); - assert_eq!("400 Bad Request", resp.status().to_string()); - child.kill().unwrap(); - child.wait().unwrap(); -} - -#[tokio::test] -#[ignore] // https://github.com/denoland/deno/issues/13491 -async fn inspector_break_on_first_line_in_test() { - let script = util::testdata_path().join("inspector/inspector_test.js"); - let mut child = util::deno_cmd() - .arg("test") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .arg(script) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - - let (socket, response) = - tokio_tungstenite::connect_async(ws_url).await.unwrap(); - assert_eq!(response.status(), 101); // Switching protocols. - - let (mut socket_tx, socket_rx) = socket.split(); - let mut socket_rx = socket_rx - .map(|msg| msg.unwrap().to_string()) - .filter(|msg| { - let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); - futures::future::ready(pass) - }) - .boxed_local(); - - let stdout = child.stdout.as_mut().unwrap(); - let mut stdout_lines = - std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); - - assert_stderr_for_inspect_brk(&mut stderr_lines); - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":1,"method":"Runtime.enable"}"#, - r#"{"id":2,"method":"Debugger.enable"}"#, - ], - &mut socket_rx, - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], - &mut socket_rx, - &[r#"{"id":3,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":4,"method":"Runtime.evaluate","params":{"expression":"Deno.core.print(\"hello from the inspector\\n\")","contextId":1,"includeCommandLineAPI":true,"silent":false,"returnByValue":true}}"#, - ], - &mut socket_rx, - &[r#"{"id":4,"result":{"result":{"type":"undefined"}}}"#], - &[], - ) - .await; - - assert_eq!(&stdout_lines.next().unwrap(), "hello from the inspector"); - - assert_inspector_messages( - &mut socket_tx, - &[r#"{"id":5,"method":"Debugger.resume"}"#], - &mut socket_rx, - &[r#"{"id":5,"result":{}}"#], - &[], - ) - .await; - - assert_starts_with!(&stdout_lines.next().unwrap(), "running 1 test from"); - assert!(&stdout_lines - .next() - .unwrap() - .contains("test has finished running")); - - child.kill().unwrap(); - child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_with_ts_files() { - let script = util::testdata_path().join("inspector/test.ts"); - let mut child = util::deno_cmd() - .arg("run") - .arg("--check") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .arg(script) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - - let (socket, response) = - tokio_tungstenite::connect_async(ws_url).await.unwrap(); - assert_eq!(response.status(), 101); // Switching protocols. - - let (mut socket_tx, socket_rx) = socket.split(); - let mut socket_rx = socket_rx - .map(|msg| msg.unwrap().to_string()) - .filter(|msg| { - let pass = (msg.starts_with(r#"{"method":"Debugger.scriptParsed","#) - && msg.contains("testdata/inspector")) - || !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); - futures::future::ready(pass) - }) - .boxed_local(); - - let stdout = child.stdout.as_mut().unwrap(); - let mut stdout_lines = - std::io::BufReader::new(stdout).lines().map(|r| r.unwrap()); - - assert_stderr_for_inspect_brk(&mut stderr_lines); - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":1,"method":"Runtime.enable"}"#, - r#"{"id":2,"method":"Debugger.enable"}"#, - ], - &mut socket_rx, - &[ - r#"{"id":1,"result":{}}"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - // receive messages with sources from this test - let script1 = socket_rx.next().await.unwrap(); - assert!(script1.contains("testdata/inspector/test.ts")); - let script1_id = { - let v: serde_json::Value = serde_json::from_str(&script1).unwrap(); - v["params"]["scriptId"].as_str().unwrap().to_string() - }; - let script2 = socket_rx.next().await.unwrap(); - assert!(script2.contains("testdata/inspector/foo.ts")); - let script2_id = { - let v: serde_json::Value = serde_json::from_str(&script2).unwrap(); - v["params"]["scriptId"].as_str().unwrap().to_string() - }; - let script3 = socket_rx.next().await.unwrap(); - assert!(script3.contains("testdata/inspector/bar.js")); - let script3_id = { - let v: serde_json::Value = serde_json::from_str(&script3).unwrap(); - v["params"]["scriptId"].as_str().unwrap().to_string() - }; - - assert_inspector_messages( - &mut socket_tx, - &[], - &mut socket_rx, - &[r#"{"id":2,"result":{"debuggerId":"#], - &[], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#], - &mut socket_rx, - &[r#"{"id":3,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[ - &format!(r#"{{"id":4,"method":"Debugger.getScriptSource","params":{{"scriptId":"{}"}}}}"#, script1_id), - &format!(r#"{{"id":5,"method":"Debugger.getScriptSource","params":{{"scriptId":"{}"}}}}"#, script2_id), - &format!(r#"{{"id":6,"method":"Debugger.getScriptSource","params":{{"scriptId":"{}"}}}}"#, script3_id), - ], - &mut socket_rx, - &[ - r#"{"id":4,"result":{"scriptSource":"import { foo } from \"./foo.ts\";\nimport { bar } from \"./bar.js\";\nconsole.log(foo());\nconsole.log(bar());\n//# sourceMappingURL=data:application/json;base64,"#, - r#"{"id":5,"result":{"scriptSource":"class Foo {\n hello() {\n return \"hello\";\n }\n}\nexport function foo() {\n const f = new Foo();\n return f.hello();\n}\n//# sourceMappingURL=data:application/json;base64,"#, - r#"{"id":6,"result":{"scriptSource":"export function bar() {\n return \"world\";\n}\n"#, - ], - &[], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[r#"{"id":7,"method":"Debugger.resume"}"#], - &mut socket_rx, - &[r#"{"id":7,"result":{}}"#], - &[], - ) - .await; - - assert_eq!(&stdout_lines.next().unwrap(), "hello"); - assert_eq!(&stdout_lines.next().unwrap(), "world"); - - child.kill().unwrap(); - child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_memory() { - let script = util::testdata_path().join("inspector/memory.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .arg(script) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - - let (socket, response) = - tokio_tungstenite::connect_async(ws_url).await.unwrap(); - assert_eq!(response.status(), 101); // Switching protocols. - - let (mut socket_tx, socket_rx) = socket.split(); - let mut socket_rx = socket_rx - .map(|msg| msg.unwrap().to_string()) - .filter(|msg| { - let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); - futures::future::ready(pass) - }) - .boxed_local(); - - assert_stderr_for_inspect_brk(&mut stderr_lines); - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":1,"method":"Runtime.enable"}"#, - r#"{"id":2,"method":"Debugger.enable"}"#, - - ], - &mut socket_rx, - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#, - r#"{"id":4,"method":"HeapProfiler.enable"}"#, - ], - &mut socket_rx, - &[r#"{"id":3,"result":{}}"#, r#"{"id":4,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - socket_tx - .send( - r#"{"id":5,"method":"Runtime.getHeapUsage", "params": {}}"# - .to_string() - .into(), - ) - .await - .unwrap(); - let msg = socket_rx.next().await.unwrap(); - let json_msg: serde_json::Value = serde_json::from_str(&msg).unwrap(); - assert_eq!(json_msg["id"].as_i64().unwrap(), 5); - let result = &json_msg["result"]; - assert!( - result["usedSize"].as_i64().unwrap() - <= result["totalSize"].as_i64().unwrap() - ); - - socket_tx.send( - r#"{"id":6,"method":"HeapProfiler.takeHeapSnapshot","params": {"reportProgress": true, "treatGlobalObjectsAsRoots": true, "captureNumberValue": false}}"# - .to_string().into() - ).await.unwrap(); - - let mut progress_report_completed = false; - loop { - let msg = socket_rx.next().await.unwrap(); - - if !progress_report_completed - && msg.starts_with( - r#"{"method":"HeapProfiler.reportHeapSnapshotProgress","params""#, - ) - { - let json_msg: serde_json::Value = serde_json::from_str(&msg).unwrap(); - if let Some(finished) = json_msg["params"].get("finished") { - progress_report_completed = finished.as_bool().unwrap(); - } - continue; - } - - if msg.starts_with(r#"{"method":"HeapProfiler.reportHeapSnapshotProgress","params":{"done":"#,) { - continue; - } - - if msg.starts_with(r#"{"id":6,"result":{}}"#) { - assert!(progress_report_completed); - break; - } - } - - child.kill().unwrap(); - child.wait().unwrap(); -} - -#[tokio::test] -async fn inspector_profile() { - let script = util::testdata_path().join("inspector/memory.js"); - let mut child = util::deno_cmd() - .arg("run") - .arg(inspect_flag_with_unique_port("--inspect-brk")) - .arg(script) - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stderr = child.stderr.as_mut().unwrap(); - let mut stderr_lines = - std::io::BufReader::new(stderr).lines().map(|r| r.unwrap()); - let ws_url = extract_ws_url_from_stderr(&mut stderr_lines); - - let (socket, response) = - tokio_tungstenite::connect_async(ws_url).await.unwrap(); - assert_eq!(response.status(), 101); // Switching protocols. - - let (mut socket_tx, socket_rx) = socket.split(); - let mut socket_rx = socket_rx - .map(|msg| msg.unwrap().to_string()) - .filter(|msg| { - let pass = !msg.starts_with(r#"{"method":"Debugger.scriptParsed","#); - futures::future::ready(pass) - }) - .boxed_local(); - - assert_stderr_for_inspect_brk(&mut stderr_lines); - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":1,"method":"Runtime.enable"}"#, - r#"{"id":2,"method":"Debugger.enable"}"#, - - ], - &mut socket_rx, - &[ - r#"{"id":1,"result":{}}"#, - r#"{"id":2,"result":{"debuggerId":"#, - ], - &[ - r#"{"method":"Runtime.executionContextCreated","params":{"context":{"id":1,"#, - ], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":3,"method":"Runtime.runIfWaitingForDebugger"}"#, - r#"{"id":4,"method":"Profiler.enable"}"#, - ], - &mut socket_rx, - &[r#"{"id":3,"result":{}}"#, r#"{"id":4,"result":{}}"#], - &[r#"{"method":"Debugger.paused","#], - ) - .await; - - assert_inspector_messages( - &mut socket_tx, - &[ - r#"{"id":5,"method":"Profiler.setSamplingInterval","params":{"interval": 100}}"#, - r#"{"id":6,"method":"Profiler.start","params":{}}"#, - ], - &mut socket_rx, - &[r#"{"id":5,"result":{}}"#, r#"{"id":6,"result":{}}"#], - &[], - ) - .await; - - tokio::time::sleep(tokio::time::Duration::from_millis(500)).await; - - socket_tx - .send( - r#"{"id":7,"method":"Profiler.stop", "params": {}}"#.to_string().into(), - ) - .await - .unwrap(); - let msg = socket_rx.next().await.unwrap(); - let json_msg: serde_json::Value = serde_json::from_str(&msg).unwrap(); - assert_eq!(json_msg["id"].as_i64().unwrap(), 7); - let result = &json_msg["result"]; - let profile = &result["profile"]; - assert!( - profile["startTime"].as_i64().unwrap() - < profile["endTime"].as_i64().unwrap() - ); - profile["samples"].as_array().unwrap(); - profile["nodes"].as_array().unwrap(); - - child.kill().unwrap(); - child.wait().unwrap(); -} diff --git a/cli/tests/integration/install_tests.rs b/cli/tests/integration/install_tests.rs deleted file mode 100644 index 2db32bd285..0000000000 --- a/cli/tests/integration/install_tests.rs +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use std::fs; -use std::process::Command; -use test_util as util; -use test_util::assert_contains; -use test_util::assert_ends_with; -use test_util::TempDir; - -#[test] -fn install_basic() { - let _guard = util::http_server(); - let temp_dir = TempDir::new(); - let temp_dir_str = temp_dir.path().to_string_lossy().to_string(); - - let status = util::deno_cmd() - .current_dir(temp_dir.path()) - .arg("install") - .arg("--check") - .arg("--name") - .arg("echo_test") - .arg("http://localhost:4545/echo.ts") - .envs([ - ("HOME", temp_dir_str.as_str()), - ("USERPROFILE", temp_dir_str.as_str()), - ("DENO_INSTALL_ROOT", ""), - ]) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - - let mut file_path = temp_dir.path().join(".deno/bin/echo_test"); - assert!(file_path.exists()); - - if cfg!(windows) { - file_path = file_path.with_extension("cmd"); - } - - let content = fs::read_to_string(file_path).unwrap(); - // ensure there's a trailing newline so the shell script can be - // more versatile. - assert_eq!(content.chars().last().unwrap(), '\n'); - - if cfg!(windows) { - assert_contains!( - content, - r#""run" "--check" "http://localhost:4545/echo.ts""# - ); - } else { - assert_contains!(content, r#"run --check 'http://localhost:4545/echo.ts'"#); - } -} - -#[test] -fn install_custom_dir_env_var() { - let _guard = util::http_server(); - let temp_dir = TempDir::new(); - let temp_dir_str = temp_dir.path().to_string_lossy().to_string(); - - let status = util::deno_cmd() - .current_dir(util::root_path()) // different cwd - .arg("install") - .arg("--check") - .arg("--name") - .arg("echo_test") - .arg("http://localhost:4545/echo.ts") - .envs([ - ("HOME", temp_dir_str.as_str()), - ("USERPROFILE", temp_dir_str.as_str()), - ("DENO_INSTALL_ROOT", temp_dir_str.as_str()), - ]) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - - let mut file_path = temp_dir.path().join("bin/echo_test"); - assert!(file_path.exists()); - - if cfg!(windows) { - file_path = file_path.with_extension("cmd"); - } - - let content = fs::read_to_string(file_path).unwrap(); - if cfg!(windows) { - assert_contains!( - content, - r#""run" "--check" "http://localhost:4545/echo.ts""# - ); - } else { - assert_contains!(content, r#"run --check 'http://localhost:4545/echo.ts'"#); - } -} - -#[test] -fn installer_test_local_module_run() { - let temp_dir = TempDir::new(); - let bin_dir = temp_dir.path().join("bin"); - std::fs::create_dir(&bin_dir).unwrap(); - let status = util::deno_cmd() - .current_dir(util::root_path()) - .arg("install") - .arg("--name") - .arg("echo_test") - .arg("--root") - .arg(temp_dir.path()) - .arg(util::testdata_path().join("echo.ts")) - .arg("hello") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - let mut file_path = bin_dir.join("echo_test"); - if cfg!(windows) { - file_path = file_path.with_extension("cmd"); - } - assert!(file_path.exists()); - // NOTE: using file_path here instead of exec_name, because tests - // shouldn't mess with user's PATH env variable - let output = Command::new(file_path) - .current_dir(temp_dir.path()) - .arg("foo") - .env("PATH", util::target_dir()) - .output() - .unwrap(); - let stdout_str = std::str::from_utf8(&output.stdout).unwrap().trim(); - assert_ends_with!(stdout_str, "hello, foo"); -} - -#[test] -fn installer_test_remote_module_run() { - let _g = util::http_server(); - let temp_dir = TempDir::new(); - let bin_dir = temp_dir.path().join("bin"); - std::fs::create_dir(&bin_dir).unwrap(); - let status = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("install") - .arg("--name") - .arg("echo_test") - .arg("--root") - .arg(temp_dir.path()) - .arg("http://localhost:4545/echo.ts") - .arg("hello") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - let mut file_path = bin_dir.join("echo_test"); - if cfg!(windows) { - file_path = file_path.with_extension("cmd"); - } - assert!(file_path.exists()); - let output = Command::new(file_path) - .current_dir(temp_dir.path()) - .arg("foo") - .env("PATH", util::target_dir()) - .output() - .unwrap(); - assert_ends_with!( - std::str::from_utf8(&output.stdout).unwrap().trim(), - "hello, foo", - ); -} - -#[test] -fn check_local_by_default() { - let _guard = util::http_server(); - let temp_dir = TempDir::new(); - let temp_dir_str = temp_dir.path().to_string_lossy().to_string(); - - let status = util::deno_cmd() - .current_dir(temp_dir.path()) - .arg("install") - .arg(util::testdata_path().join("./install/check_local_by_default.ts")) - .envs([ - ("HOME", temp_dir_str.as_str()), - ("USERPROFILE", temp_dir_str.as_str()), - ("DENO_INSTALL_ROOT", ""), - ]) - .status() - .unwrap(); - assert!(status.success()); -} - -#[test] -fn check_local_by_default2() { - let _guard = util::http_server(); - let temp_dir = TempDir::new(); - let temp_dir_str = temp_dir.path().to_string_lossy().to_string(); - - let status = util::deno_cmd() - .current_dir(temp_dir.path()) - .arg("install") - .arg(util::testdata_path().join("./install/check_local_by_default2.ts")) - .envs([ - ("HOME", temp_dir_str.as_str()), - ("NO_COLOR", "1"), - ("USERPROFILE", temp_dir_str.as_str()), - ("DENO_INSTALL_ROOT", ""), - ]) - .status() - .unwrap(); - assert!(status.success()); -} diff --git a/cli/tests/integration/lint_tests.rs b/cli/tests/integration/lint_tests.rs deleted file mode 100644 index 70a3cbbd47..0000000000 --- a/cli/tests/integration/lint_tests.rs +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::itest; -use test_util as util; - -#[test] -fn ignore_unexplicit_files() { - let output = util::deno_cmd() - .current_dir(util::root_path()) - .env("NO_COLOR", "1") - .arg("lint") - .arg("--unstable") - .arg("--ignore=./") - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - assert_eq!( - String::from_utf8_lossy(&output.stderr), - "error: No target files found.\n" - ); -} - -itest!(all { - args: "lint lint/without_config/file1.js lint/without_config/file2.ts lint/without_config/ignored_file.ts", - output: "lint/expected.out", - exit_code: 1, -}); - -itest!(quiet { - args: "lint --quiet lint/without_config/file1.js", - output: "lint/expected_quiet.out", - exit_code: 1, -}); - -itest!(json { - args: - "lint --json lint/without_config/file1.js lint/without_config/file2.ts lint/without_config/ignored_file.ts lint/without_config/malformed.js", - output: "lint/expected_json.out", - exit_code: 1, -}); - -itest!(compact { - args: - "lint --compact lint/without_config/file1.js lint/without_config/ignored_file.tss", - output: "lint/expected_compact.out", - exit_code: 1, -}); - -itest!(ignore { - args: - "lint --ignore=lint/without_config/file1.js,lint/without_config/malformed.js,lint/without_config/lint_with_config/ lint/without_config/", - output: "lint/expected_ignore.out", - exit_code: 1, -}); - -itest!(glob { - args: "lint --ignore=lint/without_config/malformed.js,lint/with_config/ lint/without_config/", - output: "lint/expected_glob.out", - exit_code: 1, -}); - -itest!(stdin { - args: "lint -", - input: Some("let _a: any;"), - output: "lint/expected_from_stdin.out", - exit_code: 1, -}); - -itest!(stdin_json { - args: "lint --json -", - input: Some("let _a: any;"), - output: "lint/expected_from_stdin_json.out", - exit_code: 1, -}); - -itest!(rules { - args: "lint --rules", - output: "lint/expected_rules.out", - exit_code: 0, -}); - -// Make sure that the rules are printed if quiet option is enabled. -itest!(rules_quiet { - args: "lint --rules -q", - output: "lint/expected_rules.out", - exit_code: 0, -}); - -itest!(lint_with_config { - args: "lint --config lint/Deno.jsonc lint/with_config/", - output: "lint/with_config.out", - exit_code: 1, -}); - -itest!(lint_with_report_config { - args: "lint --config lint/Deno.compact.format.jsonc lint/with_config/", - output: "lint/with_report_config_compact.out", - exit_code: 1, -}); - -// Check if CLI flags take precedence -itest!(lint_with_report_config_override { - args: "lint --config lint/Deno.compact.format.jsonc lint/with_config/ --json", - output: "lint/with_report_config_override.out", - exit_code: 1, -}); - -itest!(lint_with_config_and_flags { - args: "lint --config lint/Deno.jsonc --ignore=lint/with_config/a.ts", - output: "lint/with_config_and_flags.out", - exit_code: 1, -}); - -itest!(lint_with_config_without_tags { - args: "lint --config lint/Deno.no_tags.jsonc lint/with_config/", - output: "lint/with_config_without_tags.out", - exit_code: 1, -}); - -itest!(lint_with_malformed_config { - args: "lint --config lint/Deno.malformed.jsonc", - output: "lint/with_malformed_config.out", - exit_code: 1, -}); - -itest!(lint_with_malformed_config2 { - args: "lint --config lint/Deno.malformed2.jsonc", - output: "lint/with_malformed_config2.out", - exit_code: 1, -}); diff --git a/cli/tests/integration/lsp_tests.rs b/cli/tests/integration/lsp_tests.rs deleted file mode 100644 index f123f71a37..0000000000 --- a/cli/tests/integration/lsp_tests.rs +++ /dev/null @@ -1,6265 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use deno_ast::ModuleSpecifier; -use deno_core::serde::de::DeserializeOwned; -use deno_core::serde::Deserialize; -use deno_core::serde::Serialize; -use deno_core::serde_json; -use deno_core::serde_json::json; -use deno_core::serde_json::Value; -use deno_core::url::Url; -use pretty_assertions::assert_eq; -use std::collections::HashSet; -use std::fs; -use test_util::deno_exe_path; -use test_util::http_server; -use test_util::lsp::LspClient; -use test_util::testdata_path; -use test_util::TempDir; -use tower_lsp::lsp_types as lsp; - -fn load_fixture(path: &str) -> Value { - load_fixture_as(path) -} - -fn load_fixture_as(path: &str) -> T -where - T: DeserializeOwned, -{ - let fixture_str = load_fixture_str(path); - serde_json::from_str::(&fixture_str).unwrap() -} - -fn load_fixture_str(path: &str) -> String { - let fixtures_path = testdata_path().join("lsp"); - let path = fixtures_path.join(path); - fs::read_to_string(path).unwrap() -} - -fn init(init_path: &str) -> LspClient { - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", load_fixture(init_path)) - .unwrap(); - client.write_notification("initialized", json!({})).unwrap(); - client -} - -fn did_open( - client: &mut LspClient, - params: V, -) -> Vec -where - V: Serialize, -{ - client - .write_notification("textDocument/didOpen", params) - .unwrap(); - - handle_configuration_request( - client, - json!([{ - "enable": true, - "codeLens": { - "test": true - } - }]), - ); - read_diagnostics(client).0 -} - -fn handle_configuration_request(client: &mut LspClient, result: Value) { - let (id, method, _) = client.read_request::().unwrap(); - assert_eq!(method, "workspace/configuration"); - client.write_response(id, result).unwrap(); -} - -fn read_diagnostics(client: &mut LspClient) -> CollectedDiagnostics { - // diagnostics come in batches of three unless they're cancelled - let mut diagnostics = vec![]; - for _ in 0..3 { - let (method, response) = client - .read_notification::() - .unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - diagnostics.push(response.unwrap()); - } - CollectedDiagnostics(diagnostics) -} - -fn shutdown(client: &mut LspClient) { - client - .write_request::<_, _, Value>("shutdown", json!(null)) - .unwrap(); - client.write_notification("exit", json!(null)).unwrap(); -} - -pub fn ensure_directory_specifier( - mut specifier: ModuleSpecifier, -) -> ModuleSpecifier { - let path = specifier.path(); - if !path.ends_with('/') { - let new_path = format!("{}/", path); - specifier.set_path(&new_path); - } - specifier -} - -struct TestSession { - client: LspClient, - open_file_count: usize, -} - -impl TestSession { - pub fn from_file(init_path: &str) -> Self { - Self::from_client(init(init_path)) - } - - pub fn from_client(client: LspClient) -> Self { - Self { - client, - open_file_count: 0, - } - } - - pub fn did_open(&mut self, params: V) -> CollectedDiagnostics - where - V: Serialize, - { - self - .client - .write_notification("textDocument/didOpen", params) - .unwrap(); - - let (id, method, _) = self.client.read_request::().unwrap(); - assert_eq!(method, "workspace/configuration"); - self - .client - .write_response( - id, - json!([{ - "enable": true, - "codeLens": { - "test": true - } - }]), - ) - .unwrap(); - - self.open_file_count += 1; - self.read_diagnostics() - } - - pub fn read_diagnostics(&mut self) -> CollectedDiagnostics { - let mut all_diagnostics = Vec::new(); - for _ in 0..self.open_file_count { - all_diagnostics.extend(read_diagnostics(&mut self.client).0); - } - CollectedDiagnostics(all_diagnostics) - } - - pub fn shutdown_and_exit(&mut self) { - shutdown(&mut self.client); - } -} - -#[derive(Debug, Clone)] -struct CollectedDiagnostics(Vec); - -impl CollectedDiagnostics { - /// Gets the diagnostics that the editor will see after all the publishes. - pub fn viewed(&self) -> Vec { - self - .viewed_messages() - .into_iter() - .flat_map(|m| m.diagnostics) - .collect() - } - - /// Gets the messages that the editor will see after all the publishes. - pub fn viewed_messages(&self) -> Vec { - // go over the publishes in reverse order in order to get - // the final messages that will be shown in the editor - let mut messages = Vec::new(); - let mut had_specifier = HashSet::new(); - for message in self.0.iter().rev() { - if had_specifier.insert(message.uri.clone()) { - messages.insert(0, message.clone()); - } - } - messages - } - - pub fn with_source(&self, source: &str) -> lsp::PublishDiagnosticsParams { - self - .viewed_messages() - .iter() - .find(|p| { - p.diagnostics - .iter() - .any(|d| d.source == Some(source.to_string())) - }) - .map(ToOwned::to_owned) - .unwrap() - } - - pub fn with_file_and_source( - &self, - specifier: &str, - source: &str, - ) -> lsp::PublishDiagnosticsParams { - let specifier = ModuleSpecifier::parse(specifier).unwrap(); - self - .viewed_messages() - .iter() - .find(|p| { - p.uri == specifier - && p - .diagnostics - .iter() - .any(|d| d.source == Some(source.to_string())) - }) - .map(ToOwned::to_owned) - .unwrap() - } -} - -#[test] -fn lsp_startup_shutdown() { - let mut client = init("initialize_params.json"); - shutdown(&mut client); -} - -#[test] -fn lsp_init_tsconfig() { - let temp_dir = TempDir::new(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - let tsconfig = - serde_json::to_vec_pretty(&load_fixture("lib.tsconfig.json")).unwrap(); - fs::write(temp_dir.path().join("lib.tsconfig.json"), tsconfig).unwrap(); - - params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); - if let Some(Value::Object(mut map)) = params.initialization_options { - map.insert("config".to_string(), json!("./lib.tsconfig.json")); - params.initialization_options = Some(Value::Object(map)); - } - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - client.write_notification("initialized", json!({})).unwrap(); - - let diagnostics = did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "location.pathname;\n" - } - }), - ); - - let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); - assert_eq!(diagnostics.count(), 0); - - shutdown(&mut client); -} - -#[test] -fn lsp_tsconfig_types() { - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - let temp_dir = TempDir::new(); - let tsconfig = - serde_json::to_vec_pretty(&load_fixture("types.tsconfig.json")).unwrap(); - fs::write(temp_dir.path().join("types.tsconfig.json"), tsconfig).unwrap(); - let a_dts = load_fixture_str("a.d.ts"); - fs::write(temp_dir.path().join("a.d.ts"), a_dts).unwrap(); - - params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); - if let Some(Value::Object(mut map)) = params.initialization_options { - map.insert("config".to_string(), json!("./types.tsconfig.json")); - params.initialization_options = Some(Value::Object(map)); - } - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - client.write_notification("initialized", json!({})).unwrap(); - - let diagnostics = did_open( - &mut client, - json!({ - "textDocument": { - "uri": Url::from_file_path(temp_dir.path().join("test.ts")).unwrap(), - "languageId": "typescript", - "version": 1, - "text": "console.log(a);\n" - } - }), - ); - - let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); - assert_eq!(diagnostics.count(), 0); - - shutdown(&mut client); -} - -#[test] -fn lsp_tsconfig_bad_config_path() { - let mut client = init("initialize_params_bad_config_option.json"); - let (method, maybe_params) = client.read_notification().unwrap(); - assert_eq!(method, "window/showMessage"); - assert_eq!(maybe_params, Some(lsp::ShowMessageParams { - typ: lsp::MessageType::WARNING, - message: "The path to the configuration file (\"bad_tsconfig.json\") is not resolvable.".to_string() - })); - let diagnostics = did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Deno.args);\n" - } - }), - ); - let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); - assert_eq!(diagnostics.count(), 0); -} - -#[test] -fn lsp_triple_slash_types() { - let temp_dir = TempDir::new(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - let a_dts = load_fixture_str("a.d.ts"); - fs::write(temp_dir.path().join("a.d.ts"), a_dts).unwrap(); - - params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - client.write_notification("initialized", json!({})).unwrap(); - - let diagnostics = did_open( - &mut client, - json!({ - "textDocument": { - "uri": Url::from_file_path(temp_dir.path().join("test.ts")).unwrap(), - "languageId": "typescript", - "version": 1, - "text": "/// \n\nconsole.log(a);\n" - } - }), - ); - - let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); - assert_eq!(diagnostics.count(), 0); - - shutdown(&mut client); -} - -#[test] -fn lsp_import_map() { - let temp_dir = TempDir::new(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - let import_map = - serde_json::to_vec_pretty(&load_fixture("import-map.json")).unwrap(); - fs::write(temp_dir.path().join("import-map.json"), import_map).unwrap(); - fs::create_dir(temp_dir.path().join("lib")).unwrap(); - fs::write( - temp_dir.path().join("lib").join("b.ts"), - r#"export const b = "b";"#, - ) - .unwrap(); - - params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); - if let Some(Value::Object(mut map)) = params.initialization_options { - map.insert("importMap".to_string(), json!("import-map.json")); - params.initialization_options = Some(Value::Object(map)); - } - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - client.write_notification("initialized", json!({})).unwrap(); - let uri = Url::from_file_path(temp_dir.path().join("a.ts")).unwrap(); - - let diagnostics = did_open( - &mut client, - json!({ - "textDocument": { - "uri": uri, - "languageId": "typescript", - "version": 1, - "text": "import { b } from \"/~/b.ts\";\n\nconsole.log(b);\n" - } - }), - ); - - let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); - assert_eq!(diagnostics.count(), 0); - - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": uri - }, - "position": { - "line": 2, - "character": 12 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value":"(alias) const b: \"b\"\nimport b" - }, - "" - ], - "range": { - "start": { - "line": 2, - "character": 12 - }, - "end": { - "line": 2, - "character": 13 - } - } - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_import_map_data_url() { - let mut client = init("initialize_params_import_map.json"); - let diagnostics = did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import example from \"example\";\n" - } - }), - ); - - let mut diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); - // This indicates that the import map from initialize_params_import_map.json - // is applied correctly. - assert!(diagnostics.any(|diagnostic| diagnostic.code - == Some(lsp::NumberOrString::String("no-cache".to_string())) - && diagnostic - .message - .contains("https://deno.land/x/example/mod.ts"))); - shutdown(&mut client); -} - -#[test] -fn lsp_import_map_config_file() { - let temp_dir = TempDir::new(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - - let deno_import_map_jsonc = - serde_json::to_vec_pretty(&load_fixture("deno.import_map.jsonc")).unwrap(); - fs::write( - temp_dir.path().join("deno.import_map.jsonc"), - deno_import_map_jsonc, - ) - .unwrap(); - - params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); - if let Some(Value::Object(mut map)) = params.initialization_options { - map.insert("config".to_string(), json!("./deno.import_map.jsonc")); - params.initialization_options = Some(Value::Object(map)); - } - let import_map = - serde_json::to_vec_pretty(&load_fixture("import-map.json")).unwrap(); - fs::write(temp_dir.path().join("import-map.json"), import_map).unwrap(); - fs::create_dir(temp_dir.path().join("lib")).unwrap(); - fs::write( - temp_dir.path().join("lib").join("b.ts"), - r#"export const b = "b";"#, - ) - .unwrap(); - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - client.write_notification("initialized", json!({})).unwrap(); - let uri = Url::from_file_path(temp_dir.path().join("a.ts")).unwrap(); - - let diagnostics = did_open( - &mut client, - json!({ - "textDocument": { - "uri": uri, - "languageId": "typescript", - "version": 1, - "text": "import { b } from \"/~/b.ts\";\n\nconsole.log(b);\n" - } - }), - ); - - let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); - assert_eq!(diagnostics.count(), 0); - - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": uri - }, - "position": { - "line": 2, - "character": 12 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value":"(alias) const b: \"b\"\nimport b" - }, - "" - ], - "range": { - "start": { - "line": 2, - "character": 12 - }, - "end": { - "line": 2, - "character": 13 - } - } - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_deno_task() { - let temp_dir = TempDir::new(); - let workspace_root = temp_dir.path().canonicalize().unwrap(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - fs::write( - workspace_root.join("deno.jsonc"), - r#"{ - "tasks": { - "build": "deno test", - "some:test": "deno bundle mod.ts" - } - }"#, - ) - .unwrap(); - - params.root_uri = Some(Url::from_file_path(workspace_root).unwrap()); - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>("deno/task", json!(null)) - .unwrap(); - - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!([ - { - "name": "build", - "detail": "deno test" - }, - { - "name": "some:test", - "detail": "deno bundle mod.ts" - } - ])) - ); -} - -#[test] -fn lsp_import_assertions() { - let mut client = init("initialize_params_import_map.json"); - client - .write_notification( - "textDocument/didOpen", - json!({ - "textDocument": { - "uri": "file:///a/test.json", - "languageId": "json", - "version": 1, - "text": "{\"a\":1}" - } - }), - ) - .unwrap(); - handle_configuration_request( - &mut client, - json!([{ - "enable": true, - "codeLens": { - "test": true - } - }]), - ); - - let diagnostics = CollectedDiagnostics(did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/a.ts", - "languageId": "typescript", - "version": 1, - "text": "import a from \"./test.json\";\n\nconsole.log(a);\n" - } - }), - )); - - assert_eq!( - json!( - diagnostics - .with_file_and_source("file:///a/a.ts", "deno") - .diagnostics - ), - json!([ - { - "range": { - "start": { - "line": 0, - "character": 14 - }, - "end": { - "line": 0, - "character": 27 - } - }, - "severity": 1, - "code": "no-assert-type", - "source": "deno", - "message": "The module is a JSON module and not being imported with an import assertion. Consider adding `assert { type: \"json\" }` to the import statement." - } - ]) - ); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeAction", - load_fixture("code_action_params_import_assertion.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_action_response_import_assertion.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_import_map_import_completions() { - let temp_dir = TempDir::new(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - let import_map = - serde_json::to_vec_pretty(&load_fixture("import-map-completions.json")) - .unwrap(); - fs::write(temp_dir.path().join("import-map.json"), import_map).unwrap(); - fs::create_dir(temp_dir.path().join("lib")).unwrap(); - fs::write( - temp_dir.path().join("lib").join("b.ts"), - r#"export const b = "b";"#, - ) - .unwrap(); - - params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); - if let Some(Value::Object(mut map)) = params.initialization_options { - map.insert("importMap".to_string(), json!("import-map.json")); - params.initialization_options = Some(Value::Object(map)); - } - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - client.write_notification("initialized", json!({})).unwrap(); - let uri = Url::from_file_path(temp_dir.path().join("a.ts")).unwrap(); - - did_open( - &mut client, - json!({ - "textDocument": { - "uri": uri, - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"/~/b.ts\";\nimport * as b from \"\"" - } - }), - ); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/completion", - json!({ - "textDocument": { - "uri": uri - }, - "position": { - "line": 1, - "character": 20 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "\"" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "isIncomplete": false, - "items": [ - { - "label": ".", - "kind": 19, - "detail": "(local)", - "sortText": "1", - "insertText": ".", - "commitCharacters": ["\"", "'"], - }, - { - "label": "..", - "kind": 19, - "detail": "(local)", - "sortText": "1", - "insertText": "..", - "commitCharacters": ["\"", "'"], - }, - { - "label": "std", - "kind": 19, - "detail": "(import map)", - "sortText": "std", - "insertText": "std", - "commitCharacters": ["\"", "'"], - }, - { - "label": "fs", - "kind": 17, - "detail": "(import map)", - "sortText": "fs", - "insertText": "fs", - "commitCharacters": ["\"", "'"], - }, - { - "label": "/~", - "kind": 19, - "detail": "(import map)", - "sortText": "/~", - "insertText": "/~", - "commitCharacters": ["\"", "'"], - } - ] - })) - ); - - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": uri, - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 1, - "character": 20 - }, - "end": { - "line": 1, - "character": 20 - } - }, - "text": "/~/" - } - ] - }), - ) - .unwrap(); - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/completion", - json!({ - "textDocument": { - "uri": uri - }, - "position": { - "line": 1, - "character": 23 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "/" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "isIncomplete": false, - "items": [ - { - "label": "b.ts", - "kind": 9, - "detail": "(import map)", - "sortText": "1", - "filterText": "/~/b.ts", - "textEdit": { - "range": { - "start": { - "line": 1, - "character": 20 - }, - "end": { - "line": 1, - "character": 23 - } - }, - "newText": "/~/b.ts" - }, - "commitCharacters": ["\"", "'"], - } - ] - })) - ); - - shutdown(&mut client); -} - -#[test] -fn lsp_hover() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Deno.args);\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 19 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value": "const Deno.args: string[]" - }, - "Returns the script arguments to the program.\n\nGive the following command line invocation of Deno:\n\n```sh\ndeno run --allow-read https://deno.land/std/examples/cat.ts /etc/passwd\n```\n\nThen `Deno.args` will contain:\n\n```\n[ \"/etc/passwd\" ]\n```\n\nIf you are looking for a structured way to parse arguments, there is the\n[`std/flags`](https://deno.land/std/flags) module as part of the Deno\nstandard library.", - "\n\n*@category* - Runtime Environment", - ], - "range": { - "start": { - "line": 0, - "character": 17 - }, - "end": { - "line": 0, - "character": 21 - } - } - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_hover_asset() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Date.now());\n" - } - }), - ); - let (_, maybe_error) = client - .write_request::<_, _, Value>( - "textDocument/definition", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 14 - } - }), - ) - .unwrap(); - assert!(maybe_error.is_none()); - let (_, maybe_error) = client - .write_request::<_, _, Value>( - "deno/virtualTextDocument", - json!({ - "textDocument": { - "uri": "deno:/asset/lib.deno.shared_globals.d.ts" - } - }), - ) - .unwrap(); - assert!(maybe_error.is_none()); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "deno:/asset/lib.es2015.symbol.wellknown.d.ts" - }, - "position": { - "line": 109, - "character": 13 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value": "interface Date", - }, - "Enables basic storage and retrieval of dates and times." - ], - "range": { - "start": { - "line": 109, - "character": 10, - }, - "end": { - "line": 109, - "character": 14, - } - } - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_hover_disabled() { - let mut client = init("initialize_params_disabled.json"); - client - .write_notification( - "textDocument/didOpen", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Date.now());\n" - } - }), - ) - .unwrap(); - - handle_configuration_request(&mut client, json!([{ "enable": false }])); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 19 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!(null))); - shutdown(&mut client); -} - -#[test] -fn lsp_inlay_hints() { - let mut client = init("initialize_params_hints.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": r#"function a(b: string) { - return b; - } - - a("foo"); - - enum C { - A, - } - - parseInt("123", 8); - - const d = Date.now(); - - class E { - f = Date.now(); - } - - ["a"].map((v) => v + v); - "# - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/inlayHint", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 19, - "character": 0, - } - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - json!(maybe_res), - json!([ - { - "position": { - "line": 0, - "character": 21 - }, - "label": ": string", - "kind": 1, - "paddingLeft": true - }, - { - "position": { - "line": 4, - "character": 10 - }, - "label": "b:", - "kind": 2, - "paddingRight": true - }, - { - "position": { - "line": 7, - "character": 11 - }, - "label": "= 0", - "paddingLeft": true - }, - { - "position": { - "line": 10, - "character": 17 - }, - "label": "string:", - "kind": 2, - "paddingRight": true - }, - { - "position": { - "line": 10, - "character": 24 - }, - "label": "radix:", - "kind": 2, - "paddingRight": true - }, - { - "position": { - "line": 12, - "character": 15 - }, - "label": ": number", - "kind": 1, - "paddingLeft": true - }, - { - "position": { - "line": 15, - "character": 11 - }, - "label": ": number", - "kind": 1, - "paddingLeft": true - }, - { - "position": { - "line": 18, - "character": 18 - }, - "label": "callbackfn:", - "kind": 2, - "paddingRight": true - }, - { - "position": { - "line": 18, - "character": 20 - }, - "label": ": string", - "kind": 1, - "paddingLeft": true - }, - { - "position": { - "line": 18, - "character": 21 - }, - "label": ": string", - "kind": 1, - "paddingLeft": true - } - ]) - ); -} - -#[test] -fn lsp_inlay_hints_not_enabled() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": r#"function a(b: string) { - return b; - } - - a("foo"); - - enum C { - A, - } - - parseInt("123", 8); - - const d = Date.now(); - - class E { - f = Date.now(); - } - - ["a"].map((v) => v + v); - "# - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/inlayHint", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 19, - "character": 0, - } - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(json!(maybe_res), json!(null)); -} - -#[test] -fn lsp_workspace_enable_paths() { - let mut params: lsp::InitializeParams = serde_json::from_value(load_fixture( - "initialize_params_workspace_enable_paths.json", - )) - .unwrap(); - // we aren't actually writing anything to the tempdir in this test, but we - // just need a legitimate file path on the host system so that logic that - // tries to convert to and from the fs paths works on all env - let temp_dir = TempDir::new(); - - let root_specifier = - ensure_directory_specifier(Url::from_file_path(temp_dir.path()).unwrap()); - - params.root_uri = Some(root_specifier.clone()); - params.workspace_folders = Some(vec![lsp::WorkspaceFolder { - uri: root_specifier.clone(), - name: "project".to_string(), - }]); - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - client.write_notification("initialized", json!({})).unwrap(); - - handle_configuration_request( - &mut client, - json!([{ - "enable": false, - "enablePaths": [ - "./worker" - ], - }]), - ); - - did_open( - &mut client, - json!({ - "textDocument": { - "uri": root_specifier.join("./file.ts").unwrap(), - "languageId": "typescript", - "version": 1, - "text": "console.log(Date.now());\n" - } - }), - ); - - did_open( - &mut client, - json!({ - "textDocument": { - "uri": root_specifier.join("./other/file.ts").unwrap(), - "languageId": "typescript", - "version": 1, - "text": "console.log(Date.now());\n" - } - }), - ); - - did_open( - &mut client, - json!({ - "textDocument": { - "uri": root_specifier.join("./worker/file.ts").unwrap(), - "languageId": "typescript", - "version": 1, - "text": "console.log(Date.now());\n" - } - }), - ); - - did_open( - &mut client, - json!({ - "textDocument": { - "uri": root_specifier.join("./worker/subdir/file.ts").unwrap(), - "languageId": "typescript", - "version": 1, - "text": "console.log(Date.now());\n" - } - }), - ); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": root_specifier.join("./file.ts").unwrap(), - }, - "position": { - "line": 0, - "character": 19 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!(null))); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": root_specifier.join("./other/file.ts").unwrap(), - }, - "position": { - "line": 0, - "character": 19 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!(null))); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": root_specifier.join("./worker/file.ts").unwrap(), - }, - "position": { - "line": 0, - "character": 19 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value": "(method) DateConstructor.now(): number", - }, - "" - ], - "range": { - "start": { - "line": 0, - "character": 17, - }, - "end": { - "line": 0, - "character": 20, - } - } - })) - ); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": root_specifier.join("./worker/subdir/file.ts").unwrap(), - }, - "position": { - "line": 0, - "character": 19 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value": "(method) DateConstructor.now(): number", - }, - "" - ], - "range": { - "start": { - "line": 0, - "character": 17, - }, - "end": { - "line": 0, - "character": 20, - } - } - })) - ); - - shutdown(&mut client); -} - -#[test] -fn lsp_hover_unstable_disabled() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Deno.dlopen);\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 19 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value": "any" - } - ], - "range": { - "start": { - "line": 0, - "character": 17 - }, - "end": { - "line": 0, - "character": 23 - } - } - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_hover_unstable_enabled() { - let mut client = init("initialize_params_unstable.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Deno.ppid);\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 19 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents":[ - { - "language":"typescript", - "value":"const Deno.ppid: number" - }, - "The process ID of parent process of this instance of the Deno CLI.\n\n```ts\nconsole.log(Deno.ppid);\n```", - "\n\n*@category* - Runtime Environment", - ], - "range":{ - "start":{ - "line":0, - "character":17 - }, - "end":{ - "line":0, - "character":21 - } - } - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_hover_change_mbc() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "const a = `编写软件很难`;\nconst b = `👍🦕😃`;\nconsole.log(a, b);\n" - } - }), - ); - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 1, - "character": 11 - }, - "end": { - "line": 1, - // the LSP uses utf16 encoded characters indexes, so - // after the deno emoiji is character index 15 - "character": 15 - } - }, - "text": "" - } - ] - }), - ) - .unwrap(); - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 2, - "character": 15 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value": "const b: \"😃\"", - }, - "", - ], - "range": { - "start": { - "line": 2, - "character": 15, - }, - "end": { - "line": 2, - "character": 16, - }, - } - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_hover_closed_document() { - let temp_dir_guard = TempDir::new(); - let temp_dir = temp_dir_guard.path(); - let a_path = temp_dir.join("a.ts"); - fs::write(a_path, r#"export const a = "a";"#).unwrap(); - let b_path = temp_dir.join("b.ts"); - fs::write(&b_path, r#"export * from "./a.ts";"#).unwrap(); - let b_specifier = Url::from_file_path(b_path).unwrap(); - let c_path = temp_dir.join("c.ts"); - fs::write(&c_path, "import { a } from \"./b.ts\";\nconsole.log(a);\n") - .unwrap(); - let c_specifier = Url::from_file_path(c_path).unwrap(); - - let mut client = init("initialize_params.json"); - client - .write_notification( - "textDocument/didOpen", - json!({ - "textDocument": { - "uri": b_specifier, - "languageId": "typescript", - "version": 1, - "text": r#"export * from "./a.ts";"# - } - }), - ) - .unwrap(); - let (id, method, _) = client.read_request::().unwrap(); - assert_eq!(method, "workspace/configuration"); - client - .write_response(id, json!([{ "enable": true }])) - .unwrap(); - - client - .write_notification( - "textDocument/didOpen", - json!({ - "textDocument": { - "uri": c_specifier, - "languageId": "typescript", - "version": 1, - "text": "import { a } from \"./b.ts\";\nconsole.log(a);\n", - } - }), - ) - .unwrap(); - let (id, method, _) = client.read_request::().unwrap(); - assert_eq!(method, "workspace/configuration"); - client - .write_response(id, json!([{ "enable": true }])) - .unwrap(); - - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": c_specifier, - }, - "position": { - "line": 0, - "character": 10 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value": "(alias) const a: \"a\"\nimport a" - }, - "" - ], - "range": { - "start": { - "line": 0, - "character": 9 - }, - "end": { - "line": 0, - "character": 10 - } - } - })) - ); - client - .write_notification( - "textDocument/didClose", - json!({ - "textDocument": { - "uri": b_specifier, - } - }), - ) - .unwrap(); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": c_specifier, - }, - "position": { - "line": 0, - "character": 10 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value": "(alias) const a: \"a\"\nimport a" - }, - "" - ], - "range": { - "start": { - "line": 0, - "character": 9 - }, - "end": { - "line": 0, - "character": 10 - } - } - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_hover_dependency() { - let _g = http_server(); - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file_01.ts", - "languageId": "typescript", - "version": 1, - "text": "export const a = \"a\";\n", - } - }), - ); - did_open( - &mut client, - load_fixture("did_open_params_import_hover.json"), - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "deno/cache", - json!({ - "referrer": { - "uri": "file:///a/file.ts", - }, - "uris": [], - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "position": { - "line": 0, - "character": 28 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": { - "kind": "markdown", - "value": "**Resolved Dependency**\n\n**Code**: http​://127.0.0.1:4545/xTypeScriptTypes.js\n\n**Types**: http​://127.0.0.1:4545/xTypeScriptTypes.d.ts\n" - }, - "range": { - "start": { - "line": 0, - "character": 19 - }, - "end":{ - "line": 0, - "character": 62 - } - } - })) - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "position": { - "line": 3, - "character": 28 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": { - "kind": "markdown", - "value": "**Resolved Dependency**\n\n**Code**: http​://127.0.0.1:4545/subdir/type_reference.js\n\n**Types**: http​://127.0.0.1:4545/subdir/type_reference.d.ts\n" - }, - "range": { - "start": { - "line": 3, - "character": 19 - }, - "end":{ - "line": 3, - "character": 67 - } - } - })) - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "position": { - "line": 4, - "character": 28 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": { - "kind": "markdown", - "value": "**Resolved Dependency**\n\n**Code**: http​://127.0.0.1:4545/subdir/mod1.ts\n" - }, - "range": { - "start": { - "line": 4, - "character": 19 - }, - "end":{ - "line": 4, - "character": 57 - } - } - })) - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "position": { - "line": 5, - "character": 28 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": { - "kind": "markdown", - "value": "**Resolved Dependency**\n\n**Code**: _(a data url)_\n" - }, - "range": { - "start": { - "line": 5, - "character": 19 - }, - "end":{ - "line": 5, - "character": 132 - } - } - })) - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "position": { - "line": 6, - "character": 28 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": { - "kind": "markdown", - "value": "**Resolved Dependency**\n\n**Code**: file​:///a/file_01.ts\n" - }, - "range": { - "start": { - "line": 6, - "character": 19 - }, - "end":{ - "line": 6, - "character": 33 - } - } - })) - ); -} - -// This tests for a regression covered by denoland/deno#12753 where the lsp was -// unable to resolve dependencies when there was an invalid syntax in the module -#[test] -fn lsp_hover_deps_preserved_when_invalid_parse() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file1.ts", - "languageId": "typescript", - "version": 1, - "text": "export type Foo = { bar(): string };\n" - } - }), - ); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file2.ts", - "languageId": "typescript", - "version": 1, - "text": "import { Foo } from './file1.ts'; declare const f: Foo; f\n" - } - }), - ); - let (maybe_res, maybe_error) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file2.ts" - }, - "position": { - "line": 0, - "character": 56 - } - }), - ) - .unwrap(); - assert!(maybe_error.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value": "const f: Foo", - }, - "" - ], - "range": { - "start": { - "line": 0, - "character": 56, - }, - "end": { - "line": 0, - "character": 57, - } - } - })) - ); - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file2.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 0, - "character": 57 - }, - "end": { - "line": 0, - "character": 58 - } - }, - "text": "." - } - ] - }), - ) - .unwrap(); - let (maybe_res, maybe_error) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file2.ts" - }, - "position": { - "line": 0, - "character": 56 - } - }), - ) - .unwrap(); - assert!(maybe_error.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value": "const f: Foo", - }, - "" - ], - "range": { - "start": { - "line": 0, - "character": 56, - }, - "end": { - "line": 0, - "character": 57, - } - } - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_hover_typescript_types() { - let _g = http_server(); - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"http://127.0.0.1:4545/xTypeScriptTypes.js\";\n\nconsole.log(a.foo);\n", - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "deno/cache", - json!({ - "referrer": { - "uri": "file:///a/file.ts", - }, - "uris": [ - { - "uri": "http://127.0.0.1:4545/xTypeScriptTypes.js", - } - ], - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 24 - } - }), - ) - .unwrap(); - assert!(maybe_res.is_some()); - assert!(maybe_err.is_none()); - assert_eq!( - json!(maybe_res.unwrap()), - json!({ - "contents": { - "kind": "markdown", - "value": "**Resolved Dependency**\n\n**Code**: http​://127.0.0.1:4545/xTypeScriptTypes.js\n\n**Types**: http​://127.0.0.1:4545/xTypeScriptTypes.d.ts\n" - }, - "range": { - "start": { - "line": 0, - "character": 19 - }, - "end": { - "line": 0, - "character": 62 - } - } - }) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_hover_jsdoc_symbol_link() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/b.ts", - "languageId": "typescript", - "version": 1, - "text": "export function hello() {}\n" - } - }), - ); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import { hello } from \"./b.ts\";\n\nhello();\n\nconst b = \"b\";\n\n/** JSDoc {@link hello} and {@linkcode b} */\nfunction a() {}\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 7, - "character": 10 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": [ - { - "language": "typescript", - "value": "function a(): void" - }, - "JSDoc [hello](file:///a/file.ts#L1,10) and [`b`](file:///a/file.ts#L5,7)" - ], - "range": { - "start": { - "line": 7, - "character": 9 - }, - "end": { - "line": 7, - "character": 10 - } - } - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_goto_type_definition() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "interface A {\n a: string;\n}\n\nexport class B implements A {\n a = \"a\";\n log() {\n console.log(this.a);\n }\n}\n\nconst b = new B();\nb;\n", - } - }), - ); - let (maybe_res, maybe_error) = client - .write_request::<_, _, Value>( - "textDocument/typeDefinition", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 12, - "character": 1 - } - }), - ) - .unwrap(); - assert!(maybe_error.is_none()); - assert_eq!( - maybe_res, - Some(json!([ - { - "targetUri": "file:///a/file.ts", - "targetRange": { - "start": { - "line": 4, - "character": 0 - }, - "end": { - "line": 9, - "character": 1 - } - }, - "targetSelectionRange": { - "start": { - "line": 4, - "character": 13 - }, - "end": { - "line": 4, - "character": 14 - } - } - } - ])) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_call_hierarchy() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "function foo() {\n return false;\n}\n\nclass Bar {\n baz() {\n return foo();\n }\n}\n\nfunction main() {\n const bar = new Bar();\n bar.baz();\n}\n\nmain();" - } - }), - ); - let (maybe_res, maybe_error) = client - .write_request( - "textDocument/prepareCallHierarchy", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 5, - "character": 3 - } - }), - ) - .unwrap(); - assert!(maybe_error.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("prepare_call_hierarchy_response.json")) - ); - let (maybe_res, maybe_error) = client - .write_request( - "callHierarchy/incomingCalls", - load_fixture("incoming_calls_params.json"), - ) - .unwrap(); - assert!(maybe_error.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("incoming_calls_response.json")) - ); - let (maybe_res, maybe_error) = client - .write_request( - "callHierarchy/outgoingCalls", - load_fixture("outgoing_calls_params.json"), - ) - .unwrap(); - assert!(maybe_error.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("outgoing_calls_response.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_large_doc_changes() { - let mut client = init("initialize_params.json"); - did_open(&mut client, load_fixture("did_open_params_large.json")); - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 444, - "character": 11 - }, - "end": { - "line": 444, - "character": 14 - } - }, - "text": "+++" - } - ] - }), - ) - .unwrap(); - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 445, - "character": 4 - }, - "end": { - "line": 445, - "character": 4 - } - }, - "text": "// " - } - ] - }), - ) - .unwrap(); - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 477, - "character": 4 - }, - "end": { - "line": 477, - "character": 9 - } - }, - "text": "error" - } - ] - }), - ) - .unwrap(); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 421, - "character": 30 - } - }), - ) - .unwrap(); - assert!(maybe_res.is_some()); - assert!(maybe_err.is_none()); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 444, - "character": 6 - } - }), - ) - .unwrap(); - assert!(maybe_res.is_some()); - assert!(maybe_err.is_none()); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 461, - "character": 34 - } - }), - ) - .unwrap(); - assert!(maybe_res.is_some()); - assert!(maybe_err.is_none()); - shutdown(&mut client); - - assert!(client.duration().as_millis() <= 15000); -} - -#[test] -fn lsp_document_symbol() { - let mut client = init("initialize_params.json"); - did_open(&mut client, load_fixture("did_open_params_doc_symbol.json")); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/documentSymbol", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("document_symbol_response.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_folding_range() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "// #region 1\n/*\n * Some comment\n */\nclass Foo {\n bar(a, b) {\n if (a === b) {\n return true;\n }\n return false;\n }\n}\n// #endregion" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/foldingRange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!([ - { - "startLine": 0, - "endLine": 12, - "kind": "region" - }, - { - "startLine": 1, - "endLine": 3, - "kind": "comment" - }, - { - "startLine": 4, - "endLine": 10 - }, - { - "startLine": 5, - "endLine": 9 - }, - { - "startLine": 6, - "endLine": 7 - } - ])) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_rename() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - // this should not rename in comments and strings - "text": "let variable = 'a'; // variable\nconsole.log(variable);\n\"variable\";\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/rename", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 4 - }, - "newName": "variable_modified" - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(load_fixture("rename_response.json"))); - shutdown(&mut client); -} - -#[test] -fn lsp_selection_range() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "class Foo {\n bar(a, b) {\n if (a === b) {\n return true;\n }\n return false;\n }\n}" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/selectionRange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "positions": [ - { - "line": 2, - "character": 8 - } - ] - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("selection_range_response.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_semantic_tokens() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - load_fixture("did_open_params_semantic_tokens.json"), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/semanticTokens/full", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "data": [ - 0, 5, 6, 1, 1, 0, 9, 6, 8, 9, 0, 8, 6, 8, 9, 2, 15, 3, 10, 5, 0, 4, 1, - 6, 1, 0, 12, 7, 2, 16, 1, 8, 1, 7, 41, 0, 4, 1, 6, 0, 0, 2, 5, 11, 16, - 1, 9, 1, 7, 40, 3, 10, 4, 2, 1, 1, 11, 1, 9, 9, 1, 2, 3, 11, 1, 3, 6, 3, - 0, 1, 0, 15, 4, 2, 0, 1, 30, 1, 6, 9, 1, 2, 3, 11,1, 1, 9, 9, 9, 3, 0, - 16, 3, 0, 0, 1, 17, 12, 11, 3, 0, 24, 3, 0, 0, 0, 4, 9, 9, 2 - ] - })) - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/semanticTokens/range", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 6, - "character": 0 - } - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "data": [ - 0, 5, 6, 1, 1, 0, 9, 6, 8, 9, 0, 8, 6, 8, 9, 2, 15, 3, 10, 5, 0, 4, 1, - 6, 1, 0, 12, 7, 2, 16, 1, 8, 1, 7, 41, 0, 4, 1, 6, 0, 0, 2, 5, 11, 16, - 1, 9, 1, 7, 40 - ] - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_code_lens() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "class A {\n a = \"a\";\n\n b() {\n console.log(this.a);\n }\n\n c() {\n this.a = \"c\";\n }\n}\n\nconst a = new A();\na.b();\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeLens", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(load_fixture("code_lens_response.json"))); - let (maybe_res, maybe_err) = client - .write_request( - "codeLens/resolve", - json!({ - "range": { - "start": { - "line": 0, - "character": 6 - }, - "end": { - "line": 0, - "character": 7 - } - }, - "data": { - "specifier": "file:///a/file.ts", - "source": "references" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_lens_resolve_response.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_code_lens_impl() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "interface A {\n b(): void;\n}\n\nclass B implements A {\n b() {\n console.log(\"b\");\n }\n}\n\ninterface C {\n c: string;\n}\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeLens", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_lens_response_impl.json")) - ); - let (maybe_res, maybe_err) = client - .write_request( - "codeLens/resolve", - json!({ - "range": { - "start": { - "line": 0, - "character": 10 - }, - "end": { - "line": 0, - "character": 11 - } - }, - "data": { - "specifier": "file:///a/file.ts", - "source": "implementations" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_lens_resolve_response_impl.json")) - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "codeLens/resolve", - json!({ - "range": { - "start": { - "line": 10, - "character": 10 - }, - "end": { - "line": 10, - "character": 11 - } - }, - "data": { - "specifier": "file:///a/file.ts", - "source": "implementations" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "range": { - "start": { - "line": 10, - "character": 10 - }, - "end": { - "line": 10, - "character": 11 - } - }, - "command": { - "title": "0 implementations", - "command": "" - } - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_code_lens_test() { - let mut client = init("initialize_params_code_lens_test.json"); - did_open( - &mut client, - load_fixture("did_open_params_test_code_lens.json"), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeLens", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_lens_response_test.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_code_lens_test_disabled() { - let mut client = init("initialize_params_code_lens_test_disabled.json"); - client - .write_notification( - "textDocument/didOpen", - load_fixture("did_open_params_test_code_lens.json"), - ) - .unwrap(); - - let (id, method, _) = client.read_request::().unwrap(); - assert_eq!(method, "workspace/configuration"); - client - .write_response( - id, - json!([{ - "enable": true, - "codeLens": { - "test": false - } - }]), - ) - .unwrap(); - - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let (method, _) = client.read_notification::().unwrap(); - assert_eq!(method, "textDocument/publishDiagnostics"); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeLens", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!([]))); - shutdown(&mut client); -} - -#[test] -fn lsp_code_lens_non_doc_nav_tree() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Date.now());\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/references", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 3 - }, - "context": { - "includeDeclaration": true - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "deno/virtualTextDocument", - json!({ - "textDocument": { - "uri": "deno:/asset/lib.deno.shared_globals.d.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Vec>( - "textDocument/codeLens", - json!({ - "textDocument": { - "uri": "deno:/asset/lib.deno.shared_globals.d.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let res = maybe_res.unwrap(); - assert!(res.len() > 50); - let (maybe_res, maybe_err) = client - .write_request::<_, _, lsp::CodeLens>( - "codeLens/resolve", - json!({ - "range": { - "start": { - "line": 416, - "character": 12 - }, - "end": { - "line": 416, - "character": 19 - } - }, - "data": { - "specifier": "asset:///lib.deno.shared_globals.d.ts", - "source": "references" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - shutdown(&mut client); -} - -#[test] -fn lsp_nav_tree_updates() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "interface A {\n b(): void;\n}\n\nclass B implements A {\n b() {\n console.log(\"b\");\n }\n}\n\ninterface C {\n c: string;\n}\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeLens", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_lens_response_impl.json")) - ); - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 10, - "character": 0 - }, - "end": { - "line": 13, - "character": 0 - } - }, - "text": "" - } - ] - }), - ) - .unwrap(); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeLens", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_lens_response_changed.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_signature_help() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "/**\n * Adds two numbers.\n * @param a This is a first number.\n * @param b This is a second number.\n */\nfunction add(a: number, b: number) {\n return a + b;\n}\n\nadd(" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/signatureHelp", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "character": 4, - "line": 9 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "(", - "isRetrigger": false - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "signatures": [ - { - "label": "add(a: number, b: number): number", - "documentation": { - "kind": "markdown", - "value": "Adds two numbers." - }, - "parameters": [ - { - "label": "a: number", - "documentation": { - "kind": "markdown", - "value": "This is a first number." - } - }, - { - "label": "b: number", - "documentation": { - "kind": "markdown", - "value": "This is a second number." - } - } - ] - } - ], - "activeSignature": 0, - "activeParameter": 0 - })) - ); - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 9, - "character": 4 - }, - "end": { - "line": 9, - "character": 4 - } - }, - "text": "123, " - } - ] - }), - ) - .unwrap(); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/signatureHelp", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "character": 8, - "line": 9 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "signatures": [ - { - "label": "add(a: number, b: number): number", - "documentation": { - "kind": "markdown", - "value": "Adds two numbers." - }, - "parameters": [ - { - "label": "a: number", - "documentation": { - "kind": "markdown", - "value": "This is a first number." - } - }, - { - "label": "b: number", - "documentation": { - "kind": "markdown", - "value": "This is a second number." - } - } - ] - } - ], - "activeSignature": 0, - "activeParameter": 1 - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_code_actions() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "export function a(): void {\n await Promise.resolve(\"a\");\n}\n\nexport function b(): void {\n await Promise.resolve(\"b\");\n}\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeAction", - load_fixture("code_action_params.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(load_fixture("code_action_response.json"))); - let (maybe_res, maybe_err) = client - .write_request( - "codeAction/resolve", - load_fixture("code_action_resolve_params.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_action_resolve_response.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_code_actions_deno_cache() { - let mut session = TestSession::from_file("initialize_params.json"); - let diagnostics = session.did_open(json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"https://deno.land/x/a/mod.ts\";\n\nconsole.log(a);\n" - } - })); - assert_eq!( - diagnostics.with_source("deno"), - load_fixture_as("diagnostics_deno_deps.json") - ); - - let (maybe_res, maybe_err) = session - .client - .write_request( - "textDocument/codeAction", - load_fixture("code_action_params_cache.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_action_response_cache.json")) - ); - session.shutdown_and_exit(); -} - -#[test] -fn lsp_code_actions_deno_cache_npm() { - let mut session = TestSession::from_file("initialize_params.json"); - let diagnostics = session.did_open(json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import chalk from \"npm:chalk\";\n\nconsole.log(chalk.green);\n" - } - })); - assert_eq!( - diagnostics.with_source("deno"), - load_fixture_as("code_actions/cache_npm/diagnostics.json") - ); - - let (maybe_res, maybe_err) = session - .client - .write_request( - "textDocument/codeAction", - load_fixture("code_actions/cache_npm/cache_action.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_actions/cache_npm/cache_response.json")) - ); - session.shutdown_and_exit(); -} - -#[test] -fn lsp_code_actions_imports() { - let mut session = TestSession::from_file("initialize_params.json"); - session.did_open(json!({ - "textDocument": { - "uri": "file:///a/file00.ts", - "languageId": "typescript", - "version": 1, - "text": "export const abc = \"abc\";\nexport const def = \"def\";\n" - } - })); - session.did_open(json!({ - "textDocument": { - "uri": "file:///a/file01.ts", - "languageId": "typescript", - "version": 1, - "text": "\nconsole.log(abc);\nconsole.log(def)\n" - } - })); - - let (maybe_res, maybe_err) = session - .client - .write_request( - "textDocument/codeAction", - load_fixture("code_action_params_imports.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_action_response_imports.json")) - ); - let (maybe_res, maybe_err) = session - .client - .write_request( - "codeAction/resolve", - load_fixture("code_action_resolve_params_imports.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_action_resolve_response_imports.json")) - ); - - session.shutdown_and_exit(); -} - -#[test] -fn lsp_code_actions_refactor() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "var x: { a?: number; b?: string } = {};\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeAction", - load_fixture("code_action_params_refactor.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_action_response_refactor.json")) - ); - let (maybe_res, maybe_err) = client - .write_request( - "codeAction/resolve", - load_fixture("code_action_resolve_params_refactor.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_action_resolve_response_refactor.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_code_actions_refactor_no_disabled_support() { - let mut client = init("initialize_params_ca_no_disabled.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "interface A {\n a: string;\n}\n\ninterface B {\n b: string;\n}\n\nclass AB implements A, B {\n a = \"a\";\n b = \"b\";\n}\n\nnew AB().a;\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeAction", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 14, - "character": 0 - } - }, - "context": { - "diagnostics": [], - "only": [ - "refactor" - ] - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_action_response_no_disabled.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_code_actions_deadlock() { - let mut client = init("initialize_params.json"); - client - .write_notification( - "textDocument/didOpen", - load_fixture("did_open_params_large.json"), - ) - .unwrap(); - let (id, method, _) = client.read_request::().unwrap(); - assert_eq!(method, "workspace/configuration"); - client - .write_response(id, json!([{ "enable": true }])) - .unwrap(); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/semanticTokens/full", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - read_diagnostics(&mut client); - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 444, - "character": 11 - }, - "end": { - "line": 444, - "character": 14 - } - }, - "text": "+++" - } - ] - }), - ) - .unwrap(); - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 445, - "character": 4 - }, - "end": { - "line": 445, - "character": 4 - } - }, - "text": "// " - } - ] - }), - ) - .unwrap(); - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 477, - "character": 4 - }, - "end": { - "line": 477, - "character": 9 - } - }, - "text": "error" - } - ] - }), - ) - .unwrap(); - // diagnostics only trigger after changes have elapsed in a separate thread, - // so we need to delay the next messages a little bit to attempt to create a - // potential for a deadlock with the codeAction - std::thread::sleep(std::time::Duration::from_millis(50)); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "position": { - "line": 609, - "character": 33, - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/codeAction", - load_fixture("code_action_params_deadlock.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - - read_diagnostics(&mut client); - - shutdown(&mut client); -} - -#[test] -fn lsp_completions() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "Deno." - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/completion", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 5 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "." - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - if let Some(lsp::CompletionResponse::List(list)) = maybe_res { - assert!(!list.is_incomplete); - assert!(list.items.len() > 90); - } else { - panic!("unexpected response"); - } - let (maybe_res, maybe_err) = client - .write_request( - "completionItem/resolve", - load_fixture("completion_resolve_params.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("completion_resolve_response.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_completions_optional() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "interface A {\n b?: string;\n}\n\nconst o: A = {};\n\nfunction c(s: string) {}\n\nc(o.)" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/completion", - load_fixture("completion_request_params_optional.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "isIncomplete": false, - "items": [ - { - "label": "b?", - "kind": 5, - "sortText": "11", - "filterText": "b", - "insertText": "b", - "commitCharacters": [".", ",", ";", "("], - "data": { - "tsc": { - "specifier": "file:///a/file.ts", - "position": 79, - "name": "b", - "useCodeSnippet": false - } - } - } - ] - })) - ); - let (maybe_res, maybe_err) = client - .write_request( - "completionItem/resolve", - load_fixture("completion_resolve_params_optional.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "label": "b?", - "kind": 5, - "detail": "(property) A.b?: string | undefined", - "documentation": { - "kind": "markdown", - "value": "" - }, - "sortText": "1", - "filterText": "b", - "insertText": "b" - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_completions_auto_import() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/b.ts", - "languageId": "typescript", - "version": 1, - "text": "export const foo = \"foo\";\n", - } - }), - ); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "export {};\n\n", - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/completion", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 2, - "character": 0, - }, - "context": { - "triggerKind": 1, - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - if let Some(lsp::CompletionResponse::List(list)) = maybe_res { - assert!(!list.is_incomplete); - if !list.items.iter().any(|item| item.label == "foo") { - panic!("completions items missing 'foo' symbol"); - } - } else { - panic!("unexpected completion response"); - } - let (maybe_res, maybe_err) = client - .write_request( - "completionItem/resolve", - json!({ - "label": "foo", - "kind": 6, - "sortText": "￿16", - "commitCharacters": [ - ".", - ",", - ";", - "(" - ], - "data": { - "tsc": { - "specifier": "file:///a/file.ts", - "position": 12, - "name": "foo", - "source": "./b", - "data": { - "exportName": "foo", - "moduleSpecifier": "./b", - "fileName": "file:///a/b.ts" - }, - "useCodeSnippet": false - } - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "label": "foo", - "kind": 6, - "detail": "const foo: \"foo\"", - "documentation": { - "kind": "markdown", - "value": "" - }, - "sortText": "￿16", - "additionalTextEdits": [ - { - "range": { - "start": { - "line": 0, - "character": 0 - }, - "end": { - "line": 0, - "character": 0 - } - }, - "newText": "import { foo } from \"./b.ts\";\n\n" - } - ], - "commitCharacters": [ - ".", - ",", - ";", - "(" - ] - })) - ); -} - -#[test] -fn lsp_completions_snippet() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/a.tsx", - "languageId": "typescriptreact", - "version": 1, - "text": "function A({ type }: { type: string }) {\n return type;\n}\n\nfunction B() {\n return ( - "deno/cache", - json!({ - "referrer": { - "uri": "file:///a/file.ts", - }, - "uris": [ - { - "uri": "npm:@denotest/cjs-default-export", - }, - { - "uri": "npm:chalk", - } - ] - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - - // check importing a cjs default import - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 2, - "character": 0 - }, - "end": { - "line": 2, - "character": 0 - } - }, - "text": "cjsDefault." - } - ] - }), - ) - .unwrap(); - read_diagnostics(&mut client); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/completion", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 2, - "character": 11 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "." - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - if let Some(lsp::CompletionResponse::List(list)) = maybe_res { - assert!(!list.is_incomplete); - assert_eq!(list.items.len(), 3); - assert!(list.items.iter().any(|i| i.label == "default")); - assert!(list.items.iter().any(|i| i.label == "MyClass")); - } else { - panic!("unexpected response"); - } - let (maybe_res, maybe_err) = client - .write_request( - "completionItem/resolve", - load_fixture("completions/npm/resolve_params.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("completions/npm/resolve_response.json")) - ); - - // now check chalk, which is esm - client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "version": 3 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 2, - "character": 0 - }, - "end": { - "line": 2, - "character": 11 - } - }, - "text": "chalk." - } - ] - }), - ) - .unwrap(); - read_diagnostics(&mut client); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/completion", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 2, - "character": 6 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "." - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - if let Some(lsp::CompletionResponse::List(list)) = maybe_res { - assert!(!list.is_incomplete); - assert!(list.items.iter().any(|i| i.label == "green")); - assert!(list.items.iter().any(|i| i.label == "red")); - } else { - panic!("unexpected response"); - } - - shutdown(&mut client); -} - -#[test] -fn lsp_completions_registry() { - let _g = http_server(); - let mut client = init("initialize_params_registry.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"http://localhost:4545/x/a@\"" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/completion", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 46 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "@" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - if let Some(lsp::CompletionResponse::List(list)) = maybe_res { - assert!(!list.is_incomplete); - assert_eq!(list.items.len(), 3); - } else { - panic!("unexpected response"); - } - let (maybe_res, maybe_err) = client - .write_request( - "completionItem/resolve", - load_fixture("completion_resolve_params_registry.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("completion_resolve_response_registry.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_completions_registry_empty() { - let _g = http_server(); - let mut client = init("initialize_params_registry.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"\"" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/completion", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 20 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "\"" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("completion_request_response_empty.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_auto_discover_registry() { - let _g = http_server(); - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"http://localhost:4545/x/a@\"" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/completion", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 46 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "@" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let (method, maybe_res) = client.read_notification().unwrap(); - assert_eq!(method, "deno/registryState"); - assert_eq!( - maybe_res, - Some(json!({ - "origin": "http://localhost:4545", - "suggestions": true, - })) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_cache_location() { - let _g = http_server(); - let temp_dir = TempDir::new(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params_registry.json")) - .unwrap(); - - params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); - if let Some(Value::Object(mut map)) = params.initialization_options { - map.insert("cache".to_string(), json!(".cache")); - params.initialization_options = Some(Value::Object(map)); - } - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - client.write_notification("initialized", json!({})).unwrap(); - let mut session = TestSession::from_client(client); - - session.did_open(json!({ - "textDocument": { - "uri": "file:///a/file_01.ts", - "languageId": "typescript", - "version": 1, - "text": "export const a = \"a\";\n", - } - })); - let diagnostics = - session.did_open(load_fixture("did_open_params_import_hover.json")); - assert_eq!(diagnostics.viewed().len(), 7); - let (maybe_res, maybe_err) = session - .client - .write_request::<_, _, Value>( - "deno/cache", - json!({ - "referrer": { - "uri": "file:///a/file.ts", - }, - "uris": [], - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let (maybe_res, maybe_err) = session - .client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "position": { - "line": 0, - "character": 28 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": { - "kind": "markdown", - "value": "**Resolved Dependency**\n\n**Code**: http​://127.0.0.1:4545/xTypeScriptTypes.js\n\n**Types**: http​://127.0.0.1:4545/xTypeScriptTypes.d.ts\n" - }, - "range": { - "start": { - "line": 0, - "character": 19 - }, - "end":{ - "line": 0, - "character": 62 - } - } - })) - ); - let (maybe_res, maybe_err) = session - .client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "position": { - "line": 7, - "character": 28 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": { - "kind": "markdown", - "value": "**Resolved Dependency**\n\n**Code**: http​://localhost:4545/x/a/mod.ts\n\n\n---\n\n**a**\n\nmod.ts" - }, - "range": { - "start": { - "line": 7, - "character": 19 - }, - "end": { - "line": 7, - "character": 53 - } - } - })) - ); - let cache_path = temp_dir.path().join(".cache"); - assert!(cache_path.is_dir()); - assert!(cache_path.join("gen").is_dir()); - session.shutdown_and_exit(); -} - -/// Sets the TLS root certificate on startup, which allows the LSP to connect to -/// the custom signed test server and be able to retrieve the registry config -/// and cache files. -#[test] -fn lsp_tls_cert() { - let _g = http_server(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params_tls_cert.json")) - .unwrap(); - - params.root_uri = Some(Url::from_file_path(testdata_path()).unwrap()); - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - client.write_notification("initialized", json!({})).unwrap(); - let mut session = TestSession::from_client(client); - - session.did_open(json!({ - "textDocument": { - "uri": "file:///a/file_01.ts", - "languageId": "typescript", - "version": 1, - "text": "export const a = \"a\";\n", - } - })); - let diagnostics = - session.did_open(load_fixture("did_open_params_tls_cert.json")); - let diagnostics = diagnostics.viewed(); - assert_eq!(diagnostics.len(), 7); - let (maybe_res, maybe_err) = session - .client - .write_request::<_, _, Value>( - "deno/cache", - json!({ - "referrer": { - "uri": "file:///a/file.ts", - }, - "uris": [], - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let (maybe_res, maybe_err) = session - .client - .write_request( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "position": { - "line": 0, - "character": 28 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": { - "kind": "markdown", - "value": "**Resolved Dependency**\n\n**Code**: https​://localhost:5545/xTypeScriptTypes.js\n" - }, - "range": { - "start": { - "line": 0, - "character": 19 - }, - "end":{ - "line": 0, - "character": 63 - } - } - })) - ); - let (maybe_res, maybe_err) = session - .client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - }, - "position": { - "line": 7, - "character": 28 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": { - "kind": "markdown", - "value": "**Resolved Dependency**\n\n**Code**: http​://localhost:4545/x/a/mod.ts\n\n\n---\n\n**a**\n\nmod.ts" - }, - "range": { - "start": { - "line": 7, - "character": 19 - }, - "end": { - "line": 7, - "character": 53 - } - } - })) - ); - session.shutdown_and_exit(); -} - -#[test] -fn lsp_diagnostics_warn_redirect() { - let _g = http_server(); - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"http://127.0.0.1:4545/x_deno_warning.js\";\n\nconsole.log(a)\n", - }, - }), - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "deno/cache", - json!({ - "referrer": { - "uri": "file:///a/file.ts", - }, - "uris": [ - { - "uri": "http://127.0.0.1:4545/x_deno_warning.js", - } - ], - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let diagnostics = read_diagnostics(&mut client); - assert_eq!( - diagnostics.with_source("deno"), - lsp::PublishDiagnosticsParams { - uri: Url::parse("file:///a/file.ts").unwrap(), - diagnostics: vec![ - lsp::Diagnostic { - range: lsp::Range { - start: lsp::Position { - line: 0, - character: 19 - }, - end: lsp::Position { - line: 0, - character: 60 - } - }, - severity: Some(lsp::DiagnosticSeverity::WARNING), - code: Some(lsp::NumberOrString::String("deno-warn".to_string())), - source: Some("deno".to_string()), - message: "foobar".to_string(), - ..Default::default() - }, - lsp::Diagnostic { - range: lsp::Range { - start: lsp::Position { - line: 0, - character: 19 - }, - end: lsp::Position { - line: 0, - character: 60 - } - }, - severity: Some(lsp::DiagnosticSeverity::INFORMATION), - code: Some(lsp::NumberOrString::String("redirect".to_string())), - source: Some("deno".to_string()), - message: "The import of \"http://127.0.0.1:4545/x_deno_warning.js\" was redirected to \"http://127.0.0.1:4545/lsp/x_deno_warning_redirect.js\".".to_string(), - data: Some(json!({"specifier": "http://127.0.0.1:4545/x_deno_warning.js", "redirect": "http://127.0.0.1:4545/lsp/x_deno_warning_redirect.js"})), - ..Default::default() - } - ], - version: Some(1), - } - ); - shutdown(&mut client); -} - -#[test] -fn lsp_redirect_quick_fix() { - let _g = http_server(); - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"http://127.0.0.1:4545/x_deno_warning.js\";\n\nconsole.log(a)\n", - }, - }), - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "deno/cache", - json!({ - "referrer": { - "uri": "file:///a/file.ts", - }, - "uris": [ - { - "uri": "http://127.0.0.1:4545/x_deno_warning.js", - } - ], - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let diagnostics = read_diagnostics(&mut client) - .with_source("deno") - .diagnostics; - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeAction", - json!(json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "range": { - "start": { - "line": 0, - "character": 19 - }, - "end": { - "line": 0, - "character": 60 - } - }, - "context": { - "diagnostics": diagnostics, - "only": [ - "quickfix" - ] - } - })), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_action_redirect_response.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_diagnostics_deprecated() { - let mut client = init("initialize_params.json"); - let diagnostics = did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "/** @deprecated */\nexport const a = \"a\";\n\na;\n", - }, - }), - ); - assert_eq!( - json!(diagnostics), - json!([ - { - "uri": "file:///a/file.ts", - "diagnostics": [], - "version": 1 - }, - { - "uri": "file:///a/file.ts", - "diagnostics": [], - "version": 1 - }, - { - "uri": "file:///a/file.ts", - "diagnostics": [ - { - "range": { - "start": { - "line": 3, - "character": 0 - }, - "end": { - "line": 3, - "character": 1 - } - }, - "severity": 4, - "code": 6385, - "source": "deno-ts", - "message": "'a' is deprecated.", - "relatedInformation": [], - "tags": [ - 2 - ] - } - ], - "version": 1 - } - ]) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_diagnostics_deno_types() { - let mut client = init("initialize_params.json"); - client - .write_notification( - "textDocument/didOpen", - load_fixture("did_open_params_deno_types.json"), - ) - .unwrap(); - let (id, method, _) = client.read_request::().unwrap(); - assert_eq!(method, "workspace/configuration"); - client - .write_response(id, json!([{ "enable": true }])) - .unwrap(); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/documentSymbol", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - } - }), - ) - .unwrap(); - assert!(maybe_res.is_some()); - assert!(maybe_err.is_none()); - let diagnostics = read_diagnostics(&mut client); - assert_eq!(diagnostics.viewed().len(), 5); - shutdown(&mut client); -} - -#[test] -fn lsp_diagnostics_refresh_dependents() { - let mut session = TestSession::from_file("initialize_params.json"); - session.did_open(json!({ - "textDocument": { - "uri": "file:///a/file_00.ts", - "languageId": "typescript", - "version": 1, - "text": "export const a = \"a\";\n", - }, - })); - session.did_open(json!({ - "textDocument": { - "uri": "file:///a/file_01.ts", - "languageId": "typescript", - "version": 1, - "text": "export * from \"./file_00.ts\";\n", - }, - })); - let diagnostics = session.did_open(json!({ - "textDocument": { - "uri": "file:///a/file_02.ts", - "languageId": "typescript", - "version": 1, - "text": "import { a, b } from \"./file_01.ts\";\n\nconsole.log(a, b);\n" - } - })); - assert_eq!( - json!(diagnostics.with_file_and_source("file:///a/file_02.ts", "deno-ts")), - json!({ - "uri": "file:///a/file_02.ts", - "diagnostics": [ - { - "range": { - "start": { - "line": 0, - "character": 12 - }, - "end": { - "line": 0, - "character": 13 - } - }, - "severity": 1, - "code": 2305, - "source": "deno-ts", - "message": "Module '\"./file_01.ts\"' has no exported member 'b'." - } - ], - "version": 1 - }) - ); - - // fix the code causing the diagnostic - session - .client - .write_notification( - "textDocument/didChange", - json!({ - "textDocument": { - "uri": "file:///a/file_00.ts", - "version": 2 - }, - "contentChanges": [ - { - "range": { - "start": { - "line": 1, - "character": 0 - }, - "end": { - "line": 1, - "character": 0 - } - }, - "text": "export const b = \"b\";\n" - } - ] - }), - ) - .unwrap(); - let diagnostics = session.read_diagnostics(); - assert_eq!(diagnostics.viewed().len(), 0); // no diagnostics now - - session.shutdown_and_exit(); - assert_eq!(session.client.queue_len(), 0); -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct PerformanceAverage { - pub name: String, - pub count: u32, - pub average_duration: u32, -} - -#[derive(Deserialize)] -#[serde(rename_all = "camelCase")] -struct PerformanceAverages { - averages: Vec, -} - -#[test] -fn lsp_performance() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console.log(Deno.args);\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 19 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let (maybe_res, maybe_err) = client - .write_request::<_, _, PerformanceAverages>("deno/performance", json!(null)) - .unwrap(); - assert!(maybe_err.is_none()); - if let Some(res) = maybe_res { - assert_eq!(res.averages.len(), 13); - } else { - panic!("unexpected result"); - } - shutdown(&mut client); -} - -#[test] -fn lsp_format_no_changes() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console;\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/formatting", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "options": { - "tabSize": 2, - "insertSpaces": true - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!(null))); - client.assert_no_notification("window/showMessage"); - shutdown(&mut client); -} - -#[test] -fn lsp_format_error() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "console test test\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/formatting", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "options": { - "tabSize": 2, - "insertSpaces": true - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!(null))); - shutdown(&mut client); -} - -#[test] -fn lsp_format_mbc() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "const bar = '👍🇺🇸😃'\nconsole.log('hello deno')\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/formatting", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "options": { - "tabSize": 2, - "insertSpaces": true - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!(load_fixture("formatting_mbc_response.json"))) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_format_exclude_with_config() { - let temp_dir = TempDir::new(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - let deno_fmt_jsonc = - serde_json::to_vec_pretty(&load_fixture("deno.fmt.exclude.jsonc")).unwrap(); - fs::write(temp_dir.path().join("deno.fmt.jsonc"), deno_fmt_jsonc).unwrap(); - - params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); - if let Some(Value::Object(mut map)) = params.initialization_options { - map.insert("config".to_string(), json!("./deno.fmt.jsonc")); - params.initialization_options = Some(Value::Object(map)); - } - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - let file_uri = - ModuleSpecifier::from_file_path(temp_dir.path().join("ignored.ts")) - .unwrap() - .to_string(); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": file_uri, - "languageId": "typescript", - "version": 1, - "text": "function myFunc(){}" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/formatting", - json!({ - "textDocument": { - "uri": file_uri - }, - "options": { - "tabSize": 2, - "insertSpaces": true - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!(null))); - shutdown(&mut client); -} - -#[test] -fn lsp_format_exclude_default_config() { - let temp_dir = TempDir::new(); - let workspace_root = temp_dir.path().canonicalize().unwrap(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - let deno_jsonc = - serde_json::to_vec_pretty(&load_fixture("deno.fmt.exclude.jsonc")).unwrap(); - fs::write(workspace_root.join("deno.jsonc"), deno_jsonc).unwrap(); - - params.root_uri = Some(Url::from_file_path(workspace_root.clone()).unwrap()); - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - let file_uri = - ModuleSpecifier::from_file_path(workspace_root.join("ignored.ts")) - .unwrap() - .to_string(); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": file_uri, - "languageId": "typescript", - "version": 1, - "text": "function myFunc(){}" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/formatting", - json!({ - "textDocument": { - "uri": file_uri - }, - "options": { - "tabSize": 2, - "insertSpaces": true - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!(null))); - shutdown(&mut client); -} - -#[test] -fn lsp_format_json() { - let mut client = init("initialize_params.json"); - client - .write_notification( - "textDocument/didOpen", - json!({ - "textDocument": { - "uri": "file:///a/file.json", - "languageId": "json", - "version": 1, - "text": "{\"key\":\"value\"}" - } - }), - ) - .unwrap(); - - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/formatting", - json!({ - "textDocument": { - "uri": "file:///a/file.json" - }, - "options": { - "tabSize": 2, - "insertSpaces": true - } - }), - ) - .unwrap(); - - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!([ - { - "range": { - "start": { - "line": 0, - "character": 1 - }, - "end": { - "line": 0, - "character": 1 - } - }, - "newText": " " - }, - { - "range": { - "start": { "line": 0, "character": 7 }, - "end": { "line": 0, "character": 7 } - }, - "newText": " " - }, - { - "range": { - "start": { "line": 0, "character": 14 }, - "end": { "line": 0, "character": 15 } - }, - "newText": " }\n" - } - ])) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_json_no_diagnostics() { - let mut client = init("initialize_params.json"); - client - .write_notification( - "textDocument/didOpen", - json!({ - "textDocument": { - "uri": "file:///a/file.json", - "languageId": "json", - "version": 1, - "text": "{\"key\":\"value\"}" - } - }), - ) - .unwrap(); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/semanticTokens/full", - json!({ - "textDocument": { - "uri": "file:///a/file.json" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!(null))); - - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.json" - }, - "position": { - "line": 0, - "character": 3 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!(null))); - - shutdown(&mut client); -} - -#[test] -fn lsp_format_markdown() { - let mut client = init("initialize_params.json"); - client - .write_notification( - "textDocument/didOpen", - json!({ - "textDocument": { - "uri": "file:///a/file.md", - "languageId": "markdown", - "version": 1, - "text": "# Hello World" - } - }), - ) - .unwrap(); - - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/formatting", - json!({ - "textDocument": { - "uri": "file:///a/file.md" - }, - "options": { - "tabSize": 2, - "insertSpaces": true - } - }), - ) - .unwrap(); - - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!([ - { - "range": { - "start": { "line": 0, "character": 1 }, - "end": { "line": 0, "character": 3 } - }, - "newText": "" - }, - { - "range": { - "start": { "line": 0, "character": 15 }, - "end": { "line": 0, "character": 15 } - }, - "newText": "\n" - } - ])) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_format_with_config() { - let temp_dir = TempDir::new(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - let deno_fmt_jsonc = - serde_json::to_vec_pretty(&load_fixture("deno.fmt.jsonc")).unwrap(); - fs::write(temp_dir.path().join("deno.fmt.jsonc"), deno_fmt_jsonc).unwrap(); - - params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); - if let Some(Value::Object(mut map)) = params.initialization_options { - map.insert("config".to_string(), json!("./deno.fmt.jsonc")); - params.initialization_options = Some(Value::Object(map)); - } - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - client - .write_notification( - "textDocument/didOpen", - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "export async function someVeryLongFunctionName() {\nconst response = fetch(\"http://localhost:4545/some/non/existent/path.json\");\nconsole.log(response.text());\nconsole.log(\"finished!\")\n}" - } - }), - ) - .unwrap(); - - // The options below should be ignored in favor of configuration from config file. - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/formatting", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "options": { - "tabSize": 2, - "insertSpaces": true - } - }), - ) - .unwrap(); - - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!([{ - "range": { - "start": { - "line": 1, - "character": 0 - }, - "end": { - "line": 1, - "character": 0 - } - }, - "newText": "\t" - }, - { - "range": { - "start": { - "line": 1, - "character": 23 - }, - "end": { - "line": 1, - "character": 24 - } - }, - "newText": "\n\t\t'" - }, - { - "range": { - "start": { - "line": 1, - "character": 73 - }, - "end": { - "line": 1, - "character": 74 - } - }, - "newText": "',\n\t" - }, - { - "range": { - "start": { - "line": 2, - "character": 0 - }, - "end": { - "line": 2, - "character": 0 - } - }, - "newText": "\t" - }, - { - "range": { - "start": { - "line": 3, - "character": 0 - }, - "end": { - "line": 3, - "character": 0 - } - }, - "newText": "\t" - }, - { - "range": { - "start": { - "line": 3, - "character": 12 - }, - "end": { - "line": 3, - "character": 13 - } - }, - "newText": "'" - }, - { - "range": { - "start": { - "line": 3, - "character": 22 - }, - "end": { - "line": 3, - "character": 24 - } - }, - "newText": "');" - }, - { - "range": { - "start": { - "line": 4, - "character": 1 - }, - "end": { - "line": 4, - "character": 1 - } - }, - "newText": "\n" - }] - )) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_markdown_no_diagnostics() { - let mut client = init("initialize_params.json"); - client - .write_notification( - "textDocument/didOpen", - json!({ - "textDocument": { - "uri": "file:///a/file.md", - "languageId": "markdown", - "version": 1, - "text": "# Hello World" - } - }), - ) - .unwrap(); - - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/semanticTokens/full", - json!({ - "textDocument": { - "uri": "file:///a/file.md" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!(null))); - - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.md" - }, - "position": { - "line": 0, - "character": 3 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!(maybe_res, Some(json!(null))); - - shutdown(&mut client); -} - -#[test] -fn lsp_configuration_did_change() { - let _g = http_server(); - let mut client = init("initialize_params_did_config_change.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "import * as a from \"http://localhost:4545/x/a@\"" - } - }), - ); - client - .write_notification( - "workspace/didChangeConfiguration", - json!({ - "settings": {} - }), - ) - .unwrap(); - let (id, method, _) = client.read_request::().unwrap(); - assert_eq!(method, "workspace/configuration"); - client - .write_response( - id, - json!([{ - "enable": true, - "codeLens": { - "implementations": true, - "references": true - }, - "importMap": null, - "lint": true, - "suggest": { - "autoImports": true, - "completeFunctionCalls": false, - "names": true, - "paths": true, - "imports": { - "hosts": { - "http://localhost:4545/": true - } - } - }, - "unstable": false - }]), - ) - .unwrap(); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/completion", - json!({ - "textDocument": { - "uri": "file:///a/file.ts" - }, - "position": { - "line": 0, - "character": 46 - }, - "context": { - "triggerKind": 2, - "triggerCharacter": "@" - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - if let Some(lsp::CompletionResponse::List(list)) = maybe_res { - assert!(!list.is_incomplete); - assert_eq!(list.items.len(), 3); - } else { - panic!("unexpected response"); - } - let (maybe_res, maybe_err) = client - .write_request( - "completionItem/resolve", - load_fixture("completion_resolve_params_registry.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("completion_resolve_response_registry.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_workspace_symbol() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "export class A {\n fieldA: string;\n fieldB: string;\n}\n", - } - }), - ); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file_01.ts", - "languageId": "typescript", - "version": 1, - "text": "export class B {\n fieldC: string;\n fieldD: string;\n}\n", - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "workspace/symbol", - json!({ - "query": "field" - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!([ - { - "name": "fieldA", - "kind": 8, - "location": { - "uri": "file:///a/file.ts", - "range": { - "start": { - "line": 1, - "character": 2 - }, - "end": { - "line": 1, - "character": 17 - } - } - }, - "containerName": "A" - }, - { - "name": "fieldB", - "kind": 8, - "location": { - "uri": "file:///a/file.ts", - "range": { - "start": { - "line": 2, - "character": 2 - }, - "end": { - "line": 2, - "character": 17 - } - } - }, - "containerName": "A" - }, - { - "name": "fieldC", - "kind": 8, - "location": { - "uri": "file:///a/file_01.ts", - "range": { - "start": { - "line": 1, - "character": 2 - }, - "end": { - "line": 1, - "character": 17 - } - } - }, - "containerName": "B" - }, - { - "name": "fieldD", - "kind": 8, - "location": { - "uri": "file:///a/file_01.ts", - "range": { - "start": { - "line": 2, - "character": 2 - }, - "end": { - "line": 2, - "character": 17 - } - } - }, - "containerName": "B" - } - ])) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_code_actions_ignore_lint() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": "let message = 'Hello, Deno!';\nconsole.log(message);\n" - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeAction", - load_fixture("code_action_ignore_lint_params.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_action_ignore_lint_response.json")) - ); - shutdown(&mut client); -} - -/// This test exercises updating an existing deno-lint-ignore-file comment. -#[test] -fn lsp_code_actions_update_ignore_lint() { - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.ts", - "languageId": "typescript", - "version": 1, - "text": -"#!/usr/bin/env -S deno run -// deno-lint-ignore-file camelcase -let snake_case = 'Hello, Deno!'; -console.log(snake_case); -", - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request( - "textDocument/codeAction", - load_fixture("code_action_update_ignore_lint_params.json"), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(load_fixture("code_action_update_ignore_lint_response.json")) - ); - shutdown(&mut client); -} - -#[test] -fn lsp_lint_with_config() { - let temp_dir = TempDir::new(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - let deno_lint_jsonc = - serde_json::to_vec_pretty(&load_fixture("deno.lint.jsonc")).unwrap(); - fs::write(temp_dir.path().join("deno.lint.jsonc"), deno_lint_jsonc).unwrap(); - - params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); - if let Some(Value::Object(mut map)) = params.initialization_options { - map.insert("config".to_string(), json!("./deno.lint.jsonc")); - params.initialization_options = Some(Value::Object(map)); - } - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - let mut session = TestSession::from_client(client); - - let diagnostics = session.did_open(load_fixture("did_open_lint.json")); - let diagnostics = diagnostics.viewed(); - assert_eq!(diagnostics.len(), 1); - assert_eq!( - diagnostics[0].code, - Some(lsp::NumberOrString::String("ban-untagged-todo".to_string())) - ); - session.shutdown_and_exit(); -} - -#[test] -fn lsp_lint_exclude_with_config() { - let temp_dir = TempDir::new(); - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - let deno_lint_jsonc = - serde_json::to_vec_pretty(&load_fixture("deno.lint.exclude.jsonc")) - .unwrap(); - fs::write(temp_dir.path().join("deno.lint.jsonc"), deno_lint_jsonc).unwrap(); - - params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); - if let Some(Value::Object(mut map)) = params.initialization_options { - map.insert("config".to_string(), json!("./deno.lint.jsonc")); - params.initialization_options = Some(Value::Object(map)); - } - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - let diagnostics = did_open( - &mut client, - json!({ - "textDocument": { - "uri": ModuleSpecifier::from_file_path(temp_dir.path().join("ignored.ts")).unwrap().to_string(), - "languageId": "typescript", - "version": 1, - "text": "// TODO: fixme\nexport async function non_camel_case() {\nconsole.log(\"finished!\")\n}" - } - }), - ); - let diagnostics = diagnostics - .into_iter() - .flat_map(|x| x.diagnostics) - .collect::>(); - assert_eq!(diagnostics, Vec::new()); - shutdown(&mut client); -} - -#[test] -fn lsp_jsx_import_source_pragma() { - let _g = http_server(); - let mut client = init("initialize_params.json"); - did_open( - &mut client, - json!({ - "textDocument": { - "uri": "file:///a/file.tsx", - "languageId": "typescriptreact", - "version": 1, - "text": -"/** @jsxImportSource http://localhost:4545/jsx */ - -function A() { - return \"hello\"; -} - -export function B() { - return ; -} -", - } - }), - ); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "deno/cache", - json!({ - "referrer": { - "uri": "file:///a/file.tsx", - }, - "uris": [ - { - "uri": "http://127.0.0.1:4545/jsx/jsx-runtime", - } - ], - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let (maybe_res, maybe_err) = client - .write_request::<_, _, Value>( - "textDocument/hover", - json!({ - "textDocument": { - "uri": "file:///a/file.tsx" - }, - "position": { - "line": 0, - "character": 25 - } - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert_eq!( - maybe_res, - Some(json!({ - "contents": { - "kind": "markdown", - "value": "**Resolved Dependency**\n\n**Code**: http​://localhost:4545/jsx/jsx-runtime\n", - }, - "range": { - "start": { - "line": 0, - "character": 21 - }, - "end": { - "line": 0, - "character": 46 - } - } - })) - ); - shutdown(&mut client); -} - -#[derive(Debug, Clone, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -struct TestData { - id: String, - label: String, - steps: Option>, - range: Option, -} - -#[derive(Debug, Deserialize, PartialEq)] -#[serde(rename_all = "camelCase")] -enum TestModuleNotificationKind { - Insert, - Replace, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestModuleNotificationParams { - text_document: lsp::TextDocumentIdentifier, - kind: TestModuleNotificationKind, - label: String, - tests: Vec, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct EnqueuedTestModule { - text_document: lsp::TextDocumentIdentifier, - ids: Vec, -} - -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -struct TestRunResponseParams { - enqueued: Vec, -} - -#[test] -fn lsp_testing_api() { - let mut params: lsp::InitializeParams = - serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); - let temp_dir = TempDir::new(); - - let root_specifier = - ensure_directory_specifier(Url::from_file_path(temp_dir.path()).unwrap()); - - let module_path = temp_dir.path().join("./test.ts"); - let specifier = ModuleSpecifier::from_file_path(&module_path).unwrap(); - let contents = r#" -Deno.test({ - name: "test a", - fn() { - console.log("test a"); - } -}); -"#; - fs::write(&module_path, contents).unwrap(); - fs::write(temp_dir.path().join("./deno.jsonc"), r#"{}"#).unwrap(); - - params.root_uri = Some(root_specifier); - - let deno_exe = deno_exe_path(); - let mut client = LspClient::new(&deno_exe, false).unwrap(); - client - .write_request::<_, _, Value>("initialize", params) - .unwrap(); - - client.write_notification("initialized", json!({})).unwrap(); - - client - .write_notification( - "textDocument/didOpen", - json!({ - "textDocument": { - "uri": specifier, - "languageId": "typescript", - "version": 1, - "text": contents, - } - }), - ) - .unwrap(); - - handle_configuration_request( - &mut client, - json!([{ - "enable": true, - "codeLens": { - "test": true - } - }]), - ); - - for _ in 0..4 { - let result = client.read_notification::(); - assert!(result.is_ok()); - let (method, notification) = result.unwrap(); - if method.as_str() == "deno/testModule" { - let params: TestModuleNotificationParams = - serde_json::from_value(notification.unwrap()).unwrap(); - assert_eq!(params.text_document.uri, specifier); - assert_eq!(params.kind, TestModuleNotificationKind::Replace); - assert_eq!(params.label, "test.ts"); - assert_eq!(params.tests.len(), 1); - let test = ¶ms.tests[0]; - assert_eq!(test.label, "test a"); - assert!(test.steps.is_none()); - assert_eq!( - test.range, - Some(lsp::Range { - start: lsp::Position { - line: 1, - character: 5, - }, - end: lsp::Position { - line: 1, - character: 9, - } - }) - ); - } - } - - let (maybe_res, maybe_err) = client - .write_request::<_, _, TestRunResponseParams>( - "deno/testRun", - json!({ - "id": 1, - "kind": "run", - }), - ) - .unwrap(); - assert!(maybe_err.is_none()); - assert!(maybe_res.is_some()); - let res = maybe_res.unwrap(); - assert_eq!(res.enqueued.len(), 1); - assert_eq!(res.enqueued[0].text_document.uri, specifier); - assert_eq!(res.enqueued[0].ids.len(), 1); - let id = res.enqueued[0].ids[0].clone(); - - let res = client.read_notification::(); - assert!(res.is_ok()); - let (method, notification) = res.unwrap(); - assert_eq!(method, "deno/testRunProgress"); - assert_eq!( - notification, - Some(json!({ - "id": 1, - "message": { - "type": "started", - "test": { - "textDocument": { - "uri": specifier, - }, - "id": id, - }, - } - })) - ); - - let res = client.read_notification::(); - assert!(res.is_ok()); - let (method, notification) = res.unwrap(); - assert_eq!(method, "deno/testRunProgress"); - let notification_value = notification - .as_ref() - .unwrap() - .as_object() - .unwrap() - .get("message") - .unwrap() - .as_object() - .unwrap() - .get("value") - .unwrap() - .as_str() - .unwrap(); - // deno test's output capturing flushes with a zero-width space in order to - // synchronize the output pipes. Occassionally this zero width space - // might end up in the output so strip it from the output comparison here. - assert_eq!(notification_value.replace('\u{200B}', ""), "test a\r\n"); - assert_eq!( - notification, - Some(json!({ - "id": 1, - "message": { - "type": "output", - "value": notification_value, - "test": { - "textDocument": { - "uri": specifier, - }, - "id": id, - }, - } - })) - ); - - let res = client.read_notification::(); - assert!(res.is_ok()); - let (method, notification) = res.unwrap(); - assert_eq!(method, "deno/testRunProgress"); - let notification = notification.unwrap(); - let obj = notification.as_object().unwrap(); - assert_eq!(obj.get("id"), Some(&json!(1))); - let message = obj.get("message").unwrap().as_object().unwrap(); - match message.get("type").and_then(|v| v.as_str()) { - Some("passed") => { - assert_eq!( - message.get("test"), - Some(&json!({ - "textDocument": { - "uri": specifier - }, - "id": id, - })) - ); - assert!(message.contains_key("duration")); - - let res = client.read_notification::(); - assert!(res.is_ok()); - let (method, notification) = res.unwrap(); - assert_eq!(method, "deno/testRunProgress"); - assert_eq!( - notification, - Some(json!({ - "id": 1, - "message": { - "type": "end", - } - })) - ); - } - // sometimes on windows, the messages come out of order, but it actually is - // working, so if we do get the end before the passed, we will simply let - // the test pass - Some("end") => (), - _ => panic!("unexpected message {}", json!(notification)), - } - - shutdown(&mut client); -} diff --git a/cli/tests/integration/mod.rs b/cli/tests/integration/mod.rs index 373d04e706..00031a6363 100644 --- a/cli/tests/integration/mod.rs +++ b/cli/tests/integration/mod.rs @@ -1,7 +1,5 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. -use test_util as util; - #[macro_export] macro_rules! itest( ($name:ident {$( $key:ident: $value:expr,)*}) => { @@ -31,96 +29,3 @@ macro_rules! itest_flaky( } } ); - -// These files have `_tests.rs` suffix to make it easier to tell which file is -// the test (ex. `lint_tests.rs`) and which is the implementation (ex. `lint.rs`) -// when both are open, especially for two tabs in VS Code - -#[path = "bench_tests.rs"] -mod bench; -#[path = "bundle_tests.rs"] -mod bundle; -#[path = "cache_tests.rs"] -mod cache; -#[path = "cert_tests.rs"] -mod cert; -#[path = "check_tests.rs"] -mod check; -#[path = "compile_tests.rs"] -mod compile; -#[path = "coverage_tests.rs"] -mod coverage; -#[path = "doc_tests.rs"] -mod doc; -#[path = "eval_tests.rs"] -mod eval; -#[path = "flags_tests.rs"] -mod flags; -#[path = "fmt_tests.rs"] -mod fmt; -#[path = "info_tests.rs"] -mod info; -#[path = "init_tests.rs"] -mod init; -#[path = "inspector_tests.rs"] -mod inspector; -#[path = "install_tests.rs"] -mod install; -#[path = "lint_tests.rs"] -mod lint; -#[path = "lsp_tests.rs"] -mod lsp; -#[path = "npm_tests.rs"] -mod npm; -#[path = "repl_tests.rs"] -mod repl; -#[path = "run_tests.rs"] -mod run; -#[path = "task_tests.rs"] -mod task; -#[path = "test_tests.rs"] -mod test; -#[path = "upgrade_tests.rs"] -mod upgrade; -#[path = "vendor_tests.rs"] -mod vendor; -#[path = "watcher_tests.rs"] -mod watcher; -#[path = "worker_tests.rs"] -mod worker; - -#[test] -fn js_unit_tests_lint() { - let status = util::deno_cmd() - .arg("lint") - .arg("--unstable") - .arg(util::tests_path().join("unit")) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); -} - -#[test] -fn js_unit_tests() { - let _g = util::http_server(); - - // Note that the unit tests are not safe for concurrency and must be run with a concurrency limit - // of one because there are some chdir tests in there. - // TODO(caspervonb) split these tests into two groups: parallel and serial. - let mut deno = util::deno_cmd() - .current_dir(util::root_path()) - .arg("test") - .arg("--unstable") - .arg("--location=http://js-unit-tests/foo/bar") - .arg("--no-prompt") - .arg("-A") - .arg(util::tests_path().join("unit")) - .spawn() - .expect("failed to spawn script"); - - let status = deno.wait().expect("failed to wait for the child process"); - assert_eq!(Some(0), status.code()); - assert!(status.success()); -} diff --git a/cli/tests/integration/npm_tests.rs b/cli/tests/integration/npm_tests.rs deleted file mode 100644 index 288500ce44..0000000000 --- a/cli/tests/integration/npm_tests.rs +++ /dev/null @@ -1,1521 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use pretty_assertions::assert_eq; -use std::process::Stdio; -use test_util as util; -use util::assert_contains; -use util::http_server; - -// NOTE: See how to make test npm packages at ../testdata/npm/README.md - -itest!(esm_module { - args: "run --allow-read --allow-env npm/esm/main.js", - output: "npm/esm/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(esm_module_eval { - args_vec: vec![ - "eval", - "import chalk from 'npm:chalk@5'; console.log(chalk.green('chalk esm loads'));", - ], - output: "npm/esm/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(esm_module_deno_test { - args: "test --allow-read --allow-env --unstable npm/esm/test.js", - output: "npm/esm/test.out", - envs: env_vars(), - http_server: true, -}); - -itest!(esm_import_cjs_default { - args: "run --allow-read --allow-env --unstable --quiet --check=all npm/esm_import_cjs_default/main.ts", - output: "npm/esm_import_cjs_default/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(cjs_with_deps { - args: "run --allow-read --allow-env npm/cjs_with_deps/main.js", - output: "npm/cjs_with_deps/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(cjs_sub_path { - args: "run --allow-read npm/cjs_sub_path/main.js", - output: "npm/cjs_sub_path/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(cjs_local_global_decls { - args: "run --allow-read npm/cjs_local_global_decls/main.ts", - output: "npm/cjs_local_global_decls/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(cjs_reexport_collision { - args: "run -A --quiet npm/cjs_reexport_collision/main.ts", - output: "npm/cjs_reexport_collision/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(cjs_this_in_exports { - args: "run --allow-read --quiet npm/cjs_this_in_exports/main.js", - output: "npm/cjs_this_in_exports/main.out", - envs: env_vars(), - http_server: true, - exit_code: 1, -}); - -itest!(translate_cjs_to_esm { - args: "run -A --quiet npm/translate_cjs_to_esm/main.js", - output: "npm/translate_cjs_to_esm/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(compare_globals { - args: "run --allow-read --unstable --check=all npm/compare_globals/main.ts", - output: "npm/compare_globals/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(conditional_exports { - args: "run --allow-read npm/conditional_exports/main.js", - output: "npm/conditional_exports/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(dual_cjs_esm { - args: "run -A --quiet npm/dual_cjs_esm/main.ts", - output: "npm/dual_cjs_esm/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(child_process_fork_test { - args: "run -A --quiet npm/child_process_fork_test/main.ts", - output: "npm/child_process_fork_test/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(cjs_module_export_assignment { - args: "run -A --unstable --quiet --check=all npm/cjs_module_export_assignment/main.ts", - output: "npm/cjs_module_export_assignment/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(cjs_module_export_assignment_number { - args: "run -A --unstable --quiet --check=all npm/cjs_module_export_assignment_number/main.ts", - output: "npm/cjs_module_export_assignment_number/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(mixed_case_package_name_global_dir { - args: "run npm/mixed_case_package_name/global.ts", - output: "npm/mixed_case_package_name/global.out", - exit_code: 0, - envs: env_vars(), - http_server: true, -}); - -itest!(mixed_case_package_name_local_dir { - args: - "run --node-modules-dir -A $TESTDATA/npm/mixed_case_package_name/local.ts", - output: "npm/mixed_case_package_name/local.out", - exit_code: 0, - envs: env_vars(), - http_server: true, - temp_cwd: true, -}); - -// FIXME(bartlomieju): npm: specifiers are not handled in dynamic imports -// at the moment -// itest!(dynamic_import { -// args: "run --allow-read --allow-env npm/dynamic_import/main.ts", -// output: "npm/dynamic_import/main.out", -// envs: env_vars(), -// http_server: true, -// }); - -itest!(env_var_re_export_dev { - args: "run --allow-read --allow-env --quiet npm/env_var_re_export/main.js", - output_str: Some("dev\n"), - envs: env_vars(), - http_server: true, -}); - -itest!(env_var_re_export_prod { - args: "run --allow-read --allow-env --quiet npm/env_var_re_export/main.js", - output_str: Some("prod\n"), - envs: { - let mut vars = env_vars(); - vars.push(("NODE_ENV".to_string(), "production".to_string())); - vars - }, - http_server: true, -}); - -itest!(cached_only { - args: "run --cached-only npm/cached_only/main.ts", - output: "npm/cached_only/main.out", - envs: env_vars(), - exit_code: 1, -}); - -itest!(import_map { - args: "run --allow-read --allow-env --import-map npm/import_map/import_map.json npm/import_map/main.js", - output: "npm/import_map/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(lock_file { - args: "run --allow-read --allow-env --lock npm/lock_file/lock.json npm/lock_file/main.js", - output: "npm/lock_file/main.out", - envs: env_vars(), - http_server: true, - exit_code: 10, -}); - -itest!(sub_paths { - args: "run -A --quiet npm/sub_paths/main.jsx", - output: "npm/sub_paths/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(remote_npm_specifier { - args: "run --quiet npm/remote_npm_specifier/main.ts", - output: "npm/remote_npm_specifier/main.out", - envs: env_vars(), - http_server: true, - exit_code: 1, -}); - -itest!(tarball_with_global_header { - args: "run -A --quiet npm/tarball_with_global_header/main.js", - output: "npm/tarball_with_global_header/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(nonexistent_file { - args: "run -A --quiet npm/nonexistent_file/main.js", - output: "npm/nonexistent_file/main.out", - envs: env_vars(), - http_server: true, - exit_code: 1, -}); - -itest!(invalid_package_name { - args: "run -A --quiet npm/invalid_package_name/main.js", - output: "npm/invalid_package_name/main.out", - envs: env_vars(), - exit_code: 1, -}); - -itest!(require_json { - args: "run -A --quiet npm/require_json/main.js", - output: "npm/require_json/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(error_version_after_subpath { - args: "run -A --quiet npm/error_version_after_subpath/main.js", - output: "npm/error_version_after_subpath/main.out", - envs: env_vars(), - http_server: true, - exit_code: 1, -}); - -itest!(deno_cache { - args: "cache --reload npm:chalk npm:mkdirp", - output: "npm/deno_cache.out", - envs: env_vars(), - http_server: true, -}); - -itest!(check_all { - args: "check --remote npm/check_errors/main.ts", - output: "npm/check_errors/main_all.out", - envs: env_vars(), - http_server: true, - exit_code: 1, -}); - -itest!(check_local { - args: "check npm/check_errors/main.ts", - output: "npm/check_errors/main_local.out", - envs: env_vars(), - http_server: true, - exit_code: 1, -}); - -itest!(types { - args: "check --quiet npm/types/main.ts", - output: "npm/types/main.out", - envs: env_vars(), - http_server: true, - exit_code: 1, -}); - -itest!(types_ambient_module { - args: "check --quiet npm/types_ambient_module/main.ts", - output: "npm/types_ambient_module/main.out", - envs: env_vars(), - http_server: true, - exit_code: 1, -}); - -itest!(types_ambient_module_import_map { - args: "check --quiet --import-map=npm/types_ambient_module/import_map.json npm/types_ambient_module/main_import_map.ts", - output: "npm/types_ambient_module/main_import_map.out", - envs: env_vars(), - http_server: true, - exit_code: 1, -}); - -itest!(no_types_in_conditional_exports { - args: "run --check --unstable npm/no_types_in_conditional_exports/main.ts", - output: "npm/no_types_in_conditional_exports/main.out", - exit_code: 0, - envs: env_vars(), - http_server: true, -}); - -itest!(types_entry_value_not_exists { - args: "run --check=all npm/types_entry_value_not_exists/main.ts", - output: "npm/types_entry_value_not_exists/main.out", - envs: env_vars(), - http_server: true, - exit_code: 0, -}); - -itest!(types_no_types_entry { - args: "run --check=all npm/types_no_types_entry/main.ts", - output: "npm/types_no_types_entry/main.out", - envs: env_vars(), - http_server: true, - exit_code: 0, -}); - -itest!(typescript_file_in_package { - args: "run npm/typescript_file_in_package/main.ts", - output: "npm/typescript_file_in_package/main.out", - envs: env_vars(), - http_server: true, - exit_code: 1, -}); - -#[test] -fn parallel_downloading() { - let (out, _err) = util::run_and_collect_output_with_args( - true, - vec![ - "run", - "--allow-read", - "--allow-env", - "npm/cjs_with_deps/main.js", - ], - None, - // don't use the sync env var - Some(env_vars_no_sync_download()), - true, - ); - assert!(out.contains("chalk cjs loads")); -} - -#[test] -fn cached_only_after_first_run() { - let _server = http_server(); - - let deno_dir = util::new_deno_dir(); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("npm/cached_only_after_first_run/main1.ts") - .env("NO_COLOR", "1") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - assert_contains!(stderr, "Download"); - assert_contains!(stdout, "createChalk: chalk"); - assert!(output.status.success()); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("--cached-only") - .arg("npm/cached_only_after_first_run/main2.ts") - .env("NO_COLOR", "1") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - assert_contains!( - stderr, - "An npm specifier not found in cache: \"ansi-styles\", --cached-only is specified." - ); - assert!(stdout.is_empty()); - assert!(!output.status.success()); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("--cached-only") - .arg("npm/cached_only_after_first_run/main1.ts") - .env("NO_COLOR", "1") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - - let output = deno.wait_with_output().unwrap(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(output.status.success()); - assert!(stderr.is_empty()); - assert_contains!(stdout, "createChalk: chalk"); -} - -#[test] -fn reload_flag() { - let _server = http_server(); - - let deno_dir = util::new_deno_dir(); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("npm/reload/main.ts") - .env("NO_COLOR", "1") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - assert_contains!(stderr, "Download"); - assert_contains!(stdout, "createChalk: chalk"); - assert!(output.status.success()); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("--reload") - .arg("npm/reload/main.ts") - .env("NO_COLOR", "1") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - assert_contains!(stderr, "Download"); - assert_contains!(stdout, "createChalk: chalk"); - assert!(output.status.success()); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("--reload=npm:") - .arg("npm/reload/main.ts") - .env("NO_COLOR", "1") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - assert_contains!(stderr, "Download"); - assert_contains!(stdout, "createChalk: chalk"); - assert!(output.status.success()); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("--reload=npm:chalk") - .arg("npm/reload/main.ts") - .env("NO_COLOR", "1") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - assert_contains!(stderr, "Download"); - assert_contains!(stdout, "createChalk: chalk"); - assert!(output.status.success()); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("--reload=npm:foobar") - .arg("npm/reload/main.ts") - .env("NO_COLOR", "1") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - assert!(stderr.is_empty()); - assert_contains!(stdout, "createChalk: chalk"); - assert!(output.status.success()); -} - -#[test] -fn no_npm_after_first_run() { - let _server = http_server(); - - let deno_dir = util::new_deno_dir(); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("--no-npm") - .arg("npm/no_npm_after_first_run/main1.ts") - .env("NO_COLOR", "1") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - assert_contains!( - stderr, - "Following npm specifiers were requested: \"chalk@5\"; but --no-npm is specified." - ); - assert!(stdout.is_empty()); - assert!(!output.status.success()); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("npm/no_npm_after_first_run/main1.ts") - .env("NO_COLOR", "1") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - assert_contains!(stderr, "Download"); - assert_contains!(stdout, "createChalk: chalk"); - assert!(output.status.success()); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("--no-npm") - .arg("npm/no_npm_after_first_run/main1.ts") - .env("NO_COLOR", "1") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - let stderr = String::from_utf8_lossy(&output.stderr); - let stdout = String::from_utf8_lossy(&output.stdout); - assert_contains!( - stderr, - "Following npm specifiers were requested: \"chalk@5\"; but --no-npm is specified." - ); - assert!(stdout.is_empty()); - assert!(!output.status.success()); -} - -#[test] -fn deno_run_cjs_module() { - let _server = http_server(); - - let deno_dir = util::new_deno_dir(); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(deno_dir.path()) - .arg("run") - .arg("--allow-read") - .arg("--allow-env") - .arg("--allow-write") - .arg("npm:mkdirp@1.0.4") - .arg("test_dir") - .env("NO_COLOR", "1") - .envs(env_vars()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert!(output.status.success()); - - assert!(deno_dir.path().join("test_dir").exists()); -} - -itest!(deno_run_cowsay { - args: "run -A --quiet npm:cowsay@1.5.0 Hello", - output: "npm/deno_run_cowsay.out", - envs: env_vars_no_sync_download(), - http_server: true, -}); - -itest!(deno_run_cowsay_explicit { - args: "run -A --quiet npm:cowsay@1.5.0/cowsay Hello", - output: "npm/deno_run_cowsay.out", - envs: env_vars_no_sync_download(), - http_server: true, -}); - -itest!(deno_run_cowthink { - args: "run -A --quiet npm:cowsay@1.5.0/cowthink Hello", - output: "npm/deno_run_cowthink.out", - envs: env_vars_no_sync_download(), - http_server: true, -}); - -itest!(deno_run_bin_esm { - args: "run -A --quiet npm:@denotest/bin/cli-esm this is a test", - output: "npm/deno_run_esm.out", - envs: env_vars(), - http_server: true, -}); - -itest!(deno_run_bin_no_ext { - args: "run -A --quiet npm:@denotest/bin/cli-no-ext this is a test", - output: "npm/deno_run_no_ext.out", - envs: env_vars(), - http_server: true, -}); - -itest!(deno_run_bin_cjs { - args: "run -A --quiet npm:@denotest/bin/cli-cjs this is a test", - output: "npm/deno_run_cjs.out", - envs: env_vars(), - http_server: true, -}); - -itest!(deno_run_non_existent { - args: "run npm:mkdirp@0.5.125", - output: "npm/deno_run_non_existent.out", - envs: env_vars(), - http_server: true, - exit_code: 1, -}); - -itest!(builtin_module_module { - args: "run --allow-read --quiet npm/builtin_module_module/main.js", - output: "npm/builtin_module_module/main.out", - envs: env_vars(), - http_server: true, -}); - -itest!(node_modules_dir_require_added_node_modules_folder { - args: - "run --node-modules-dir -A --quiet $TESTDATA/npm/require_added_nm_folder/main.js", - output: "npm/require_added_nm_folder/main.out", - envs: env_vars(), - http_server: true, - exit_code: 0, - temp_cwd: true, -}); - -itest!(node_modules_dir_with_deps { - args: "run --allow-read --allow-env --node-modules-dir $TESTDATA/npm/cjs_with_deps/main.js", - output: "npm/cjs_with_deps/main.out", - envs: env_vars(), - http_server: true, - temp_cwd: true, -}); - -itest!(node_modules_dir_yargs { - args: "run --allow-read --allow-env --node-modules-dir $TESTDATA/npm/cjs_yargs/main.js", - output: "npm/cjs_yargs/main.out", - envs: env_vars(), - http_server: true, - temp_cwd: true, -}); - -#[test] -fn node_modules_dir_cache() { - let _server = http_server(); - - let deno_dir = util::new_deno_dir(); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(deno_dir.path()) - .arg("cache") - .arg("--node-modules-dir") - .arg("--quiet") - .arg(util::testdata_path().join("npm/dual_cjs_esm/main.ts")) - .envs(env_vars()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert!(output.status.success()); - - let node_modules = deno_dir.path().join("node_modules"); - assert!(node_modules - .join( - ".deno/@denotest+dual-cjs-esm@1.0.0/node_modules/@denotest/dual-cjs-esm" - ) - .exists()); - assert!(node_modules.join("@denotest/dual-cjs-esm").exists()); - - // now try deleting the folder with the package source in the npm cache dir - let package_global_cache_dir = deno_dir - .path() - .join("npm") - .join("localhost_4545") - .join("npm") - .join("registry") - .join("@denotest") - .join("dual-cjs-esm") - .join("1.0.0"); - assert!(package_global_cache_dir.exists()); - std::fs::remove_dir_all(&package_global_cache_dir).unwrap(); - - // run the output, and it shouldn't bother recreating the directory - // because it already has everything cached locally in the node_modules folder - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(deno_dir.path()) - .arg("run") - .arg("--node-modules-dir") - .arg("--quiet") - .arg("-A") - .arg(util::testdata_path().join("npm/dual_cjs_esm/main.ts")) - .envs(env_vars()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert!(output.status.success()); - - // this won't exist, but actually the parent directory - // will because it still re-downloads the registry information - assert!(!package_global_cache_dir.exists()); -} - -#[test] -fn ensure_registry_files_local() { - // ensures the registry files all point at local tarballs - let registry_dir_path = util::testdata_path().join("npm").join("registry"); - for entry in std::fs::read_dir(®istry_dir_path).unwrap() { - let entry = entry.unwrap(); - if entry.metadata().unwrap().is_dir() { - let registry_json_path = registry_dir_path - .join(entry.file_name()) - .join("registry.json"); - if registry_json_path.exists() { - let file_text = std::fs::read_to_string(®istry_json_path).unwrap(); - if file_text.contains("https://registry.npmjs.org/") { - panic!( - "file {} contained a reference to the npm registry", - registry_json_path.display(), - ); - } - } - } - } -} - -itest!(compile_errors { - args: "compile -A --quiet npm/cached_only/main.ts", - output_str: Some("error: npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: npm:chalk@5\n"), - exit_code: 1, - envs: env_vars(), - http_server: true, -}); - -itest!(bundle_errors { - args: "bundle --quiet npm/esm/main.js", - output_str: Some("error: npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: npm:chalk@5\n"), - exit_code: 1, - envs: env_vars(), - http_server: true, -}); - -itest!(info_chalk_display { - args: "info --quiet npm/cjs_with_deps/main.js", - output: "npm/cjs_with_deps/main_info.out", - exit_code: 0, - envs: env_vars(), - http_server: true, -}); - -itest!(info_chalk_display_node_modules_dir { - args: "info --quiet --node-modules-dir $TESTDATA/npm/cjs_with_deps/main.js", - output: "npm/cjs_with_deps/main_info.out", - exit_code: 0, - envs: env_vars(), - http_server: true, - temp_cwd: true, -}); - -itest!(info_chalk_json { - args: "info --quiet --json npm/cjs_with_deps/main.js", - output: "npm/cjs_with_deps/main_info_json.out", - exit_code: 0, - envs: env_vars(), - http_server: true, -}); - -itest!(info_chalk_json_node_modules_dir { - args: - "info --quiet --node-modules-dir --json $TESTDATA/npm/cjs_with_deps/main.js", - output: "npm/cjs_with_deps/main_info_json.out", - exit_code: 0, - envs: env_vars(), - http_server: true, - temp_cwd: true, -}); - -itest!(info_cli_chalk_display { - args: "info --quiet npm:chalk@4", - output: "npm/info/chalk.out", - exit_code: 0, - envs: env_vars(), - http_server: true, -}); - -itest!(info_cli_chalk_json { - args: "info --quiet --json npm:chalk@4", - output: "npm/info/chalk_json.out", - exit_code: 0, - envs: env_vars(), - http_server: true, -}); - -#[test] -fn lock_file_missing_top_level_package() { - let _server = http_server(); - - let deno_dir = util::new_deno_dir(); - let temp_dir = util::TempDir::new(); - - // write empty config file - temp_dir.write("deno.json", "{}"); - - // Lock file that is automatically picked up has been intentionally broken, - // by removing "cowsay" package from it. This test ensures that npm resolver - // snapshot can be successfully hydrated in such situation - let lock_file_content = r#"{ - "version": "2", - "remote": {}, - "npm": { - "specifiers": { "cowsay": "cowsay@1.5.0" }, - "packages": { - "ansi-regex@3.0.1": { - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dependencies": {} - }, - "ansi-regex@5.0.1": { - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dependencies": {} - }, - "ansi-styles@4.3.0": { - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { "color-convert": "color-convert@2.0.1" } - }, - "camelcase@5.3.1": { - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dependencies": {} - }, - "cliui@6.0.0": { - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dependencies": { - "string-width": "string-width@4.2.3", - "strip-ansi": "strip-ansi@6.0.1", - "wrap-ansi": "wrap-ansi@6.2.0" - } - }, - "color-convert@2.0.1": { - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { "color-name": "color-name@1.1.4" } - }, - "color-name@1.1.4": { - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dependencies": {} - }, - "decamelize@1.2.0": { - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dependencies": {} - }, - "emoji-regex@8.0.0": { - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dependencies": {} - }, - "find-up@4.1.0": { - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "locate-path@5.0.0", - "path-exists": "path-exists@4.0.0" - } - }, - "get-caller-file@2.0.5": { - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dependencies": {} - }, - "get-stdin@8.0.0": { - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dependencies": {} - }, - "is-fullwidth-code-point@2.0.0": { - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dependencies": {} - }, - "is-fullwidth-code-point@3.0.0": { - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dependencies": {} - }, - "locate-path@5.0.0": { - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { "p-locate": "p-locate@4.1.0" } - }, - "p-limit@2.3.0": { - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { "p-try": "p-try@2.2.0" } - }, - "p-locate@4.1.0": { - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { "p-limit": "p-limit@2.3.0" } - }, - "p-try@2.2.0": { - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dependencies": {} - }, - "path-exists@4.0.0": { - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dependencies": {} - }, - "require-directory@2.1.1": { - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dependencies": {} - }, - "require-main-filename@2.0.0": { - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dependencies": {} - }, - "set-blocking@2.0.0": { - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dependencies": {} - }, - "string-width@2.1.1": { - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dependencies": { - "is-fullwidth-code-point": "is-fullwidth-code-point@2.0.0", - "strip-ansi": "strip-ansi@4.0.0" - } - }, - "string-width@4.2.3": { - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "emoji-regex@8.0.0", - "is-fullwidth-code-point": "is-fullwidth-code-point@3.0.0", - "strip-ansi": "strip-ansi@6.0.1" - } - }, - "strip-ansi@4.0.0": { - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dependencies": { "ansi-regex": "ansi-regex@3.0.1" } - }, - "strip-ansi@6.0.1": { - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { "ansi-regex": "ansi-regex@5.0.1" } - }, - "strip-final-newline@2.0.0": { - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dependencies": {} - }, - "which-module@2.0.0": { - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dependencies": {} - }, - "wrap-ansi@6.2.0": { - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dependencies": { - "ansi-styles": "ansi-styles@4.3.0", - "string-width": "string-width@4.2.3", - "strip-ansi": "strip-ansi@6.0.1" - } - }, - "y18n@4.0.3": { - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dependencies": {} - }, - "yargs-parser@18.1.3": { - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dependencies": { - "camelcase": "camelcase@5.3.1", - "decamelize": "decamelize@1.2.0" - } - }, - "yargs@15.4.1": { - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dependencies": { - "cliui": "cliui@6.0.0", - "decamelize": "decamelize@1.2.0", - "find-up": "find-up@4.1.0", - "get-caller-file": "get-caller-file@2.0.5", - "require-directory": "require-directory@2.1.1", - "require-main-filename": "require-main-filename@2.0.0", - "set-blocking": "set-blocking@2.0.0", - "string-width": "string-width@4.2.3", - "which-module": "which-module@2.0.0", - "y18n": "y18n@4.0.3", - "yargs-parser": "yargs-parser@18.1.3" - } - } - } - } - } - "#; - temp_dir.write("deno.lock", lock_file_content); - let main_contents = r#" - import cowsay from "npm:cowsay"; - console.log(cowsay); - "#; - temp_dir.write("main.ts", main_contents); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(temp_dir.path()) - .arg("run") - .arg("--quiet") - .arg("--lock") - .arg("deno.lock") - .arg("-A") - .arg("main.ts") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert!(!output.status.success()); - - let stderr = String::from_utf8(output.stderr).unwrap(); - assert_eq!( - stderr, - "error: failed reading lockfile 'deno.lock'\n\nCaused by:\n the lockfile is corrupt. You can recreate it with --lock-write\n" - ); -} - -#[test] -fn lock_file_lock_write() { - // https://github.com/denoland/deno/issues/16666 - // Ensure that --lock-write still adds npm packages to the lockfile - let _server = http_server(); - - let deno_dir = util::new_deno_dir(); - let temp_dir = util::TempDir::new(); - - // write empty config file - temp_dir.write("deno.json", "{}"); - - // write a lock file with borked integrity - let lock_file_content = r#"{ - "version": "2", - "remote": {}, - "npm": { - "specifiers": { "cowsay@1.5.0": "cowsay@1.5.0" }, - "packages": { - "ansi-regex@3.0.1": { - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dependencies": {} - }, - "ansi-regex@5.0.1": { - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dependencies": {} - }, - "ansi-styles@4.3.0": { - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { "color-convert": "color-convert@2.0.1" } - }, - "camelcase@5.3.1": { - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dependencies": {} - }, - "cliui@6.0.0": { - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dependencies": { - "string-width": "string-width@4.2.3", - "strip-ansi": "strip-ansi@6.0.1", - "wrap-ansi": "wrap-ansi@6.2.0" - } - }, - "color-convert@2.0.1": { - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dependencies": { "color-name": "color-name@1.1.4" } - }, - "color-name@1.1.4": { - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dependencies": {} - }, - "cowsay@1.5.0": { - "integrity": "sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==", - "dependencies": { - "get-stdin": "get-stdin@8.0.0", - "string-width": "string-width@2.1.1", - "strip-final-newline": "strip-final-newline@2.0.0", - "yargs": "yargs@15.4.1" - } - }, - "decamelize@1.2.0": { - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "dependencies": {} - }, - "emoji-regex@8.0.0": { - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dependencies": {} - }, - "find-up@4.1.0": { - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "locate-path@5.0.0", - "path-exists": "path-exists@4.0.0" - } - }, - "get-caller-file@2.0.5": { - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dependencies": {} - }, - "get-stdin@8.0.0": { - "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", - "dependencies": {} - }, - "is-fullwidth-code-point@2.0.0": { - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", - "dependencies": {} - }, - "is-fullwidth-code-point@3.0.0": { - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dependencies": {} - }, - "locate-path@5.0.0": { - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { "p-locate": "p-locate@4.1.0" } - }, - "p-limit@2.3.0": { - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dependencies": { "p-try": "p-try@2.2.0" } - }, - "p-locate@4.1.0": { - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { "p-limit": "p-limit@2.3.0" } - }, - "p-try@2.2.0": { - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dependencies": {} - }, - "path-exists@4.0.0": { - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dependencies": {} - }, - "require-directory@2.1.1": { - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "dependencies": {} - }, - "require-main-filename@2.0.0": { - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dependencies": {} - }, - "set-blocking@2.0.0": { - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dependencies": {} - }, - "string-width@2.1.1": { - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dependencies": { - "is-fullwidth-code-point": "is-fullwidth-code-point@2.0.0", - "strip-ansi": "strip-ansi@4.0.0" - } - }, - "string-width@4.2.3": { - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dependencies": { - "emoji-regex": "emoji-regex@8.0.0", - "is-fullwidth-code-point": "is-fullwidth-code-point@3.0.0", - "strip-ansi": "strip-ansi@6.0.1" - } - }, - "strip-ansi@4.0.0": { - "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", - "dependencies": { "ansi-regex": "ansi-regex@3.0.1" } - }, - "strip-ansi@6.0.1": { - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { "ansi-regex": "ansi-regex@5.0.1" } - }, - "strip-final-newline@2.0.0": { - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dependencies": {} - }, - "which-module@2.0.0": { - "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", - "dependencies": {} - }, - "wrap-ansi@6.2.0": { - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dependencies": { - "ansi-styles": "ansi-styles@4.3.0", - "string-width": "string-width@4.2.3", - "strip-ansi": "strip-ansi@6.0.1" - } - }, - "y18n@4.0.3": { - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dependencies": {} - }, - "yargs-parser@18.1.3": { - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dependencies": { - "camelcase": "camelcase@5.3.1", - "decamelize": "decamelize@1.2.0" - } - }, - "yargs@15.4.1": { - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dependencies": { - "cliui": "cliui@6.0.0", - "decamelize": "decamelize@1.2.0", - "find-up": "find-up@4.1.0", - "get-caller-file": "get-caller-file@2.0.5", - "require-directory": "require-directory@2.1.1", - "require-main-filename": "require-main-filename@2.0.0", - "set-blocking": "set-blocking@2.0.0", - "string-width": "string-width@4.2.3", - "which-module": "which-module@2.0.0", - "y18n": "y18n@4.0.3", - "yargs-parser": "yargs-parser@18.1.3" - } - } - } - } -} -"#; - temp_dir.write("deno.lock", lock_file_content); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(temp_dir.path()) - .arg("cache") - .arg("--lock-write") - .arg("--quiet") - .arg("npm:cowsay@1.5.0") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert!(output.status.success()); - assert_eq!(output.status.code(), Some(0)); - - let stdout = String::from_utf8(output.stdout).unwrap(); - assert!(stdout.is_empty()); - let stderr = String::from_utf8(output.stderr).unwrap(); - assert!(stderr.is_empty()); - assert_eq!( - lock_file_content, - std::fs::read_to_string(temp_dir.path().join("deno.lock")).unwrap() - ); -} - -#[test] -fn auto_discover_lock_file() { - let _server = http_server(); - - let deno_dir = util::new_deno_dir(); - let temp_dir = util::TempDir::new(); - - // write empty config file - temp_dir.write("deno.json", "{}"); - - // write a lock file with borked integrity - let lock_file_content = r#"{ - "version": "2", - "remote": {}, - "npm": { - "specifiers": { "@denotest/bin": "@denotest/bin@1.0.0" }, - "packages": { - "@denotest/bin@1.0.0": { - "integrity": "sha512-foobar", - "dependencies": {} - } - } - } - }"#; - temp_dir.write("deno.lock", lock_file_content); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(temp_dir.path()) - .arg("run") - .arg("--unstable") - .arg("-A") - .arg("npm:@denotest/bin/cli-esm") - .arg("test") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert!(!output.status.success()); - assert_eq!(output.status.code(), Some(10)); - - let stderr = String::from_utf8(output.stderr).unwrap(); - assert!(stderr.contains( - "Integrity check failed for npm package: \"@denotest/bin@1.0.0\"" - )); -} - -#[test] -fn peer_deps_with_copied_folders_and_lockfile() { - let _server = http_server(); - - let deno_dir = util::new_deno_dir(); - let temp_dir = util::TempDir::new(); - - // write empty config file - temp_dir.write("deno.json", "{}"); - let test_folder_path = test_util::testdata_path() - .join("npm") - .join("peer_deps_with_copied_folders"); - let main_contents = - std::fs::read_to_string(test_folder_path.join("main.ts")).unwrap(); - temp_dir.write("./main.ts", main_contents); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(temp_dir.path()) - .arg("run") - .arg("-A") - .arg("main.ts") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert!(output.status.success()); - - let expected_output = - std::fs::read_to_string(test_folder_path.join("main.out")).unwrap(); - - assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output); - - assert!(temp_dir.path().join("deno.lock").exists()); - let grandchild_path = deno_dir - .path() - .join("npm") - .join("localhost_4545") - .join("npm") - .join("registry") - .join("@denotest") - .join("peer-dep-test-grandchild"); - assert!(grandchild_path.join("1.0.0").exists()); - assert!(grandchild_path.join("1.0.0_1").exists()); // copy folder, which is hardlinked - - // run again - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(temp_dir.path()) - .arg("run") - .arg("-A") - .arg("main.ts") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!(String::from_utf8(output.stderr).unwrap(), "1\n2\n"); - assert!(output.status.success()); - - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(temp_dir.path()) - .arg("run") - .arg("--reload") - .arg("-A") - .arg("main.ts") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output); - assert!(output.status.success()); - - // now run with local node modules - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(temp_dir.path()) - .arg("run") - .arg("--node-modules-dir") - .arg("-A") - .arg("main.ts") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!(String::from_utf8(output.stderr).unwrap(), "1\n2\n"); - assert!(output.status.success()); - - let deno_folder = temp_dir.path().join("node_modules").join(".deno"); - assert!(deno_folder - .join("@denotest+peer-dep-test-grandchild@1.0.0") - .exists()); - assert!(deno_folder - .join("@denotest+peer-dep-test-grandchild@1.0.0_1") - .exists()); // copy folder - - // now again run with local node modules - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(temp_dir.path()) - .arg("run") - .arg("--node-modules-dir") - .arg("-A") - .arg("main.ts") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert!(output.status.success()); - assert_eq!(String::from_utf8(output.stderr).unwrap(), "1\n2\n"); - - // now ensure it works with reloading - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(temp_dir.path()) - .arg("run") - .arg("--node-modules-dir") - .arg("--reload") - .arg("-A") - .arg("main.ts") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert!(output.status.success()); - assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output); - - // now ensure it works with reloading and no lockfile - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(temp_dir.path()) - .arg("run") - .arg("--node-modules-dir") - .arg("--no-lock") - .arg("--reload") - .arg("-A") - .arg("main.ts") - .envs(env_vars()) - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output,); - assert!(output.status.success()); -} - -itest!(info_peer_deps { - args: "info --quiet npm/peer_deps_with_copied_folders/main.ts", - output: "npm/peer_deps_with_copied_folders/main_info.out", - exit_code: 0, - envs: env_vars(), - http_server: true, -}); - -itest!(info_peer_deps_json { - args: "info --quiet --json npm/peer_deps_with_copied_folders/main.ts", - output: "npm/peer_deps_with_copied_folders/main_info_json.out", - exit_code: 0, - envs: env_vars(), - http_server: true, -}); - -itest!(create_require { - args: "run --reload npm/create_require/main.ts", - output: "npm/create_require/main.out", - exit_code: 0, - envs: env_vars(), - http_server: true, -}); - -fn env_vars_no_sync_download() -> Vec<(String, String)> { - vec![ - ("DENO_NODE_COMPAT_URL".to_string(), util::std_file_url()), - ("DENO_NPM_REGISTRY".to_string(), util::npm_registry_url()), - ("NO_COLOR".to_string(), "1".to_string()), - ] -} - -fn env_vars() -> Vec<(String, String)> { - let mut env_vars = env_vars_no_sync_download(); - env_vars.push(( - // make downloads determinstic - "DENO_UNSTABLE_NPM_SYNC_DOWNLOAD".to_string(), - "1".to_string(), - )); - env_vars -} diff --git a/cli/tests/integration/repl_tests.rs b/cli/tests/integration/repl_tests.rs deleted file mode 100644 index 44c7ec08ff..0000000000 --- a/cli/tests/integration/repl_tests.rs +++ /dev/null @@ -1,877 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use test_util as util; -use test_util::assert_contains; -use test_util::assert_ends_with; -use test_util::assert_not_contains; - -#[test] -fn pty_multiline() { - util::with_pty(&["repl"], |mut console| { - console.write_line("(\n1 + 2\n)"); - console.write_line("{\nfoo: \"foo\"\n}"); - console.write_line("`\nfoo\n`"); - console.write_line("`\n\\`\n`"); - console.write_line("'{'"); - console.write_line("'('"); - console.write_line("'['"); - console.write_line("/{/"); - console.write_line("/\\(/"); - console.write_line("/\\[/"); - console.write_line("console.log(\"{test1} abc {test2} def {{test3}}\".match(/{([^{].+?)}/));"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_contains!(output, '3'); - assert_contains!(output, "{ foo: \"foo\" }"); - assert_contains!(output, "\"\\nfoo\\n\""); - assert_contains!(output, "\"\\n`\\n\""); - assert_contains!(output, "\"{\""); - assert_contains!(output, "\"(\""); - assert_contains!(output, "\"[\""); - assert_contains!(output, "/{/"); - assert_contains!(output, "/\\(/"); - assert_contains!(output, "/\\[/"); - assert_contains!(output, "[ \"{test1}\", \"test1\" ]"); - }); -} - -#[test] -fn pty_null() { - util::with_pty(&["repl"], |mut console| { - console.write_line("null"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_contains!(output, "null"); - }); -} - -#[test] -fn pty_unpaired_braces() { - util::with_pty(&["repl"], |mut console| { - console.write_line(")"); - console.write_line("]"); - console.write_line("}"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_contains!(output, "Unexpected token `)`"); - assert_contains!(output, "Unexpected token `]`"); - assert_contains!(output, "Unexpected token `}`"); - }); -} - -#[test] -fn pty_bad_input() { - util::with_pty(&["repl"], |mut console| { - console.write_line("'\\u{1f3b5}'[0]"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_contains!(output, "Unterminated string literal"); - }); -} - -#[test] -fn pty_syntax_error_input() { - util::with_pty(&["repl"], |mut console| { - console.write_line("('\\u')"); - console.write_line("'"); - console.write_line("[{'a'}];"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_contains!( - output, - "Bad character escape sequence, expected 4 hex characters" - ); - assert_contains!(output, "Unterminated string constant"); - assert_contains!(output, "Expected a semicolon"); - }); -} - -#[test] -fn pty_complete_symbol() { - util::with_pty(&["repl"], |mut console| { - console.write_line("Symbol.it\t"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_contains!(output, "Symbol(Symbol.iterator)"); - }); -} - -#[test] -fn pty_complete_declarations() { - util::with_pty(&["repl"], |mut console| { - console.write_line("class MyClass {}"); - console.write_line("My\t"); - console.write_line("let myVar;"); - console.write_line("myV\t"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_contains!(output, "> MyClass"); - assert_contains!(output, "> myVar"); - }); -} - -#[test] -fn pty_complete_primitives() { - util::with_pty(&["repl"], |mut console| { - console.write_line("let func = function test(){}"); - console.write_line("func.appl\t"); - console.write_line("let str = ''"); - console.write_line("str.leng\t"); - console.write_line("false.valueO\t"); - console.write_line("5n.valueO\t"); - console.write_line("let num = 5"); - console.write_line("num.toStrin\t"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_contains!(output, "> func.apply"); - assert_contains!(output, "> str.length"); - assert_contains!(output, "> 5n.valueOf"); - assert_contains!(output, "> false.valueOf"); - assert_contains!(output, "> num.toString"); - }); -} - -#[test] -fn pty_complete_expression() { - util::with_pty(&["repl"], |mut console| { - console.write_text("Deno.\t\t"); - console.write_text("y"); - console.write_line(""); - console.write_line("close();"); - let output = console.read_all_output(); - assert_contains!(output, "Display all"); - assert_contains!(output, "core"); - assert_contains!(output, "args"); - assert_contains!(output, "exit"); - assert_contains!(output, "symlink"); - assert_contains!(output, "permissions"); - }); -} - -#[test] -fn pty_complete_imports() { - util::with_pty(&["repl"], |mut console| { - // single quotes - console.write_line("import './run/001_hel\t'"); - // double quotes - console.write_line("import { output } from \"./run/045_out\t\""); - console.write_line("output('testing output');"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_contains!(output, "Hello World"); - assert_contains!( - output, - // on windows, could contain either (it's flaky) - "\ntesting output", - "testing output\u{1b}", - ); - }); - - // ensure when the directory changes that the suggestions come from the cwd - util::with_pty(&["repl"], |mut console| { - console.write_line("Deno.chdir('./subdir');"); - console.write_line("import '../run/001_hel\t'"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_contains!(output, "Hello World"); - }); -} - -#[test] -fn pty_complete_imports_no_panic_empty_specifier() { - // does not panic when tabbing when empty - util::with_pty(&["repl"], |mut console| { - console.write_line("import '\t';"); - console.write_line("close();"); - }); -} - -#[test] -fn pty_ignore_symbols() { - util::with_pty(&["repl"], |mut console| { - console.write_line("Array.Symbol\t"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_contains!(output, "undefined"); - assert_not_contains!( - output, - "Uncaught TypeError: Array.Symbol is not a function" - ); - }); -} - -#[test] -fn pty_assign_global_this() { - util::with_pty(&["repl"], |mut console| { - console.write_line("globalThis = 42;"); - console.write_line("close();"); - - let output = console.read_all_output(); - assert_not_contains!(output, "panicked"); - }); -} - -#[test] -fn pty_emoji() { - // windows was having issues displaying this - util::with_pty(&["repl"], |mut console| { - console.write_line(r#"console.log('\u{1F995}');"#); - console.write_line("close();"); - - let output = console.read_all_output(); - // only one for the output (since input is escaped) - let emoji_count = output.chars().filter(|c| *c == '🦕').count(); - assert_eq!(emoji_count, 1); - }); -} - -#[test] -fn console_log() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["console.log('hello')", "'world'"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "hello\nundefined\n\"world\"\n"); - assert!(err.is_empty()); -} - -#[test] -fn object_literal() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["{}", "{ foo: 'bar' }"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "{}\n{ foo: \"bar\" }\n"); - assert!(err.is_empty()); -} - -#[test] -fn block_expression() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["{};", "{\"\"}"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "undefined\n\"\"\n"); - assert!(err.is_empty()); -} - -#[test] -fn await_resolve() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["await Promise.resolve('done')"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "\"done\"\n"); - assert!(err.is_empty()); -} - -#[test] -fn await_timeout() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["await new Promise((r) => setTimeout(r, 0, 'done'))"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "\"done\"\n"); - assert!(err.is_empty()); -} - -#[test] -fn let_redeclaration() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["let foo = 0;", "foo", "let foo = 1;", "foo"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "undefined\n0\nundefined\n1\n"); - assert!(err.is_empty()); -} - -#[test] -fn repl_cwd() { - let (_out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["Deno.cwd()"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert!(err.is_empty()); -} - -#[test] -fn typescript() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec![ - "function add(a: number, b: number) { return a + b }", - "const result: number = add(1, 2) as number;", - "result", - ]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "undefined\nundefined\n3\n"); - assert!(err.is_empty()); -} - -#[test] -fn typescript_declarations() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec![ - "namespace Test { export enum Values { A, B, C } }", - "Test.Values.A", - "Test.Values.C", - "interface MyInterface { prop: string; }", - "type MyTypeAlias = string;", - ]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - let expected_end_text = "undefined\n0\n2\nundefined\nundefined\n"; - assert_ends_with!(out, expected_end_text); - assert!(err.is_empty()); -} - -#[test] -fn typescript_decorators() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec![ - "function dec(target) { target.prototype.test = () => 2; }", - "@dec class Test {}", - "new Test().test()", - ]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "undefined\n[Function: Test]\n2\n"); - assert!(err.is_empty()); -} - -#[test] -fn eof() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["1 + 2"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "3\n"); - assert!(err.is_empty()); -} - -#[test] -fn strict() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec![ - "let a = {};", - "Object.preventExtensions(a);", - "a.c = 1;", - ]), - None, - false, - ); - assert_contains!( - out, - "Uncaught TypeError: Cannot add property c, object is not extensible" - ); - assert!(err.is_empty()); -} - -#[test] -fn close_command() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["close()", "'ignored'"]), - None, - false, - ); - - assert_not_contains!(out, "ignored"); - assert!(err.is_empty()); -} - -#[test] -fn function() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["Deno.writeFileSync"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "[Function: writeFileSync]\n"); - assert!(err.is_empty()); -} - -#[test] -#[ignore] -fn multiline() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["(\n1 + 2\n)"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "3\n"); - assert!(err.is_empty()); -} - -#[test] -fn import() { - let (out, _) = util::run_and_collect_output( - true, - "repl", - Some(vec!["import('./subdir/auto_print_hello.ts')"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_contains!(out, "hello!\n"); -} - -#[test] -fn import_declarations() { - let (out, _) = util::run_and_collect_output( - true, - "repl", - Some(vec!["import './subdir/auto_print_hello.ts';"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_contains!(out, "hello!\n"); -} - -#[test] -fn exports_stripped() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["export default 5;", "export class Test {}"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_contains!(out, "5\n"); - assert!(err.is_empty()); -} - -#[test] -fn call_eval_unterminated() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["eval('{')"]), - None, - false, - ); - assert_contains!(out, "Unexpected end of input"); - assert!(err.is_empty()); -} - -#[test] -fn unpaired_braces() { - for right_brace in &[")", "]", "}"] { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec![right_brace]), - None, - false, - ); - assert_contains!(out, "Unexpected token"); - assert!(err.is_empty()); - } -} - -#[test] -fn reference_error() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["not_a_variable"]), - None, - false, - ); - assert_contains!(out, "not_a_variable is not defined"); - assert!(err.is_empty()); -} - -#[test] -fn syntax_error() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec![ - "syntax error", - "2", // ensure it keeps accepting input after - ]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "parse error: Expected ';', '}' or at 1:8\n2\n"); - assert!(err.is_empty()); -} - -#[test] -fn syntax_error_jsx() { - // JSX is not supported in the REPL - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["const element =
;"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_contains!(out, "Unexpected token `>`"); - assert!(err.is_empty()); -} - -#[test] -fn type_error() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["console()"]), - None, - false, - ); - assert_contains!(out, "console is not a function"); - assert!(err.is_empty()); -} - -#[test] -fn variable() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["var a = 123;", "a"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "undefined\n123\n"); - assert!(err.is_empty()); -} - -#[test] -fn lexical_scoped_variable() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["let a = 123;", "a"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "undefined\n123\n"); - assert!(err.is_empty()); -} - -#[test] -fn missing_deno_dir() { - use std::fs::{read_dir, remove_dir_all}; - const DENO_DIR: &str = "nonexistent"; - let test_deno_dir = test_util::testdata_path().join(DENO_DIR); - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["1"]), - Some(vec![ - ("DENO_DIR".to_owned(), DENO_DIR.to_owned()), - ("NO_COLOR".to_owned(), "1".to_owned()), - ]), - false, - ); - assert!(read_dir(&test_deno_dir).is_ok()); - remove_dir_all(&test_deno_dir).unwrap(); - assert_ends_with!(out, "1\n"); - assert!(err.is_empty()); -} - -#[test] -fn save_last_eval() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["1", "_"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "1\n1\n"); - assert!(err.is_empty()); -} - -#[test] -fn save_last_thrown() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["throw 1", "_error"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!(out, "Uncaught 1\n1\n"); - assert!(err.is_empty()); -} - -#[test] -fn assign_underscore() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["_ = 1", "2", "_"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - assert_ends_with!( - out, - "Last evaluation result is no longer saved to _.\n1\n2\n1\n" - ); - assert!(err.is_empty()); -} - -#[test] -fn assign_underscore_error() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["_error = 1", "throw 2", "_error"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - println!("{}", out); - assert_ends_with!( - out, - "Last thrown error is no longer saved to _error.\n1\nUncaught 2\n1\n" - ); - assert!(err.is_empty()); -} - -#[test] -fn custom_inspect() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec![ - r#"const o = { - [Symbol.for("Deno.customInspect")]() { - throw new Error('Oops custom inspect error'); - }, - };"#, - "o", - ]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - - assert_contains!(out, "Oops custom inspect error"); - assert!(err.is_empty()); -} - -#[test] -fn eval_flag_valid_input() { - let (out, err) = util::run_and_collect_output_with_args( - true, - vec!["repl", "--eval", "const t = 10;"], - Some(vec!["t * 500;"]), - None, - false, - ); - assert_contains!(out, "5000"); - assert!(err.is_empty()); -} - -#[test] -fn eval_flag_parse_error() { - let (out, err) = util::run_and_collect_output_with_args( - true, - vec!["repl", "--eval", "const %"], - Some(vec!["250 * 10"]), - None, - false, - ); - assert_contains!( - test_util::strip_ansi_codes(&out), - "error in --eval flag. parse error: Unexpected token `%`." - ); - assert_contains!(out, "2500"); // should not prevent input - assert!(err.is_empty()); -} - -#[test] -fn eval_flag_runtime_error() { - let (out, err) = util::run_and_collect_output_with_args( - true, - vec!["repl", "--eval", "throw new Error('Testing')"], - Some(vec!["250 * 10"]), - None, - false, - ); - assert_contains!(out, "error in --eval flag. Uncaught Error: Testing"); - assert_contains!(out, "2500"); // should not prevent input - assert!(err.is_empty()); -} - -#[test] -fn eval_file_flag_valid_input() { - let (out, err) = util::run_and_collect_output_with_args( - true, - vec!["repl", "--eval-file=./run/001_hello.js"], - None, - None, - false, - ); - assert_contains!(out, "Hello World"); - assert!(err.is_empty()); -} - -#[test] -fn eval_file_flag_call_defined_function() { - let (out, err) = util::run_and_collect_output_with_args( - true, - vec!["repl", "--eval-file=./tsc/d.ts"], - Some(vec!["v4()"]), - None, - false, - ); - assert_contains!(out, "hello"); - assert!(err.is_empty()); -} - -#[test] -fn eval_file_flag_http_input() { - let (out, err) = util::run_and_collect_output_with_args( - true, - vec!["repl", "--eval-file=http://127.0.0.1:4545/tsc/d.ts"], - Some(vec!["v4()"]), - None, - true, - ); - assert_contains!(out, "hello"); - assert!(err.contains("Download")); -} - -#[test] -fn eval_file_flag_multiple_files() { - let (out, err) = util::run_and_collect_output_with_args( - true, - vec!["repl", "--eval-file=http://127.0.0.1:4545/repl/import_type.ts,./tsc/d.ts,http://127.0.0.1:4545/type_definitions/foo.js"], - Some(vec!["b.method1=v4", "b.method1()+foo.toUpperCase()"]), - None, - true, - ); - assert_contains!(out, "helloFOO"); - assert_contains!(err, "Download"); -} - -#[test] -fn pty_clear_function() { - util::with_pty(&["repl"], |mut console| { - console.write_line("console.log('hello');"); - console.write_line("clear();"); - console.write_line("const clear = 1234 + 2000;"); - console.write_line("clear;"); - console.write_line("close();"); - - let output = console.read_all_output(); - if cfg!(windows) { - // Windows will overwrite what's in the console buffer before - // we read from it. It contains this string repeated many times - // to clear the screen. - assert_contains!(output, "\r\n\u{1b}[K\r\n\u{1b}[K\r\n\u{1b}[K"); - } else { - assert_contains!(output, "hello"); - assert_contains!(output, "[1;1H"); - } - assert_contains!(output, "undefined"); - assert_contains!(output, "const clear = 1234 + 2000;"); - assert_contains!(output, "3234"); - }); -} - -#[test] -fn pty_tab_handler() { - // If the last character is **not** whitespace, we show the completions - util::with_pty(&["repl"], |mut console| { - console.write_line("a\t\t"); - console.write_line("close();"); - let output = console.read_all_output(); - assert_contains!(output, "addEventListener"); - assert_contains!(output, "alert"); - assert_contains!(output, "atob"); - }); - // If the last character is whitespace, we just insert a tab - util::with_pty(&["repl"], |mut console| { - console.write_line("a; \t\t"); // last character is whitespace - console.write_line("close();"); - let output = console.read_all_output(); - assert_not_contains!(output, "addEventListener"); - assert_not_contains!(output, "alert"); - assert_not_contains!(output, "atob"); - }); -} - -#[test] -fn repl_report_error() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec![ - r#"console.log(1); reportError(new Error("foo")); console.log(2);"#, - ]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - - // TODO(nayeemrmn): The REPL should report event errors and rejections. - assert_contains!(out, "1\n2\nundefined\n"); - assert!(err.is_empty()); -} - -#[test] -fn pty_aggregate_error() { - let (out, err) = util::run_and_collect_output( - true, - "repl", - Some(vec!["await Promise.any([])"]), - Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), - false, - ); - - assert_contains!(out, "AggregateError"); - assert!(err.is_empty()); -} diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs deleted file mode 100644 index 866fd2793c..0000000000 --- a/cli/tests/integration/run_tests.rs +++ /dev/null @@ -1,3660 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use deno_core::url; -use deno_runtime::deno_fetch::reqwest; -use std::io::Read; -use std::io::Write; -use std::process::Command; -use std::process::Stdio; -use test_util as util; -use test_util::TempDir; -use tokio::task::LocalSet; -use trust_dns_client::serialize::txt::Lexer; -use trust_dns_client::serialize::txt::Parser; -use util::assert_contains; - -itest!(stdout_write_all { - args: "run --quiet run/stdout_write_all.ts", - output: "run/stdout_write_all.out", -}); - -itest!(stdin_read_all { - args: "run --quiet run/stdin_read_all.ts", - output: "run/stdin_read_all.out", - input: Some("01234567890123456789012345678901234567890123456789"), -}); - -itest!(stdout_write_sync_async { - args: "run --quiet run/stdout_write_sync_async.ts", - output: "run/stdout_write_sync_async.out", -}); - -itest!(_001_hello { - args: "run --reload run/001_hello.js", - output: "run/001_hello.js.out", -}); - -itest!(_002_hello { - args: "run --quiet --reload run/002_hello.ts", - output: "run/002_hello.ts.out", -}); - -itest!(_003_relative_import { - args: "run --quiet --reload run/003_relative_import.ts", - output: "run/003_relative_import.ts.out", -}); - -itest!(_004_set_timeout { - args: "run --quiet --reload run/004_set_timeout.ts", - output: "run/004_set_timeout.ts.out", -}); - -itest!(_005_more_imports { - args: "run --quiet --reload run/005_more_imports.ts", - output: "run/005_more_imports.ts.out", -}); - -itest!(_006_url_imports { - args: "run --quiet --reload run/006_url_imports.ts", - output: "run/006_url_imports.ts.out", - http_server: true, -}); - -itest!(_012_async { - args: "run --quiet --reload run/012_async.ts", - output: "run/012_async.ts.out", -}); - -itest!(_013_dynamic_import { - args: "run --quiet --reload --allow-read run/013_dynamic_import.ts", - output: "run/013_dynamic_import.ts.out", -}); - -itest!(_014_duplicate_import { - args: "run --quiet --reload --allow-read run/014_duplicate_import.ts ", - output: "run/014_duplicate_import.ts.out", -}); - -itest!(_015_duplicate_parallel_import { - args: - "run --quiet --reload --allow-read run/015_duplicate_parallel_import.js", - output: "run/015_duplicate_parallel_import.js.out", -}); - -itest!(_016_double_await { - args: "run --quiet --allow-read --reload run/016_double_await.ts", - output: "run/016_double_await.ts.out", -}); - -itest!(_017_import_redirect { - args: "run --quiet --reload run/017_import_redirect.ts", - output: "run/017_import_redirect.ts.out", -}); - -itest!(_017_import_redirect_nocheck { - args: "run --quiet --reload --no-check run/017_import_redirect.ts", - output: "run/017_import_redirect.ts.out", -}); - -itest!(_017_import_redirect_info { - args: "info --quiet --reload run/017_import_redirect.ts", - output: "run/017_import_redirect_info.out", -}); - -itest!(_018_async_catch { - args: "run --quiet --reload run/018_async_catch.ts", - output: "run/018_async_catch.ts.out", -}); - -itest!(_019_media_types { - args: "run --reload run/019_media_types.ts", - output: "run/019_media_types.ts.out", - http_server: true, -}); - -itest!(_020_json_modules { - args: "run --reload run/020_json_modules.ts", - output: "run/020_json_modules.ts.out", - exit_code: 1, -}); - -itest!(_021_mjs_modules { - args: "run --quiet --reload run/021_mjs_modules.ts", - output: "run/021_mjs_modules.ts.out", -}); - -itest!(_023_no_ext { - args: "run --reload --check run/023_no_ext", - output: "run/023_no_ext.out", -}); - -// TODO(lucacasonato): remove --unstable when permissions goes stable -itest!(_025_hrtime { - args: "run --quiet --allow-hrtime --unstable --reload run/025_hrtime.ts", - output: "run/025_hrtime.ts.out", -}); - -itest!(_025_reload_js_type_error { - args: "run --quiet --reload run/025_reload_js_type_error.js", - output: "run/025_reload_js_type_error.js.out", -}); - -itest!(_026_redirect_javascript { - args: "run --quiet --reload run/026_redirect_javascript.js", - output: "run/026_redirect_javascript.js.out", - http_server: true, -}); - -itest!(_027_redirect_typescript { - args: "run --quiet --reload run/027_redirect_typescript.ts", - output: "run/027_redirect_typescript.ts.out", - http_server: true, -}); - -itest!(_028_args { - args: - "run --quiet --reload run/028_args.ts --arg1 val1 --arg2=val2 -- arg3 arg4", - output: "run/028_args.ts.out", -}); - -itest!(_033_import_map { - args: - "run --quiet --reload --import-map=import_maps/import_map.json import_maps/test.ts", - output: "run/033_import_map.out", -}); - -itest!(_033_import_map_remote { - args: - "run --quiet --reload --import-map=http://127.0.0.1:4545/import_maps/import_map_remote.json --unstable import_maps/test_remote.ts", - output: "run/033_import_map_remote.out", - http_server: true, -}); - -itest!(onload { - args: "run --quiet --reload run/onload/main.ts", - output: "run/onload/main.out", -}); - -itest!(_035_cached_only_flag { - args: "run --reload --check --cached-only http://127.0.0.1:4545/run/019_media_types.ts", - output: "run/035_cached_only_flag.out", - exit_code: 1, - http_server: true, -}); - -itest!(_038_checkjs { - // checking if JS file is run through TS compiler - args: - "run --reload --config run/checkjs.tsconfig.json --check run/038_checkjs.js", - exit_code: 1, - output: "run/038_checkjs.js.out", -}); - -itest!(_042_dyn_import_evalcontext { - args: "run --quiet --allow-read --reload run/042_dyn_import_evalcontext.ts", - output: "run/042_dyn_import_evalcontext.ts.out", -}); - -itest!(_044_bad_resource { - args: "run --quiet --reload --allow-read run/044_bad_resource.ts", - output: "run/044_bad_resource.ts.out", - exit_code: 1, -}); - -// TODO(bartlomieju): remove --unstable once Deno.spawn is stabilized -itest!(_045_proxy { - args: "run -L debug --unstable --allow-net --allow-env --allow-run --allow-read --reload --quiet run/045_proxy_test.ts", - output: "run/045_proxy_test.ts.out", - http_server: true, -}); - -itest!(_046_tsx { - args: "run --quiet --reload run/046_jsx_test.tsx", - output: "run/046_jsx_test.tsx.out", -}); - -itest!(_047_jsx { - args: "run --quiet --reload run/047_jsx_test.jsx", - output: "run/047_jsx_test.jsx.out", -}); - -itest!(_048_media_types_jsx { - args: "run --reload run/048_media_types_jsx.ts", - output: "run/048_media_types_jsx.ts.out", - http_server: true, -}); - -itest!(_052_no_remote_flag { - args: - "run --reload --check --no-remote http://127.0.0.1:4545/run/019_media_types.ts", - output: "run/052_no_remote_flag.out", - exit_code: 1, - http_server: true, -}); - -itest!(_056_make_temp_file_write_perm { - args: - "run --quiet --allow-read --allow-write=./subdir/ run/056_make_temp_file_write_perm.ts", - output: "run/056_make_temp_file_write_perm.out", -}); - -itest!(_058_tasks_microtasks_close { - args: "run --quiet run/058_tasks_microtasks_close.ts", - output: "run/058_tasks_microtasks_close.ts.out", -}); - -itest!(_059_fs_relative_path_perm { - args: "run run/059_fs_relative_path_perm.ts", - output: "run/059_fs_relative_path_perm.ts.out", - exit_code: 1, -}); - -itest!(_070_location { - args: "run --location https://foo/bar?baz#bat run/070_location.ts", - output: "run/070_location.ts.out", -}); - -itest!(_071_location_unset { - args: "run run/071_location_unset.ts", - output: "run/071_location_unset.ts.out", -}); - -itest!(_072_location_relative_fetch { - args: "run --location http://127.0.0.1:4545/ --allow-net run/072_location_relative_fetch.ts", - output: "run/072_location_relative_fetch.ts.out", - http_server: true, -}); - -// tests the beforeunload event -itest!(beforeunload_event { - args: "run run/before_unload.js", - output: "run/before_unload.js.out", -}); - -// tests the serialization of webstorage (both localStorage and sessionStorage) -itest!(webstorage_serialization { - args: "run run/webstorage/serialization.ts", - output: "run/webstorage/serialization.ts.out", -}); - -// tests to ensure that when `--location` is set, all code shares the same -// localStorage cache based on the origin of the location URL. -#[test] -fn webstorage_location_shares_origin() { - let deno_dir = util::new_deno_dir(); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--location") - .arg("https://example.com/a.ts") - .arg("run/webstorage/fixture.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Storage { length: 0 }\n"); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--location") - .arg("https://example.com/b.ts") - .arg("run/webstorage/logger.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Storage { length: 1, hello: \"deno\" }\n"); -} - -// test to ensure that when a --config file is set, but no --location, that -// storage persists against unique configuration files. -#[test] -fn webstorage_config_file() { - let deno_dir = util::new_deno_dir(); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--config") - .arg("run/webstorage/config_a.jsonc") - .arg("run/webstorage/fixture.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Storage { length: 0 }\n"); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--config") - .arg("run/webstorage/config_b.jsonc") - .arg("run/webstorage/logger.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Storage { length: 0 }\n"); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--config") - .arg("run/webstorage/config_a.jsonc") - .arg("run/webstorage/logger.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Storage { length: 1, hello: \"deno\" }\n"); -} - -// tests to ensure `--config` does not effect persisted storage when a -// `--location` is provided. -#[test] -fn webstorage_location_precedes_config() { - let deno_dir = util::new_deno_dir(); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--location") - .arg("https://example.com/a.ts") - .arg("--config") - .arg("run/webstorage/config_a.jsonc") - .arg("run/webstorage/fixture.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Storage { length: 0 }\n"); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--location") - .arg("https://example.com/b.ts") - .arg("--config") - .arg("run/webstorage/config_b.jsonc") - .arg("run/webstorage/logger.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Storage { length: 1, hello: \"deno\" }\n"); -} - -// test to ensure that when there isn't a configuration or location, that the -// main module is used to determine how to persist storage data. -#[test] -fn webstorage_main_module() { - let deno_dir = util::new_deno_dir(); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("run/webstorage/fixture.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Storage { length: 0 }\n"); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("run/webstorage/logger.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Storage { length: 0 }\n"); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("run/webstorage/fixture.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert_eq!(output.stdout, b"Storage { length: 1, hello: \"deno\" }\n"); -} - -itest!(_075_import_local_query_hash { - args: "run run/075_import_local_query_hash.ts", - output: "run/075_import_local_query_hash.ts.out", -}); - -itest!(_077_fetch_empty { - args: "run -A run/077_fetch_empty.ts", - output: "run/077_fetch_empty.ts.out", - exit_code: 1, -}); - -itest!(_078_unload_on_exit { - args: "run run/078_unload_on_exit.ts", - output: "run/078_unload_on_exit.ts.out", - exit_code: 1, -}); - -itest!(_079_location_authentication { - args: - "run --location https://foo:bar@baz/qux run/079_location_authentication.ts", - output: "run/079_location_authentication.ts.out", -}); - -itest!(_081_location_relative_fetch_redirect { - args: "run --location http://127.0.0.1:4546/ --allow-net run/081_location_relative_fetch_redirect.ts", - output: "run/081_location_relative_fetch_redirect.ts.out", - http_server: true, - }); - -itest!(_082_prepare_stack_trace_throw { - args: "run run/082_prepare_stack_trace_throw.js", - output: "run/082_prepare_stack_trace_throw.js.out", - exit_code: 1, -}); - -#[test] -fn _083_legacy_external_source_map() { - let _g = util::http_server(); - let deno_dir = TempDir::new(); - let module_url = url::Url::parse( - "http://localhost:4545/run/083_legacy_external_source_map.ts", - ) - .unwrap(); - // Write a faulty old external source map. - let faulty_map_path = deno_dir.path().join("gen/http/localhost_PORT4545/9576bd5febd0587c5c4d88d57cb3ac8ebf2600c529142abe3baa9a751d20c334.js.map"); - std::fs::create_dir_all(faulty_map_path.parent().unwrap()).unwrap(); - std::fs::write(faulty_map_path, "{\"version\":3,\"file\":\"\",\"sourceRoot\":\"\",\"sources\":[\"http://localhost:4545/083_legacy_external_source_map.ts\"],\"names\":[],\"mappings\":\";AAAA,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC\"}").unwrap(); - let output = Command::new(util::deno_exe_path()) - .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) - .arg("run") - .arg(module_url.to_string()) - .output() - .unwrap(); - // Before https://github.com/denoland/deno/issues/6965 was fixed, the faulty - // old external source map would cause a panic while formatting the error - // and the exit code would be 101. The external source map should be ignored - // in favor of the inline one. - assert_eq!(output.status.code(), Some(1)); - let out = std::str::from_utf8(&output.stdout).unwrap(); - assert_eq!(out, ""); -} - -itest!(dynamic_import_async_error { - args: "run --allow-read run/dynamic_import_async_error/main.ts", - output: "run/dynamic_import_async_error/main.out", -}); - -itest!(dynamic_import_already_rejected { - args: "run --allow-read run/dynamic_import_already_rejected/main.ts", - output: "run/dynamic_import_already_rejected/main.out", -}); - -itest!(no_check_imports_not_used_as_values { - args: "run --config run/no_check_imports_not_used_as_values/preserve_imports.tsconfig.json --no-check run/no_check_imports_not_used_as_values/main.ts", - output: "run/no_check_imports_not_used_as_values/main.out", - }); - -itest!(_088_dynamic_import_already_evaluating { - args: "run --allow-read run/088_dynamic_import_already_evaluating.ts", - output: "run/088_dynamic_import_already_evaluating.ts.out", -}); - -// TODO(bartlomieju): remove --unstable once Deno.spawn is stabilized -itest!(_089_run_allow_list { - args: "run --unstable --allow-run=curl run/089_run_allow_list.ts", - output: "run/089_run_allow_list.ts.out", -}); - -#[test] -fn _090_run_permissions_request() { - let args = "run --quiet run/090_run_permissions_request.ts"; - use util::PtyData::*; - util::test_pty2(args, vec![ - Output("⚠️ ️Deno requests run access to \"ls\". Run again with --allow-run to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), - Input("y\n"), - Output("⚠️ ️Deno requests run access to \"cat\". Run again with --allow-run to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), - Input("n\n"), - Output("granted\r\n"), - Output("prompt\r\n"), - Output("denied\r\n"), - ]); -} - -itest!(_091_use_define_for_class_fields { - args: "run --check run/091_use_define_for_class_fields.ts", - output: "run/091_use_define_for_class_fields.ts.out", - exit_code: 1, -}); - -itest!(_092_import_map_unmapped_bare_specifier { - args: "run --import-map import_maps/import_map.json run/092_import_map_unmapped_bare_specifier.ts", - output: "run/092_import_map_unmapped_bare_specifier.ts.out", - exit_code: 1, -}); - -itest!(js_import_detect { - args: "run --quiet --reload run/js_import_detect.ts", - output: "run/js_import_detect.ts.out", - exit_code: 0, -}); - -itest!(blob_gc_finalization { - args: "run run/blob_gc_finalization.js", - output: "run/blob_gc_finalization.js.out", - exit_code: 0, -}); - -itest!(fetch_response_finalization { - args: - "run --v8-flags=--expose-gc --allow-net run/fetch_response_finalization.js", - output: "run/fetch_response_finalization.js.out", - http_server: true, - exit_code: 0, -}); - -itest!(import_type { - args: "run --reload run/import_type.ts", - output: "run/import_type.ts.out", -}); - -itest!(import_type_no_check { - args: "run --reload --no-check run/import_type.ts", - output: "run/import_type.ts.out", -}); - -itest!(private_field_presence { - args: "run --reload run/private_field_presence.ts", - output: "run/private_field_presence.ts.out", -}); - -itest!(private_field_presence_no_check { - args: "run --reload --no-check run/private_field_presence.ts", - output: "run/private_field_presence.ts.out", -}); - -// TODO(bartlomieju): remove --unstable once Deno.spawn is stabilized -itest!(lock_write_fetch { - args: - "run --quiet --allow-read --allow-write --allow-env --allow-run --unstable run/lock_write_fetch/main.ts", - output: "run/lock_write_fetch/main.out", - http_server: true, - exit_code: 0, -}); - -itest!(lock_check_ok { - args: - "run --lock=run/lock_check_ok.json http://127.0.0.1:4545/run/003_relative_import.ts", - output: "run/003_relative_import.ts.out", - http_server: true, -}); - -itest!(lock_check_ok2 { - args: "run --lock=run/lock_check_ok2.json run/019_media_types.ts", - output: "run/019_media_types.ts.out", - http_server: true, -}); - -itest!(lock_dynamic_imports { - args: "run --lock=run/lock_dynamic_imports.json --allow-read --allow-net http://127.0.0.1:4545/run/013_dynamic_import.ts", - output: "run/lock_dynamic_imports.out", - exit_code: 10, - http_server: true, -}); - -itest!(lock_check_err { - args: "run --lock=run/lock_check_err.json http://127.0.0.1:4545/run/003_relative_import.ts", - output: "run/lock_check_err.out", - exit_code: 10, - http_server: true, -}); - -itest!(lock_check_err2 { - args: "run --lock=run/lock_check_err2.json run/019_media_types.ts", - output: "run/lock_check_err2.out", - exit_code: 10, - http_server: true, -}); - -itest!(lock_v2_check_ok { - args: - "run --lock=run/lock_v2_check_ok.json http://127.0.0.1:4545/run/003_relative_import.ts", - output: "run/003_relative_import.ts.out", - http_server: true, -}); - -itest!(lock_v2_check_ok2 { - args: "run --lock=run/lock_v2_check_ok2.json run/019_media_types.ts", - output: "run/019_media_types.ts.out", - http_server: true, -}); - -itest!(lock_v2_dynamic_imports { - args: "run --lock=run/lock_v2_dynamic_imports.json --allow-read --allow-net http://127.0.0.1:4545/run/013_dynamic_import.ts", - output: "run/lock_v2_dynamic_imports.out", - exit_code: 10, - http_server: true, -}); - -itest!(lock_v2_check_err { - args: "run --lock=run/lock_v2_check_err.json http://127.0.0.1:4545/run/003_relative_import.ts", - output: "run/lock_v2_check_err.out", - exit_code: 10, - http_server: true, -}); - -itest!(lock_v2_check_err2 { - args: "run --lock=run/lock_v2_check_err2.json run/019_media_types.ts", - output: "run/lock_v2_check_err2.out", - exit_code: 10, - http_server: true, -}); - -itest!(lock_only_http_and_https { - args: "run --lock=run/lock_only_http_and_https/deno.lock run/lock_only_http_and_https/main.ts", - output: "run/lock_only_http_and_https/main.out", - http_server: true, -}); - -itest!(mts_dmts_mjs { - args: "run subdir/import.mts", - output: "run/mts_dmts_mjs.out", -}); - -itest!(mts_dmts_mjs_no_check { - args: "run --no-check subdir/import.mts", - output: "run/mts_dmts_mjs.out", -}); - -itest!(async_error { - exit_code: 1, - args: "run --reload run/async_error.ts", - output: "run/async_error.ts.out", -}); - -itest!(config { - args: - "run --reload --config run/config/tsconfig.json --check run/config/main.ts", - output: "run/config/main.out", -}); - -itest!(config_types { - args: - "run --reload --quiet --config run/config_types/tsconfig.json run/config_types/main.ts", - output: "run/config_types/main.out", -}); - -itest!(config_types_remote { - http_server: true, - args: "run --reload --quiet --config run/config_types/remote.tsconfig.json run/config_types/main.ts", - output: "run/config_types/main.out", - }); - -itest!(empty_typescript { - args: "run --reload --check run/empty.ts", - output_str: Some("Check file:[WILDCARD]/run/empty.ts\n"), -}); - -itest!(error_001 { - args: "run --reload run/error_001.ts", - exit_code: 1, - output: "run/error_001.ts.out", -}); - -itest!(error_002 { - args: "run --reload run/error_002.ts", - exit_code: 1, - output: "run/error_002.ts.out", -}); - -itest!(error_003_typescript { - args: "run --reload --check run/error_003_typescript.ts", - exit_code: 1, - output: "run/error_003_typescript.ts.out", -}); - -// Supposing that we've already attempted to run error_003_typescript.ts -// we want to make sure that JS wasn't emitted. Running again without reload flag -// should result in the same output. -// https://github.com/denoland/deno/issues/2436 -itest!(error_003_typescript2 { - args: "run --check run/error_003_typescript.ts", - exit_code: 1, - output: "run/error_003_typescript.ts.out", -}); - -itest!(error_004_missing_module { - args: "run --reload run/error_004_missing_module.ts", - exit_code: 1, - output: "run/error_004_missing_module.ts.out", -}); - -itest!(error_005_missing_dynamic_import { - args: - "run --reload --allow-read --quiet run/error_005_missing_dynamic_import.ts", - exit_code: 1, - output: "run/error_005_missing_dynamic_import.ts.out", -}); - -itest!(error_006_import_ext_failure { - args: "run --reload run/error_006_import_ext_failure.ts", - exit_code: 1, - output: "run/error_006_import_ext_failure.ts.out", -}); - -itest!(error_007_any { - args: "run --reload run/error_007_any.ts", - exit_code: 1, - output: "run/error_007_any.ts.out", -}); - -itest!(error_008_checkjs { - args: "run --reload run/error_008_checkjs.js", - exit_code: 1, - output: "run/error_008_checkjs.js.out", -}); - -itest!(error_009_extensions_error { - args: "run run/error_009_extensions_error.js", - output: "run/error_009_extensions_error.js.out", - exit_code: 1, -}); - -itest!(error_011_bad_module_specifier { - args: "run --reload run/error_011_bad_module_specifier.ts", - exit_code: 1, - output: "run/error_011_bad_module_specifier.ts.out", -}); - -itest!(error_012_bad_dynamic_import_specifier { - args: "run --reload --check run/error_012_bad_dynamic_import_specifier.ts", - exit_code: 1, - output: "run/error_012_bad_dynamic_import_specifier.ts.out", -}); - -itest!(error_013_missing_script { - args: "run --reload missing_file_name", - exit_code: 1, - output: "run/error_013_missing_script.out", -}); - -itest!(error_014_catch_dynamic_import_error { - args: - "run --reload --allow-read run/error_014_catch_dynamic_import_error.js", - output: "run/error_014_catch_dynamic_import_error.js.out", -}); - -itest!(error_015_dynamic_import_permissions { - args: "run --reload --quiet run/error_015_dynamic_import_permissions.js", - output: "run/error_015_dynamic_import_permissions.out", - exit_code: 1, - http_server: true, -}); - -// We have an allow-net flag but not allow-read, it should still result in error. -itest!(error_016_dynamic_import_permissions2 { - args: "run --reload --allow-net run/error_016_dynamic_import_permissions2.js", - output: "run/error_016_dynamic_import_permissions2.out", - exit_code: 1, - http_server: true, -}); - -itest!(error_017_hide_long_source_ts { - args: "run --reload --check run/error_017_hide_long_source_ts.ts", - output: "run/error_017_hide_long_source_ts.ts.out", - exit_code: 1, -}); - -itest!(error_018_hide_long_source_js { - args: "run run/error_018_hide_long_source_js.js", - output: "run/error_018_hide_long_source_js.js.out", - exit_code: 1, -}); - -itest!(error_019_stack_function { - args: "run run/error_019_stack_function.ts", - output: "run/error_019_stack_function.ts.out", - exit_code: 1, -}); - -itest!(error_020_stack_constructor { - args: "run run/error_020_stack_constructor.ts", - output: "run/error_020_stack_constructor.ts.out", - exit_code: 1, -}); - -itest!(error_021_stack_method { - args: "run run/error_021_stack_method.ts", - output: "run/error_021_stack_method.ts.out", - exit_code: 1, -}); - -itest!(error_022_stack_custom_error { - args: "run run/error_022_stack_custom_error.ts", - output: "run/error_022_stack_custom_error.ts.out", - exit_code: 1, -}); - -itest!(error_023_stack_async { - args: "run run/error_023_stack_async.ts", - output: "run/error_023_stack_async.ts.out", - exit_code: 1, -}); - -itest!(error_024_stack_promise_all { - args: "run run/error_024_stack_promise_all.ts", - output: "run/error_024_stack_promise_all.ts.out", - exit_code: 1, -}); - -itest!(error_025_tab_indent { - args: "run run/error_025_tab_indent", - output: "run/error_025_tab_indent.out", - exit_code: 1, -}); - -itest!(error_026_remote_import_error { - args: "run run/error_026_remote_import_error.ts", - output: "run/error_026_remote_import_error.ts.out", - exit_code: 1, - http_server: true, -}); - -itest!(error_for_await { - args: "run --reload --check run/error_for_await.ts", - output: "run/error_for_await.ts.out", - exit_code: 1, -}); - -itest!(error_missing_module_named_import { - args: "run --reload run/error_missing_module_named_import.ts", - output: "run/error_missing_module_named_import.ts.out", - exit_code: 1, -}); - -itest!(error_no_check { - args: "run --reload --no-check run/error_no_check.ts", - output: "run/error_no_check.ts.out", - exit_code: 1, -}); - -itest!(error_syntax { - args: "run --reload run/error_syntax.js", - exit_code: 1, - output: "run/error_syntax.js.out", -}); - -itest!(error_syntax_empty_trailing_line { - args: "run --reload run/error_syntax_empty_trailing_line.mjs", - exit_code: 1, - output: "run/error_syntax_empty_trailing_line.mjs.out", -}); - -itest!(error_type_definitions { - args: "run --reload --check run/error_type_definitions.ts", - exit_code: 1, - output: "run/error_type_definitions.ts.out", -}); - -itest!(error_local_static_import_from_remote_ts { - args: "run --reload http://localhost:4545/run/error_local_static_import_from_remote.ts", - exit_code: 1, - http_server: true, - output: "run/error_local_static_import_from_remote.ts.out", - }); - -itest!(error_local_static_import_from_remote_js { - args: "run --reload http://localhost:4545/run/error_local_static_import_from_remote.js", - exit_code: 1, - http_server: true, - output: "run/error_local_static_import_from_remote.js.out", - }); - -itest!(exit_error42 { - exit_code: 42, - args: "run --quiet --reload run/exit_error42.ts", - output: "run/exit_error42.ts.out", -}); - -itest!(set_exit_code_0 { - args: "run --no-check --unstable run/set_exit_code_0.ts", - output_str: Some(""), - exit_code: 0, -}); - -itest!(set_exit_code_1 { - args: "run --no-check --unstable run/set_exit_code_1.ts", - output_str: Some(""), - exit_code: 42, -}); - -itest!(set_exit_code_2 { - args: "run --no-check --unstable run/set_exit_code_2.ts", - output_str: Some(""), - exit_code: 42, -}); - -itest!(op_exit_op_set_exit_code_in_worker { - args: "run --no-check --unstable --allow-read run/op_exit_op_set_exit_code_in_worker.ts", - exit_code: 21, - output_str: Some(""), -}); - -itest!(deno_exit_tampering { - args: "run --no-check --unstable run/deno_exit_tampering.ts", - output_str: Some(""), - exit_code: 42, -}); - -itest!(heapstats { - args: "run --quiet --unstable --v8-flags=--expose-gc run/heapstats.js", - output: "run/heapstats.js.out", -}); - -itest!(finalization_registry { - args: - "run --quiet --unstable --v8-flags=--expose-gc run/finalization_registry.js", - output: "run/finalization_registry.js.out", -}); - -itest!(https_import { - args: "run --quiet --reload --cert tls/RootCA.pem run/https_import.ts", - output: "run/https_import.ts.out", - http_server: true, -}); - -itest!(if_main { - args: "run --quiet --reload run/if_main.ts", - output: "run/if_main.ts.out", -}); - -itest!(import_meta { - args: "run --quiet --reload --import-map=run/import_meta/importmap.json run/import_meta/main.ts", - output: "run/import_meta/main.out", -}); - -itest!(main_module { - args: "run --quiet --allow-read --reload run/main_module/main.ts", - output: "run/main_module/main.out", -}); - -itest!(no_check { - args: "run --quiet --reload --no-check run/006_url_imports.ts", - output: "run/006_url_imports.ts.out", - http_server: true, -}); - -itest!(no_check_decorators { - args: "run --quiet --reload --no-check run/no_check_decorators.ts", - output: "run/no_check_decorators.ts.out", -}); - -itest!(check_remote { - args: "run --quiet --reload --check=all run/no_check_remote.ts", - output: "run/no_check_remote.ts.disabled.out", - exit_code: 1, - http_server: true, -}); - -itest!(no_check_remote { - args: "run --quiet --reload --no-check=remote run/no_check_remote.ts", - output: "run/no_check_remote.ts.enabled.out", - http_server: true, -}); - -itest!(runtime_decorators { - args: "run --quiet --reload --no-check run/runtime_decorators.ts", - output: "run/runtime_decorators.ts.out", -}); - -itest!(seed_random { - args: "run --seed=100 run/seed_random.js", - output: "run/seed_random.js.out", -}); - -itest!(type_definitions { - args: "run --reload run/type_definitions.ts", - output: "run/type_definitions.ts.out", -}); - -itest!(type_definitions_for_export { - args: "run --reload --check run/type_definitions_for_export.ts", - output: "run/type_definitions_for_export.ts.out", - exit_code: 1, -}); - -itest!(type_directives_01 { - args: "run --reload --check=all -L debug run/type_directives_01.ts", - output: "run/type_directives_01.ts.out", - http_server: true, -}); - -itest!(type_directives_02 { - args: "run --reload --check=all -L debug run/type_directives_02.ts", - output: "run/type_directives_02.ts.out", -}); - -itest!(type_directives_js_main { - args: "run --reload -L debug run/type_directives_js_main.js", - output: "run/type_directives_js_main.js.out", - exit_code: 0, -}); - -itest!(type_directives_redirect { - args: "run --reload --check run/type_directives_redirect.ts", - output: "run/type_directives_redirect.ts.out", - http_server: true, -}); - -itest!(type_headers_deno_types { - args: "run --reload --check run/type_headers_deno_types.ts", - output: "run/type_headers_deno_types.ts.out", - http_server: true, -}); - -itest!(ts_type_imports { - args: "run --reload --check run/ts_type_imports.ts", - output: "run/ts_type_imports.ts.out", - exit_code: 1, -}); - -itest!(ts_decorators { - args: "run --reload --check run/ts_decorators.ts", - output: "run/ts_decorators.ts.out", -}); - -itest!(ts_type_only_import { - args: "run --reload --check run/ts_type_only_import.ts", - output: "run/ts_type_only_import.ts.out", -}); - -itest!(swc_syntax_error { - args: "run --reload --check run/swc_syntax_error.ts", - output: "run/swc_syntax_error.ts.out", - exit_code: 1, -}); - -itest!(unbuffered_stderr { - args: "run --reload run/unbuffered_stderr.ts", - output: "run/unbuffered_stderr.ts.out", -}); - -itest!(unbuffered_stdout { - args: "run --quiet --reload run/unbuffered_stdout.ts", - output: "run/unbuffered_stdout.ts.out", -}); - -itest!(v8_flags_run { - args: "run --v8-flags=--expose-gc run/v8_flags.js", - output: "run/v8_flags.js.out", -}); - -itest!(v8_flags_unrecognized { - args: "repl --v8-flags=--foo,bar,--trace-gc,-baz", - output: "run/v8_flags_unrecognized.out", - exit_code: 1, -}); - -itest!(v8_help { - args: "repl --v8-flags=--help", - output: "run/v8_help.out", -}); - -itest!(unsupported_dynamic_import_scheme { - args: "eval import('xxx:')", - output: "run/unsupported_dynamic_import_scheme.out", - exit_code: 1, -}); - -itest!(wasm { - args: "run --quiet run/wasm.ts", - output: "run/wasm.ts.out", -}); - -itest!(wasm_shared { - args: "run --quiet run/wasm_shared.ts", - output: "run/wasm_shared.out", -}); - -itest!(wasm_async { - args: "run run/wasm_async.js", - output: "run/wasm_async.out", -}); - -itest!(wasm_unreachable { - args: "run --allow-read run/wasm_unreachable.js", - output: "run/wasm_unreachable.out", - exit_code: 1, -}); - -itest!(wasm_url { - args: "run --quiet --allow-net=localhost:4545 run/wasm_url.js", - output: "run/wasm_url.out", - exit_code: 1, - http_server: true, -}); - -itest!(weakref { - args: "run --quiet --reload run/weakref.ts", - output: "run/weakref.ts.out", -}); - -itest!(top_level_await_order { - args: "run --allow-read run/top_level_await/order.js", - output: "run/top_level_await/order.out", -}); - -itest!(top_level_await_loop { - args: "run --allow-read run/top_level_await/loop.js", - output: "run/top_level_await/loop.out", -}); - -itest!(top_level_await_circular { - args: "run --allow-read run/top_level_await/circular.js", - output: "run/top_level_await/circular.out", - exit_code: 1, -}); - -// Regression test for https://github.com/denoland/deno/issues/11238. -itest!(top_level_await_nested { - args: "run --allow-read run/top_level_await/nested/main.js", - output: "run/top_level_await/nested.out", -}); - -itest!(top_level_await_unresolved { - args: "run run/top_level_await/unresolved.js", - output: "run/top_level_await/unresolved.out", - exit_code: 1, -}); - -itest!(top_level_await { - args: "run --allow-read run/top_level_await/top_level_await.js", - output: "run/top_level_await/top_level_await.out", -}); - -itest!(top_level_await_ts { - args: "run --quiet --allow-read run/top_level_await/top_level_await.ts", - output: "run/top_level_await/top_level_await.out", -}); - -itest!(top_level_for_await { - args: "run --quiet run/top_level_await/top_level_for_await.js", - output: "run/top_level_await/top_level_for_await.out", -}); - -itest!(top_level_for_await_ts { - args: "run --quiet run/top_level_await/top_level_for_await.ts", - output: "run/top_level_await/top_level_for_await.out", -}); - -itest!(unstable_disabled { - args: "run --reload --check run/unstable.ts", - exit_code: 1, - output: "run/unstable_disabled.out", -}); - -itest!(unstable_enabled { - args: "run --quiet --reload --unstable run/unstable.ts", - output: "run/unstable_enabled.out", -}); - -itest!(unstable_disabled_js { - args: "run --reload run/unstable.js", - output: "run/unstable_disabled_js.out", -}); - -itest!(unstable_enabled_js { - args: "run --quiet --reload --unstable run/unstable.ts", - output: "run/unstable_enabled_js.out", -}); - -itest!(unstable_worker { - args: "run --reload --unstable --quiet --allow-read run/unstable_worker.ts", - output: "run/unstable_worker.ts.out", -}); - -itest!(import_compression { - args: "run --quiet --reload --allow-net run/import_compression/main.ts", - output: "run/import_compression/main.out", - http_server: true, -}); - -itest!(disallow_http_from_https_js { - args: "run --quiet --reload --cert tls/RootCA.pem https://localhost:5545/run/disallow_http_from_https.js", - output: "run/disallow_http_from_https_js.out", - http_server: true, - exit_code: 1, -}); - -itest!(disallow_http_from_https_ts { - args: "run --quiet --reload --cert tls/RootCA.pem https://localhost:5545/run/disallow_http_from_https.ts", - output: "run/disallow_http_from_https_ts.out", - http_server: true, - exit_code: 1, -}); - -itest!(dynamic_import_conditional { - args: "run --quiet --reload run/dynamic_import_conditional.js", - output: "run/dynamic_import_conditional.js.out", -}); - -itest!(tsx_imports { - args: "run --reload --check run/tsx_imports/tsx_imports.ts", - output: "run/tsx_imports/tsx_imports.ts.out", -}); - -itest!(fix_dynamic_import_errors { - args: "run --reload run/fix_dynamic_import_errors.js", - output: "run/fix_dynamic_import_errors.js.out", -}); - -itest!(fix_emittable_skipped { - args: "run --reload run/fix_emittable_skipped.js", - output: "run/fix_emittable_skipped.ts.out", -}); - -itest!(fix_js_import_js { - args: "run --quiet --reload run/fix_js_import_js.ts", - output: "run/fix_js_import_js.ts.out", -}); - -itest!(fix_js_imports { - args: "run --quiet --reload run/fix_js_imports.ts", - output: "run/fix_js_imports.ts.out", -}); - -itest!(fix_tsc_file_exists { - args: "run --quiet --reload tsc/test.js", - output: "run/fix_tsc_file_exists.out", -}); - -itest!(fix_worker_dispatchevent { - args: "run --quiet --reload run/fix_worker_dispatchevent.ts", - output: "run/fix_worker_dispatchevent.ts.out", -}); - -itest!(es_private_fields { - args: "run --quiet --reload run/es_private_fields.js", - output: "run/es_private_fields.js.out", -}); - -itest!(cjs_imports { - args: "run --quiet --reload run/cjs_imports/main.ts", - output: "run/cjs_imports/main.out", -}); - -itest!(ts_import_from_js { - args: "run --quiet --reload run/ts_import_from_js/main.js", - output: "run/ts_import_from_js/main.out", - http_server: true, -}); - -itest!(jsx_import_from_ts { - args: "run --quiet --reload run/jsx_import_from_ts.ts", - output: "run/jsx_import_from_ts.ts.out", -}); - -itest!(jsx_import_source_pragma { - args: "run --reload run/jsx_import_source_pragma.tsx", - output: "run/jsx_import_source.out", - http_server: true, -}); - -itest!(jsx_import_source_pragma_with_config { - args: - "run --reload --config jsx/deno-jsx.jsonc --no-lock run/jsx_import_source_pragma.tsx", - output: "run/jsx_import_source.out", - http_server: true, -}); - -itest!(jsx_import_source_pragma_with_dev_config { - args: - "run --reload --config jsx/deno-jsxdev.jsonc --no-lock run/jsx_import_source_pragma.tsx", - output: "run/jsx_import_source_dev.out", - http_server: true, -}); - -itest!(jsx_import_source_no_pragma { - args: - "run --reload --config jsx/deno-jsx.jsonc --no-lock run/jsx_import_source_no_pragma.tsx", - output: "run/jsx_import_source.out", - http_server: true, -}); - -itest!(jsx_import_source_no_pragma_dev { - args: "run --reload --config jsx/deno-jsxdev.jsonc --no-lock run/jsx_import_source_no_pragma.tsx", - output: "run/jsx_import_source_dev.out", - http_server: true, -}); - -itest!(jsx_import_source_pragma_import_map { - args: "run --reload --import-map jsx/import-map.json run/jsx_import_source_pragma_import_map.tsx", - output: "run/jsx_import_source_import_map.out", - http_server: true, -}); - -itest!(jsx_import_source_pragma_import_map_dev { - args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsxdev-import-map.jsonc run/jsx_import_source_pragma_import_map.tsx", - output: "run/jsx_import_source_import_map_dev.out", - http_server: true, -}); - -itest!(jsx_import_source_import_map { - args: "run --reload --import-map jsx/import-map.json --no-lock --config jsx/deno-jsx-import-map.jsonc run/jsx_import_source_no_pragma.tsx", - output: "run/jsx_import_source_import_map.out", - http_server: true, -}); - -itest!(jsx_import_source_import_map_dev { - args: "run --reload --import-map jsx/import-map.json --no-lock --config jsx/deno-jsxdev-import-map.jsonc run/jsx_import_source_no_pragma.tsx", - output: "run/jsx_import_source_import_map_dev.out", - http_server: true, -}); - -itest!(jsx_import_source_import_map_scoped { - args: "run --reload --import-map jsx/import-map-scoped.json --no-lock --config jsx/deno-jsx-import-map.jsonc subdir/jsx_import_source_no_pragma.tsx", - output: "run/jsx_import_source_import_map.out", - http_server: true, -}); - -itest!(jsx_import_source_import_map_scoped_dev { - args: "run --reload --import-map jsx/import-map-scoped.json --no-lock --config jsx/deno-jsxdev-import-map.jsonc subdir/jsx_import_source_no_pragma.tsx", - output: "run/jsx_import_source_import_map_dev.out", - http_server: true, -}); - -itest!(jsx_import_source_pragma_no_check { - args: "run --reload --no-check run/jsx_import_source_pragma.tsx", - output: "run/jsx_import_source.out", - http_server: true, -}); - -itest!(jsx_import_source_pragma_with_config_no_check { - args: "run --reload --config jsx/deno-jsx.jsonc --no-lock --no-check run/jsx_import_source_pragma.tsx", - output: "run/jsx_import_source.out", - http_server: true, -}); - -itest!(jsx_import_source_no_pragma_no_check { - args: - "run --reload --config jsx/deno-jsx.jsonc --no-lock --no-check run/jsx_import_source_no_pragma.tsx", - output: "run/jsx_import_source.out", - http_server: true, -}); - -itest!(jsx_import_source_pragma_import_map_no_check { - args: "run --reload --import-map jsx/import-map.json --no-check run/jsx_import_source_pragma_import_map.tsx", - output: "run/jsx_import_source_import_map.out", - http_server: true, -}); - -itest!(jsx_import_source_import_map_no_check { - args: "run --reload --import-map jsx/import-map.json --no-lock --config jsx/deno-jsx-import-map.jsonc --no-check run/jsx_import_source_no_pragma.tsx", - output: "run/jsx_import_source_import_map.out", - http_server: true, -}); - -itest!(jsx_import_source_error { - args: "run --config jsx/deno-jsx-error.jsonc --check run/jsx_import_source_no_pragma.tsx", - output: "run/jsx_import_source_error.out", - exit_code: 1, -}); - -// TODO(#11128): Flaky. Re-enable later. -// itest!(single_compile_with_reload { -// args: "run --relcert/oad --allow-read run/single_compile_with_reload.ts", -// output: "run/single_compile_with_reload.ts.out", -// }); - -itest!(proto_exploit { - args: "run run/proto_exploit.js", - output: "run/proto_exploit.js.out", -}); - -itest!(reference_types { - args: "run --reload --quiet run/reference_types.ts", - output: "run/reference_types.ts.out", -}); - -itest!(references_types_remote { - http_server: true, - args: "run --reload --quiet run/reference_types_remote.ts", - output: "run/reference_types_remote.ts.out", -}); - -itest!(reference_types_error { - args: - "run --config run/checkjs.tsconfig.json --check run/reference_types_error.js", - output: "run/reference_types_error.js.out", - exit_code: 1, -}); - -itest!(reference_types_error_no_check { - args: "run --no-check run/reference_types_error.js", - output_str: Some(""), -}); - -itest!(import_data_url_error_stack { - args: "run --quiet --reload run/import_data_url_error_stack.ts", - output: "run/import_data_url_error_stack.ts.out", - exit_code: 1, -}); - -itest!(import_data_url_import_relative { - args: "run --quiet --reload run/import_data_url_import_relative.ts", - output: "run/import_data_url_import_relative.ts.out", - exit_code: 1, -}); - -itest!(import_data_url_import_map { - args: "run --quiet --reload --import-map import_maps/import_map.json run/import_data_url.ts", - output: "run/import_data_url.ts.out", - }); - -itest!(import_data_url_imports { - args: "run --quiet --reload run/import_data_url_imports.ts", - output: "run/import_data_url_imports.ts.out", - http_server: true, -}); - -itest!(import_data_url_jsx { - args: "run --quiet --reload run/import_data_url_jsx.ts", - output: "run/import_data_url_jsx.ts.out", -}); - -itest!(import_data_url { - args: "run --quiet --reload run/import_data_url.ts", - output: "run/import_data_url.ts.out", -}); - -itest!(import_dynamic_data_url { - args: "run --quiet --reload run/import_dynamic_data_url.ts", - output: "run/import_dynamic_data_url.ts.out", -}); - -itest!(import_blob_url_error_stack { - args: "run --quiet --reload run/import_blob_url_error_stack.ts", - output: "run/import_blob_url_error_stack.ts.out", - exit_code: 1, -}); - -itest!(import_blob_url_import_relative { - args: "run --quiet --reload run/import_blob_url_import_relative.ts", - output: "run/import_blob_url_import_relative.ts.out", - exit_code: 1, -}); - -itest!(import_blob_url_imports { - args: - "run --quiet --reload --allow-net=localhost:4545 run/import_blob_url_imports.ts", - output: "run/import_blob_url_imports.ts.out", - http_server: true, -}); - -itest!(import_blob_url_jsx { - args: "run --quiet --reload run/import_blob_url_jsx.ts", - output: "run/import_blob_url_jsx.ts.out", -}); - -itest!(import_blob_url { - args: "run --quiet --reload run/import_blob_url.ts", - output: "run/import_blob_url.ts.out", -}); - -itest!(import_file_with_colon { - args: "run --quiet --reload run/import_file_with_colon.ts", - output: "run/import_file_with_colon.ts.out", - http_server: true, -}); - -itest!(import_extensionless { - args: "run --quiet --reload run/import_extensionless.ts", - output: "run/import_extensionless.ts.out", - http_server: true, -}); - -itest!(classic_workers_event_loop { - args: - "run --enable-testing-features-do-not-use run/classic_workers_event_loop.js", - output: "run/classic_workers_event_loop.js.out", -}); - -// FIXME(bartlomieju): disabled, because this test is very flaky on CI -// itest!(local_sources_not_cached_in_memory { -// args: "run --allow-read --allow-write run/no_mem_cache.js", -// output: "run/no_mem_cache.js.out", -// }); - -// This test checks that inline source map data is used. It uses a hand crafted -// source map that maps to a file that exists, but is not loaded into the module -// graph (inline_js_source_map_2.ts) (because there are no direct dependencies). -// Source line is not remapped because no inline source contents are included in -// the sourcemap and the file is not present in the dependency graph. -itest!(inline_js_source_map_2 { - args: "run --quiet run/inline_js_source_map_2.js", - output: "run/inline_js_source_map_2.js.out", - exit_code: 1, -}); - -// This test checks that inline source map data is used. It uses a hand crafted -// source map that maps to a file that exists, but is not loaded into the module -// graph (inline_js_source_map_2.ts) (because there are no direct dependencies). -// Source line remapped using th inline source contents that are included in the -// inline source map. -itest!(inline_js_source_map_2_with_inline_contents { - args: "run --quiet run/inline_js_source_map_2_with_inline_contents.js", - output: "run/inline_js_source_map_2_with_inline_contents.js.out", - exit_code: 1, -}); - -// This test checks that inline source map data is used. It uses a hand crafted -// source map that maps to a file that exists, and is loaded into the module -// graph because of a direct import statement (inline_js_source_map.ts). The -// source map was generated from an earlier version of this file, where the throw -// was not commented out. The source line is remapped using source contents that -// from the module graph. -itest!(inline_js_source_map_with_contents_from_graph { - args: "run --quiet run/inline_js_source_map_with_contents_from_graph.js", - output: "run/inline_js_source_map_with_contents_from_graph.js.out", - exit_code: 1, - http_server: true, -}); - -// This test ensures that a descriptive error is shown when we're unable to load -// the import map. Even though this tests only the `run` subcommand, we can be sure -// that the error message is similar for other subcommands as they all use -// `program_state.maybe_import_map` to access the import map underneath. -itest!(error_import_map_unable_to_load { - args: "run --import-map=import_maps/does_not_exist.json import_maps/test.ts", - output: "run/error_import_map_unable_to_load.out", - exit_code: 1, -}); - -// Test that setting `self` in the main thread to some other value doesn't break -// the world. -itest!(replace_self { - args: "run run/replace_self.js", - output: "run/replace_self.js.out", -}); - -itest!(worker_event_handler_test { - args: "run --quiet --reload --allow-read run/worker_event_handler_test.js", - output: "run/worker_event_handler_test.js.out", -}); - -itest!(worker_close_race { - args: "run --quiet --reload --allow-read run/worker_close_race.js", - output: "run/worker_close_race.js.out", -}); - -itest!(worker_drop_handle_race { - args: "run --quiet --reload --allow-read run/worker_drop_handle_race.js", - output: "run/worker_drop_handle_race.js.out", - exit_code: 1, -}); - -itest!(worker_drop_handle_race_terminate { - args: "run --unstable run/worker_drop_handle_race_terminate.js", - output: "run/worker_drop_handle_race_terminate.js.out", -}); - -itest!(worker_close_nested { - args: "run --quiet --reload --allow-read run/worker_close_nested.js", - output: "run/worker_close_nested.js.out", -}); - -itest!(worker_message_before_close { - args: "run --quiet --reload --allow-read run/worker_message_before_close.js", - output: "run/worker_message_before_close.js.out", -}); - -itest!(worker_close_in_wasm_reactions { - args: - "run --quiet --reload --allow-read run/worker_close_in_wasm_reactions.js", - output: "run/worker_close_in_wasm_reactions.js.out", -}); - -itest!(shebang_tsc { - args: "run --quiet --check run/shebang.ts", - output: "run/shebang.ts.out", -}); - -itest!(shebang_swc { - args: "run --quiet run/shebang.ts", - output: "run/shebang.ts.out", -}); - -itest!(shebang_with_json_imports_tsc { - args: "run --quiet import_assertions/json_with_shebang.ts", - output: "import_assertions/json_with_shebang.ts.out", - exit_code: 1, -}); - -itest!(shebang_with_json_imports_swc { - args: "run --quiet --no-check import_assertions/json_with_shebang.ts", - output: "import_assertions/json_with_shebang.ts.out", - exit_code: 1, -}); - -#[test] -fn no_validate_asm() { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("run/no_validate_asm.js") - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert!(output.stderr.is_empty()); - assert!(output.stdout.is_empty()); -} - -#[test] -fn exec_path() { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--allow-read") - .arg("run/exec_path.ts") - .stdout(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let stdout_str = std::str::from_utf8(&output.stdout).unwrap().trim(); - let actual = std::fs::canonicalize(std::path::Path::new(stdout_str)).unwrap(); - let expected = std::fs::canonicalize(util::deno_exe_path()).unwrap(); - assert_eq!(expected, actual); -} - -#[cfg(windows)] -// Clippy suggests to remove the `NoStd` prefix from all variants. I disagree. -#[allow(clippy::enum_variant_names)] -enum WinProcConstraints { - NoStdIn, - NoStdOut, - NoStdErr, -} - -#[cfg(windows)] -fn run_deno_script_constrained( - script_path: std::path::PathBuf, - constraints: WinProcConstraints, -) -> Result<(), i64> { - let file_path = "assets/DenoWinRunner.ps1"; - let constraints = match constraints { - WinProcConstraints::NoStdIn => "1", - WinProcConstraints::NoStdOut => "2", - WinProcConstraints::NoStdErr => "4", - }; - let deno_exe_path = util::deno_exe_path() - .into_os_string() - .into_string() - .unwrap(); - - let deno_script_path = script_path.into_os_string().into_string().unwrap(); - - let args = vec![&deno_exe_path[..], &deno_script_path[..], constraints]; - util::run_powershell_script_file(file_path, args) -} - -#[cfg(windows)] -#[test] -fn should_not_panic_on_no_stdin() { - let output = run_deno_script_constrained( - util::testdata_path().join("echo.ts"), - WinProcConstraints::NoStdIn, - ); - output.unwrap(); -} - -#[cfg(windows)] -#[test] -fn should_not_panic_on_no_stdout() { - let output = run_deno_script_constrained( - util::testdata_path().join("echo.ts"), - WinProcConstraints::NoStdOut, - ); - output.unwrap(); -} - -#[cfg(windows)] -#[test] -fn should_not_panic_on_no_stderr() { - let output = run_deno_script_constrained( - util::testdata_path().join("echo.ts"), - WinProcConstraints::NoStdErr, - ); - output.unwrap(); -} - -#[cfg(not(windows))] -#[test] -fn should_not_panic_on_undefined_home_environment_variable() { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("echo.ts") - .env_remove("HOME") - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); -} - -#[test] -fn should_not_panic_on_undefined_deno_dir_environment_variable() { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("echo.ts") - .env_remove("DENO_DIR") - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); -} - -#[cfg(not(windows))] -#[test] -fn should_not_panic_on_undefined_deno_dir_and_home_environment_variables() { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("echo.ts") - .env_remove("DENO_DIR") - .env_remove("HOME") - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); -} - -#[test] -fn rust_log() { - // Without RUST_LOG the stderr is empty. - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("run/001_hello.js") - .stderr(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert!(output.stderr.is_empty()); - - // With RUST_LOG the stderr is not empty. - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("run/001_hello.js") - .env("RUST_LOG", "debug") - .stderr(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - assert!(!output.stderr.is_empty()); -} - -#[test] -fn dont_cache_on_check_fail() { - let deno_dir = util::new_deno_dir(); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--check=all") - .arg("--reload") - .arg("run/error_003_typescript.ts") - .stderr(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - assert!(!output.stderr.is_empty()); - - let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); - let output = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--check=all") - .arg("run/error_003_typescript.ts") - .stderr(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - assert!(!output.stderr.is_empty()); -} - -mod permissions { - use test_util as util; - - // TODO(bartlomieju): remove --unstable once Deno.spawn is stabilized - #[test] - fn with_allow() { - for permission in &util::PERMISSION_VARIANTS { - let status = util::deno_cmd() - .current_dir(&util::testdata_path()) - .arg("run") - .arg("--unstable") - .arg(format!("--allow-{0}", permission)) - .arg("run/permission_test.ts") - .arg(format!("{0}Required", permission)) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - } - } - - // TODO(bartlomieju): remove --unstable once Deno.spawn is stabilized - #[test] - fn without_allow() { - for permission in &util::PERMISSION_VARIANTS { - let (_, err) = util::run_and_collect_output( - false, - &format!( - "run --unstable run/permission_test.ts {0}Required", - permission - ), - None, - None, - false, - ); - assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); - } - } - - #[test] - fn rw_inside_project_dir() { - const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; - for permission in &PERMISSION_VARIANTS { - let status = util::deno_cmd() - .current_dir(&util::testdata_path()) - .arg("run") - .arg(format!( - "--allow-{0}={1}", - permission, - util::testdata_path() - .into_os_string() - .into_string() - .unwrap() - )) - .arg("run/complex_permissions_test.ts") - .arg(permission) - .arg("run/complex_permissions_test.ts") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - } - } - - #[test] - fn rw_outside_test_dir() { - const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; - for permission in &PERMISSION_VARIANTS { - let (_, err) = util::run_and_collect_output( - false, - &format!( - "run --allow-{0}={1} run/complex_permissions_test.ts {0} {2}", - permission, - util::testdata_path() - .into_os_string() - .into_string() - .unwrap(), - util::root_path() - .join("Cargo.toml") - .into_os_string() - .into_string() - .unwrap(), - ), - None, - None, - false, - ); - assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); - } - } - - #[test] - fn rw_inside_test_dir() { - const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; - for permission in &PERMISSION_VARIANTS { - let status = util::deno_cmd() - .current_dir(&util::testdata_path()) - .arg("run") - .arg(format!( - "--allow-{0}={1}", - permission, - util::testdata_path() - .into_os_string() - .into_string() - .unwrap() - )) - .arg("run/complex_permissions_test.ts") - .arg(permission) - .arg("run/complex_permissions_test.ts") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - } - } - - #[test] - fn rw_outside_test_and_js_dir() { - const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; - let test_dir = util::testdata_path() - .into_os_string() - .into_string() - .unwrap(); - let js_dir = util::root_path() - .join("js") - .into_os_string() - .into_string() - .unwrap(); - for permission in &PERMISSION_VARIANTS { - let (_, err) = util::run_and_collect_output( - false, - &format!( - "run --allow-{0}={1},{2} run/complex_permissions_test.ts {0} {3}", - permission, - test_dir, - js_dir, - util::root_path() - .join("Cargo.toml") - .into_os_string() - .into_string() - .unwrap(), - ), - None, - None, - false, - ); - assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); - } - } - - #[test] - fn rw_inside_test_and_js_dir() { - const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; - let test_dir = util::testdata_path() - .into_os_string() - .into_string() - .unwrap(); - let js_dir = util::root_path() - .join("js") - .into_os_string() - .into_string() - .unwrap(); - for permission in &PERMISSION_VARIANTS { - let status = util::deno_cmd() - .current_dir(&util::testdata_path()) - .arg("run") - .arg(format!("--allow-{0}={1},{2}", permission, test_dir, js_dir)) - .arg("run/complex_permissions_test.ts") - .arg(permission) - .arg("run/complex_permissions_test.ts") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - } - } - - #[test] - fn rw_relative() { - const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; - for permission in &PERMISSION_VARIANTS { - let status = util::deno_cmd() - .current_dir(&util::testdata_path()) - .arg("run") - .arg(format!("--allow-{0}=.", permission)) - .arg("run/complex_permissions_test.ts") - .arg(permission) - .arg("run/complex_permissions_test.ts") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - } - } - - #[test] - fn rw_no_prefix() { - const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; - for permission in &PERMISSION_VARIANTS { - let status = util::deno_cmd() - .current_dir(&util::testdata_path()) - .arg("run") - .arg(format!("--allow-{0}=tls/../", permission)) - .arg("run/complex_permissions_test.ts") - .arg(permission) - .arg("run/complex_permissions_test.ts") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - } - } - - #[test] - fn net_fetch_allow_localhost_4545() { - let (_, err) = util::run_and_collect_output( - true, - "run --allow-net=localhost:4545 run/complex_permissions_test.ts netFetch http://localhost:4545/", - None, - None, - true, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn net_fetch_allow_deno_land() { - let (_, err) = util::run_and_collect_output( - false, - "run --allow-net=deno.land run/complex_permissions_test.ts netFetch http://localhost:4545/", - None, - None, - true, - ); - assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn net_fetch_localhost_4545_fail() { - let (_, err) = util::run_and_collect_output( - false, - "run --allow-net=localhost:4545 run/complex_permissions_test.ts netFetch http://localhost:4546/", - None, - None, - true, - ); - assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn net_fetch_localhost() { - let (_, err) = util::run_and_collect_output( - true, - "run --allow-net=localhost run/complex_permissions_test.ts netFetch http://localhost:4545/ http://localhost:4546/ http://localhost:4547/", - None, - None, - true, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn net_connect_allow_localhost_ip_4555() { - let (_, err) = util::run_and_collect_output( - true, - "run --allow-net=127.0.0.1:4545 run/complex_permissions_test.ts netConnect 127.0.0.1:4545", - None, - None, - true, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn net_connect_allow_deno_land() { - let (_, err) = util::run_and_collect_output( - false, - "run --allow-net=deno.land run/complex_permissions_test.ts netConnect 127.0.0.1:4546", - None, - None, - true, - ); - assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn net_connect_allow_localhost_ip_4545_fail() { - let (_, err) = util::run_and_collect_output( - false, - "run --allow-net=127.0.0.1:4545 run/complex_permissions_test.ts netConnect 127.0.0.1:4546", - None, - None, - true, - ); - assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn net_connect_allow_localhost_ip() { - let (_, err) = util::run_and_collect_output( - true, - "run --allow-net=127.0.0.1 run/complex_permissions_test.ts netConnect 127.0.0.1:4545 127.0.0.1:4546 127.0.0.1:4547", - None, - None, - true, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn net_listen_allow_localhost_4555() { - let (_, err) = util::run_and_collect_output( - true, - "run --allow-net=localhost:4558 run/complex_permissions_test.ts netListen localhost:4558", - None, - None, - false, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn net_listen_allow_deno_land() { - let (_, err) = util::run_and_collect_output( - false, - "run --allow-net=deno.land run/complex_permissions_test.ts netListen localhost:4545", - None, - None, - false, - ); - assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn net_listen_allow_localhost_4555_fail() { - let (_, err) = util::run_and_collect_output( - false, - "run --allow-net=localhost:4555 run/complex_permissions_test.ts netListen localhost:4556", - None, - None, - false, - ); - assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn net_listen_allow_localhost() { - // Port 4600 is chosen to not colide with those used by - // target/debug/test_server - let (_, err) = util::run_and_collect_output( - true, - "run --allow-net=localhost run/complex_permissions_test.ts netListen localhost:4600", - None, - None, - false, - ); - assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); - } - - #[test] - fn _061_permissions_request() { - let args = "run --quiet run/061_permissions_request.ts"; - use util::PtyData::*; - util::test_pty2(args, vec![ - Output("⚠️ ️Deno requests read access to \"foo\". Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)] "), - Input("y\n"), - Output("⚠️ ️Deno requests read access to \"bar\". Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), - Input("n\n"), - Output("granted\r\n"), - Output("prompt\r\n"), - Output("denied\r\n"), - ]); - } - - #[test] - fn _062_permissions_request_global() { - let args = "run --quiet run/062_permissions_request_global.ts"; - use util::PtyData::*; - util::test_pty2(args, vec![ - Output("⚠️ ️Deno requests read access. Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)] "), - Input("y\n"), - Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"), - Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"), - Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"), - ]); - } - - itest!(_063_permissions_revoke { - args: "run --allow-read=foo,bar run/063_permissions_revoke.ts", - output: "run/063_permissions_revoke.ts.out", - }); - - itest!(_064_permissions_revoke_global { - args: "run --allow-read=foo,bar run/064_permissions_revoke_global.ts", - output: "run/064_permissions_revoke_global.ts.out", - }); - - #[test] - fn _066_prompt() { - let args = "run --quiet --unstable run/066_prompt.ts"; - use util::PtyData::*; - util::test_pty2( - args, - vec![ - Output("What is your name? [Jane Doe] "), - Input("John Doe\n"), - Output("Your name is John Doe.\r\n"), - Output("What is your name? [Jane Doe] "), - Input("\n"), - Output("Your name is Jane Doe.\r\n"), - Output("Prompt "), - Input("foo\n"), - Output("Your input is foo.\r\n"), - Output("Question 0 [y/N] "), - Input("Y\n"), - Output("Your answer is true\r\n"), - Output("Question 1 [y/N] "), - Input("N\n"), - Output("Your answer is false\r\n"), - Output("Question 2 [y/N] "), - Input("yes\n"), - Output("Your answer is false\r\n"), - Output("Confirm [y/N] "), - Input("\n"), - Output("Your answer is false\r\n"), - Output("What is Windows EOL? "), - Input("windows\n"), - Output("Your answer is \"windows\"\r\n"), - Output("Hi [Enter] "), - Input("\n"), - Output("Alert [Enter] "), - Input("\n"), - Output("The end of test\r\n"), - Output("What is EOF? "), - Input("\n"), - Output("Your answer is null\r\n"), - ], - ); - } - - itest!(dynamic_import_permissions_remote_remote { - args: "run --quiet --reload --allow-net=localhost:4545 dynamic_import/permissions_remote_remote.ts", - output: "dynamic_import/permissions_remote_remote.ts.out", - http_server: true, - exit_code: 1, - }); - - itest!(dynamic_import_permissions_data_remote { - args: "run --quiet --reload --allow-net=localhost:4545 dynamic_import/permissions_data_remote.ts", - output: "dynamic_import/permissions_data_remote.ts.out", - http_server: true, - exit_code: 1, - }); - - itest!(dynamic_import_permissions_blob_remote { - args: "run --quiet --reload --allow-net=localhost:4545 dynamic_import/permissions_blob_remote.ts", - output: "dynamic_import/permissions_blob_remote.ts.out", - http_server: true, - exit_code: 1, - }); - - itest!(dynamic_import_permissions_data_local { - args: "run --quiet --reload --allow-net=localhost:4545 dynamic_import/permissions_data_local.ts", - output: "dynamic_import/permissions_data_local.ts.out", - http_server: true, - exit_code: 1, - }); - - itest!(dynamic_import_permissions_blob_local { - args: "run --quiet --reload --allow-net=localhost:4545 dynamic_import/permissions_blob_local.ts", - output: "dynamic_import/permissions_blob_local.ts.out", - http_server: true, - exit_code: 1, - }); -} - -itest!(tls_starttls { - args: "run --quiet --reload --allow-net --allow-read --unstable --cert tls/RootCA.pem run/tls_starttls.js", - output: "run/tls.out", -}); - -itest!(tls_connecttls { - args: "run --quiet --reload --allow-net --allow-read --cert tls/RootCA.pem run/tls_connecttls.js", - output: "run/tls.out", -}); - -itest!(byte_order_mark { - args: "run --no-check run/byte_order_mark.ts", - output: "run/byte_order_mark.out", -}); - -#[test] -fn issue9750() { - use util::PtyData::*; - util::test_pty2( - "run --prompt run/issue9750.js", - vec![ - Output("Enter 'yy':\r\n"), - Input("yy\n"), - Output("⚠️ ️Deno requests env access. Run again with --allow-env to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), - Input("n\n"), - Output("⚠️ ️Deno requests env access to \"SECRET\". Run again with --allow-env to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), - Input("n\n"), - Output("error: Uncaught (in promise) PermissionDenied: Requires env access to \"SECRET\", run again with the --allow-env flag\r\n"), - ], - ); -} - -// Regression test for https://github.com/denoland/deno/issues/11451. -itest!(dom_exception_formatting { - args: "run run/dom_exception_formatting.ts", - output: "run/dom_exception_formatting.ts.out", - exit_code: 1, -}); - -itest!(long_data_url_formatting { - args: "run run/long_data_url_formatting.ts", - output: "run/long_data_url_formatting.ts.out", - exit_code: 1, -}); - -itest!(eval_context_throw_dom_exception { - args: "run run/eval_context_throw_dom_exception.js", - output: "run/eval_context_throw_dom_exception.js.out", -}); - -#[test] -#[cfg(unix)] -fn navigator_language_unix() { - let (res, _) = util::run_and_collect_output( - true, - "run navigator_language.ts", - None, - Some(vec![("LC_ALL".to_owned(), "pl_PL".to_owned())]), - false, - ); - assert_eq!(res, "pl-PL\n") -} - -#[test] -fn navigator_language() { - let (res, _) = util::run_and_collect_output( - true, - "run navigator_language.ts", - None, - None, - false, - ); - assert!(!res.is_empty()) -} - -#[test] -#[cfg(unix)] -fn navigator_languages_unix() { - let (res, _) = util::run_and_collect_output( - true, - "run navigator_languages.ts", - None, - Some(vec![ - ("LC_ALL".to_owned(), "pl_PL".to_owned()), - ("NO_COLOR".to_owned(), "1".to_owned()), - ]), - false, - ); - assert_eq!(res, "[ \"pl-PL\" ]\n") -} - -#[test] -fn navigator_languages() { - let (res, _) = util::run_and_collect_output( - true, - "run navigator_languages.ts", - None, - None, - false, - ); - assert!(!res.is_empty()) -} - -/// Regression test for https://github.com/denoland/deno/issues/12740. -#[test] -fn issue12740() { - let mod_dir = TempDir::new(); - let mod1_path = mod_dir.path().join("mod1.ts"); - let mod2_path = mod_dir.path().join("mod2.ts"); - let mut deno_cmd = util::deno_cmd(); - std::fs::write(&mod1_path, "").unwrap(); - let status = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg(&mod1_path) - .stderr(Stdio::null()) - .stdout(Stdio::null()) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - std::fs::write(&mod1_path, "export { foo } from \"./mod2.ts\";").unwrap(); - std::fs::write(&mod2_path, "(").unwrap(); - let status = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg(&mod1_path) - .stderr(Stdio::null()) - .stdout(Stdio::null()) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(!status.success()); -} - -/// Regression test for https://github.com/denoland/deno/issues/12807. -#[test] -fn issue12807() { - let mod_dir = TempDir::new(); - let mod1_path = mod_dir.path().join("mod1.ts"); - let mod2_path = mod_dir.path().join("mod2.ts"); - let mut deno_cmd = util::deno_cmd(); - // With a fresh `DENO_DIR`, run a module with a dependency and a type error. - std::fs::write(&mod1_path, "import './mod2.ts'; Deno.exit('0');").unwrap(); - std::fs::write(&mod2_path, "console.log('Hello, world!');").unwrap(); - let status = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--check") - .arg(&mod1_path) - .stderr(Stdio::null()) - .stdout(Stdio::null()) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(!status.success()); - // Fix the type error and run again. - std::fs::write(&mod1_path, "import './mod2.ts'; Deno.exit(0);").unwrap(); - let status = deno_cmd - .current_dir(util::testdata_path()) - .arg("run") - .arg("--check") - .arg(&mod1_path) - .stderr(Stdio::null()) - .stdout(Stdio::null()) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); -} - -itest!(issue_13562 { - args: "run run/issue13562.ts", - output: "run/issue13562.ts.out", -}); - -itest!(import_assertions_static_import { - args: "run --allow-read import_assertions/static_import.ts", - output: "import_assertions/static_import.out", -}); - -itest!(import_assertions_static_export { - args: "run --allow-read import_assertions/static_export.ts", - output: "import_assertions/static_export.out", -}); - -itest!(import_assertions_static_error { - args: "run --allow-read import_assertions/static_error.ts", - output: "import_assertions/static_error.out", - exit_code: 1, -}); - -itest!(import_assertions_dynamic_import { - args: "run --allow-read import_assertions/dynamic_import.ts", - output: "import_assertions/dynamic_import.out", -}); - -itest!(import_assertions_dynamic_error { - args: "run --allow-read import_assertions/dynamic_error.ts", - output: "import_assertions/dynamic_error.out", - exit_code: 1, -}); - -itest!(import_assertions_type_check { - args: "run --allow-read --check import_assertions/type_check.ts", - output: "import_assertions/type_check.out", - exit_code: 1, -}); - -itest!(delete_window { - args: "run run/delete_window.js", - output_str: Some("true\n"), -}); - -itest!(colors_without_global_this { - args: "run run/colors_without_globalThis.js", - output_str: Some("true\n"), -}); - -itest!(config_auto_discovered_for_local_script { - args: "run --quiet run/with_config/frontend_work.ts", - output_str: Some("ok\n"), -}); - -itest!(no_config_auto_discovery_for_local_script { - args: "run --quiet --no-config --check run/with_config/frontend_work.ts", - output: "run/with_config/no_auto_discovery.out", - exit_code: 1, -}); - -itest!(config_not_auto_discovered_for_remote_script { - args: "run --quiet http://127.0.0.1:4545/run/with_config/server_side_work.ts", - output_str: Some("ok\n"), - http_server: true, -}); - -itest!(wasm_streaming_panic_test { - args: "run run/wasm_streaming_panic_test.js", - output: "run/wasm_streaming_panic_test.js.out", - exit_code: 1, -}); - -// Regression test for https://github.com/denoland/deno/issues/13897. -itest!(fetch_async_error_stack { - args: "run --quiet -A run/fetch_async_error_stack.ts", - output: "run/fetch_async_error_stack.ts.out", - exit_code: 1, -}); - -itest!(unstable_ffi_1 { - args: "run run/ffi/unstable_ffi_1.js", - output: "run/ffi/unstable_ffi_1.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_2 { - args: "run run/ffi/unstable_ffi_2.js", - output: "run/ffi/unstable_ffi_2.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_3 { - args: "run run/ffi/unstable_ffi_3.js", - output: "run/ffi/unstable_ffi_3.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_4 { - args: "run run/ffi/unstable_ffi_4.js", - output: "run/ffi/unstable_ffi_4.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_5 { - args: "run run/ffi/unstable_ffi_5.js", - output: "run/ffi/unstable_ffi_5.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_6 { - args: "run run/ffi/unstable_ffi_6.js", - output: "run/ffi/unstable_ffi_6.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_7 { - args: "run run/ffi/unstable_ffi_7.js", - output: "run/ffi/unstable_ffi_7.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_8 { - args: "run run/ffi/unstable_ffi_8.js", - output: "run/ffi/unstable_ffi_8.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_9 { - args: "run run/ffi/unstable_ffi_9.js", - output: "run/ffi/unstable_ffi_9.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_10 { - args: "run run/ffi/unstable_ffi_10.js", - output: "run/ffi/unstable_ffi_10.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_11 { - args: "run run/ffi/unstable_ffi_11.js", - output: "run/ffi/unstable_ffi_11.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_12 { - args: "run run/ffi/unstable_ffi_12.js", - output: "run/ffi/unstable_ffi_12.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_13 { - args: "run run/ffi/unstable_ffi_13.js", - output: "run/ffi/unstable_ffi_13.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_14 { - args: "run run/ffi/unstable_ffi_14.js", - output: "run/ffi/unstable_ffi_14.js.out", - exit_code: 70, -}); - -itest!(unstable_ffi_15 { - args: "run run/ffi/unstable_ffi_15.js", - output: "run/ffi/unstable_ffi_15.js.out", - exit_code: 70, -}); - -itest!(future_check2 { - args: "run --check run/future_check.ts", - output: "run/future_check2.out", -}); - -itest!(event_listener_error { - args: "run --quiet run/event_listener_error.ts", - output: "run/event_listener_error.ts.out", - exit_code: 1, -}); - -itest!(event_listener_error_handled { - args: "run --quiet run/event_listener_error_handled.ts", - output: "run/event_listener_error_handled.ts.out", -}); - -// https://github.com/denoland/deno/pull/14159#issuecomment-1092285446 -itest!(event_listener_error_immediate_exit { - args: "run --quiet run/event_listener_error_immediate_exit.ts", - output: "run/event_listener_error_immediate_exit.ts.out", - exit_code: 1, -}); - -// https://github.com/denoland/deno/pull/14159#issuecomment-1092285446 -itest!(event_listener_error_immediate_exit_worker { - args: - "run --quiet --unstable -A run/event_listener_error_immediate_exit_worker.ts", - output: "run/event_listener_error_immediate_exit_worker.ts.out", - exit_code: 1, -}); - -itest!(set_timeout_error { - args: "run --quiet run/set_timeout_error.ts", - output: "run/set_timeout_error.ts.out", - exit_code: 1, -}); - -itest!(set_timeout_error_handled { - args: "run --quiet run/set_timeout_error_handled.ts", - output: "run/set_timeout_error_handled.ts.out", -}); - -itest!(aggregate_error { - args: "run --quiet run/aggregate_error.ts", - output: "run/aggregate_error.out", - exit_code: 1, -}); - -itest!(complex_error { - args: "run --quiet run/complex_error.ts", - output: "run/complex_error.ts.out", - exit_code: 1, -}); - -// Regression test for https://github.com/denoland/deno/issues/12143. -itest!(js_root_with_ts_check { - args: "run --quiet --check run/js_root_with_ts_check.js", - output: "run/js_root_with_ts_check.js.out", - exit_code: 1, -}); - -#[test] -fn check_local_then_remote() { - let _http_guard = util::http_server(); - let deno_dir = util::new_deno_dir(); - let output = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--check") - .arg("run/remote_type_error/main.ts") - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - let output = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--check=all") - .arg("run/remote_type_error/main.ts") - .env("NO_COLOR", "1") - .stderr(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - let stderr = std::str::from_utf8(&output.stderr).unwrap(); - assert_contains!(stderr, "Type 'string' is not assignable to type 'number'."); -} - -// Regression test for https://github.com/denoland/deno/issues/15163 -itest!(check_js_points_to_ts { - args: "run --quiet --check --config run/checkjs.tsconfig.json run/check_js_points_to_ts/test.js", - output: "run/check_js_points_to_ts/test.js.out", - exit_code: 1, -}); - -itest!(no_prompt_flag { - args: "run --quiet --unstable --no-prompt run/no_prompt.ts", - output_str: Some(""), -}); - -#[test] -fn deno_no_prompt_environment_variable() { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--unstable") - .arg("run/no_prompt.ts") - .env("DENO_NO_PROMPT", "1") - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); -} - -itest!(report_error { - args: "run --quiet run/report_error.ts", - output: "run/report_error.ts.out", - exit_code: 1, -}); - -itest!(report_error_handled { - args: "run --quiet run/report_error_handled.ts", - output: "run/report_error_handled.ts.out", -}); - -// Regression test for https://github.com/denoland/deno/issues/15513. -itest!(report_error_end_of_program { - args: "run --quiet run/report_error_end_of_program.ts", - output: "run/report_error_end_of_program.ts.out", - exit_code: 1, -}); - -itest!(queue_microtask_error { - args: "run --quiet run/queue_microtask_error.ts", - output: "run/queue_microtask_error.ts.out", - exit_code: 1, -}); - -itest!(queue_microtask_error_handled { - args: "run --quiet run/queue_microtask_error_handled.ts", - output: "run/queue_microtask_error_handled.ts.out", -}); - -itest!(spawn_stdout_inherit { - args: "run --quiet --unstable -A run/spawn_stdout_inherit.ts", - output: "run/spawn_stdout_inherit.ts.out", -}); - -itest!(error_name_non_string { - args: "run --quiet run/error_name_non_string.js", - output: "run/error_name_non_string.js.out", - exit_code: 1, -}); - -itest!(custom_inspect_url { - args: "run run/custom_inspect_url.js", - output: "run/custom_inspect_url.js.out", -}); - -itest!(config_json_import { - args: "run --quiet -c jsx/deno-jsx.json run/config_json_import.ts", - output: "run/config_json_import.ts.out", - http_server: true, -}); - -#[test] -fn running_declaration_files() { - let temp_dir = TempDir::new(); - let files = vec!["file.d.ts", "file.d.cts", "file.d.mts"]; - - for file in files { - temp_dir.write(file, ""); - let mut deno_cmd = util::deno_cmd_with_deno_dir(&temp_dir); - let output = deno_cmd - .current_dir(temp_dir.path()) - .args(["run", file]) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(output.status.success()); - } -} - -itest!(test_and_bench_are_noops_in_run { - args: "run run/test_and_bench_in_run.js", - output_str: Some(""), -}); - -itest!(followup_dyn_import_resolved { - args: "run --unstable --allow-read run/followup_dyn_import_resolves/main.ts", - output: "run/followup_dyn_import_resolves/main.ts.out", -}); - -itest!(unhandled_rejection { - args: "run --check run/unhandled_rejection.ts", - output: "run/unhandled_rejection.ts.out", -}); - -itest!(unhandled_rejection_sync_error { - args: "run --check run/unhandled_rejection_sync_error.ts", - output: "run/unhandled_rejection_sync_error.ts.out", -}); - -itest!(nested_error { - args: "run run/nested_error.ts", - output: "run/nested_error.ts.out", - exit_code: 1, -}); - -itest!(node_env_var_allowlist { - args: "run --unstable --no-prompt run/node_env_var_allowlist.ts", - output: "run/node_env_var_allowlist.ts.out", - exit_code: 1, -}); - -#[test] -fn cache_test() { - let _g = util::http_server(); - let deno_dir = TempDir::new(); - let module_url = - url::Url::parse("http://localhost:4545/run/006_url_imports.ts").unwrap(); - let output = Command::new(util::deno_exe_path()) - .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) - .arg("cache") - .arg("--check=all") - .arg("-L") - .arg("debug") - .arg(module_url.to_string()) - .output() - .expect("Failed to spawn script"); - assert!(output.status.success()); - - let prg = util::deno_exe_path(); - let output = Command::new(&prg) - .env("DENO_DIR", deno_dir.path()) - .env("HTTP_PROXY", "http://nil") - .env("NO_COLOR", "1") - .current_dir(util::testdata_path()) - .arg("run") - .arg(module_url.to_string()) - .output() - .expect("Failed to spawn script"); - - let str_output = std::str::from_utf8(&output.stdout).unwrap(); - - let module_output_path = - util::testdata_path().join("run/006_url_imports.ts.out"); - let mut module_output = String::new(); - let mut module_output_file = std::fs::File::open(module_output_path).unwrap(); - module_output_file - .read_to_string(&mut module_output) - .unwrap(); - - assert_eq!(module_output, str_output); -} - -#[test] -fn cache_invalidation_test() { - let deno_dir = TempDir::new(); - let fixture_path = deno_dir.path().join("fixture.ts"); - { - let mut file = std::fs::File::create(fixture_path.clone()) - .expect("could not create fixture"); - file - .write_all(b"console.log(\"42\");") - .expect("could not write fixture"); - } - let output = Command::new(util::deno_exe_path()) - .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) - .arg("run") - .arg(fixture_path.to_str().unwrap()) - .output() - .expect("Failed to spawn script"); - assert!(output.status.success()); - let actual = std::str::from_utf8(&output.stdout).unwrap(); - assert_eq!(actual, "42\n"); - { - let mut file = std::fs::File::create(fixture_path.clone()) - .expect("could not create fixture"); - file - .write_all(b"console.log(\"43\");") - .expect("could not write fixture"); - } - let output = Command::new(util::deno_exe_path()) - .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) - .arg("run") - .arg(fixture_path.to_str().unwrap()) - .output() - .expect("Failed to spawn script"); - assert!(output.status.success()); - let actual = std::str::from_utf8(&output.stdout).unwrap(); - assert_eq!(actual, "43\n"); -} - -#[test] -fn cache_invalidation_test_no_check() { - let deno_dir = TempDir::new(); - let fixture_path = deno_dir.path().join("fixture.ts"); - { - let mut file = std::fs::File::create(fixture_path.clone()) - .expect("could not create fixture"); - file - .write_all(b"console.log(\"42\");") - .expect("could not write fixture"); - } - let output = Command::new(util::deno_exe_path()) - .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--no-check") - .arg(fixture_path.to_str().unwrap()) - .output() - .expect("Failed to spawn script"); - assert!(output.status.success()); - let actual = std::str::from_utf8(&output.stdout).unwrap(); - assert_eq!(actual, "42\n"); - { - let mut file = std::fs::File::create(fixture_path.clone()) - .expect("could not create fixture"); - file - .write_all(b"console.log(\"43\");") - .expect("could not write fixture"); - } - let output = Command::new(util::deno_exe_path()) - .env("DENO_DIR", deno_dir.path()) - .current_dir(util::testdata_path()) - .arg("run") - .arg("--no-check") - .arg(fixture_path.to_str().unwrap()) - .output() - .expect("Failed to spawn script"); - assert!(output.status.success()); - let actual = std::str::from_utf8(&output.stdout).unwrap(); - assert_eq!(actual, "43\n"); -} - -#[test] -fn ts_dependency_recompilation() { - let t = TempDir::new(); - let ats = t.path().join("a.ts"); - - std::fs::write( - &ats, - " - import { foo } from \"./b.ts\"; - - function print(str: string): void { - console.log(str); - } - - print(foo);", - ) - .unwrap(); - - let bts = t.path().join("b.ts"); - std::fs::write( - &bts, - " - export const foo = \"foo\";", - ) - .unwrap(); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--check") - .arg(&ats) - .output() - .expect("failed to spawn script"); - - let stdout_output = std::str::from_utf8(&output.stdout).unwrap().trim(); - let stderr_output = std::str::from_utf8(&output.stderr).unwrap().trim(); - - assert!(stdout_output.ends_with("foo")); - assert!(stderr_output.starts_with("Check")); - - // Overwrite contents of b.ts and run again - std::fs::write( - &bts, - " - export const foo = 5;", - ) - .expect("error writing file"); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--check") - .arg(&ats) - .output() - .expect("failed to spawn script"); - - let stdout_output = std::str::from_utf8(&output.stdout).unwrap().trim(); - let stderr_output = std::str::from_utf8(&output.stderr).unwrap().trim(); - - // error: TS2345 [ERROR]: Argument of type '5' is not assignable to parameter of type 'string'. - assert!(stderr_output.contains("TS2345")); - assert!(!output.status.success()); - assert!(stdout_output.is_empty()); -} - -#[test] -fn basic_auth_tokens() { - let _g = util::http_server(); - - let output = util::deno_cmd() - .current_dir(util::root_path()) - .arg("run") - .arg("http://127.0.0.1:4554/run/001_hello.js") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - - assert!(!output.status.success()); - - let stdout_str = std::str::from_utf8(&output.stdout).unwrap().trim(); - assert!(stdout_str.is_empty()); - - let stderr_str = std::str::from_utf8(&output.stderr).unwrap().trim(); - eprintln!("{}", stderr_str); - - assert!(stderr_str - .contains("Module not found \"http://127.0.0.1:4554/run/001_hello.js\".")); - - let output = util::deno_cmd() - .current_dir(util::root_path()) - .arg("run") - .arg("http://127.0.0.1:4554/run/001_hello.js") - .env("DENO_AUTH_TOKENS", "testuser123:testpassabc@127.0.0.1:4554") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - - let stderr_str = std::str::from_utf8(&output.stderr).unwrap().trim(); - eprintln!("{}", stderr_str); - - assert!(output.status.success()); - - let stdout_str = std::str::from_utf8(&output.stdout).unwrap().trim(); - assert_eq!(util::strip_ansi_codes(stdout_str), "Hello World"); -} - -#[tokio::test(flavor = "multi_thread", worker_threads = 2)] -async fn test_resolve_dns() { - use std::net::SocketAddr; - use std::str::FromStr; - use std::sync::Arc; - use std::time::Duration; - use tokio::net::TcpListener; - use tokio::net::UdpSocket; - use tokio::sync::oneshot; - use trust_dns_server::authority::Catalog; - use trust_dns_server::authority::ZoneType; - use trust_dns_server::proto::rr::Name; - use trust_dns_server::store::in_memory::InMemoryAuthority; - use trust_dns_server::ServerFuture; - - const DNS_PORT: u16 = 4553; - - // Setup DNS server for testing - async fn run_dns_server(tx: oneshot::Sender<()>) { - let zone_file = std::fs::read_to_string( - util::testdata_path().join("run/resolve_dns.zone.in"), - ) - .unwrap(); - let lexer = Lexer::new(&zone_file); - let records = Parser::new().parse( - lexer, - Some(Name::from_str("example.com").unwrap()), - None, - ); - if records.is_err() { - panic!("failed to parse: {:?}", records.err()) - } - let (origin, records) = records.unwrap(); - let authority = Box::new(Arc::new( - InMemoryAuthority::new(origin, records, ZoneType::Primary, false) - .unwrap(), - )); - let mut catalog: Catalog = Catalog::new(); - catalog.upsert(Name::root().into(), authority); - - let mut server_fut = ServerFuture::new(catalog); - let socket_addr = SocketAddr::from(([127, 0, 0, 1], DNS_PORT)); - let tcp_listener = TcpListener::bind(socket_addr).await.unwrap(); - let udp_socket = UdpSocket::bind(socket_addr).await.unwrap(); - server_fut.register_socket(udp_socket); - server_fut.register_listener(tcp_listener, Duration::from_secs(2)); - - // Notifies that the DNS server is ready - tx.send(()).unwrap(); - - server_fut.block_until_done().await.unwrap(); - } - - let (ready_tx, ready_rx) = oneshot::channel(); - let dns_server_fut = run_dns_server(ready_tx); - let handle = tokio::spawn(dns_server_fut); - - // Waits for the DNS server to be ready - ready_rx.await.unwrap(); - - // Pass: `--allow-net` - { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--check") - .arg("--allow-net") - .arg("run/resolve_dns.ts") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - let err = String::from_utf8_lossy(&output.stderr); - let out = String::from_utf8_lossy(&output.stdout); - println!("{}", err); - assert!(output.status.success()); - assert!(err.starts_with("Check file")); - - let expected = std::fs::read_to_string( - util::testdata_path().join("run/resolve_dns.ts.out"), - ) - .unwrap(); - assert_eq!(expected, out); - } - - // Pass: `--allow-net=127.0.0.1:4553` - { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--check") - .arg("--allow-net=127.0.0.1:4553") - .arg("run/resolve_dns.ts") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - let err = String::from_utf8_lossy(&output.stderr); - let out = String::from_utf8_lossy(&output.stdout); - assert!(output.status.success()); - assert!(err.starts_with("Check file")); - - let expected = std::fs::read_to_string( - util::testdata_path().join("run/resolve_dns.ts.out"), - ) - .unwrap(); - assert_eq!(expected, out); - } - - // Permission error: `--allow-net=deno.land` - { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--check") - .arg("--allow-net=deno.land") - .arg("run/resolve_dns.ts") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - let err = String::from_utf8_lossy(&output.stderr); - let out = String::from_utf8_lossy(&output.stdout); - assert!(!output.status.success()); - assert!(err.starts_with("Check file")); - assert!(err.contains(r#"error: Uncaught PermissionDenied: Requires net access to "127.0.0.1:4553""#)); - assert!(out.is_empty()); - } - - // Permission error: no permission specified - { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--check") - .arg("run/resolve_dns.ts") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - let err = String::from_utf8_lossy(&output.stderr); - let out = String::from_utf8_lossy(&output.stdout); - assert!(!output.status.success()); - assert!(err.starts_with("Check file")); - assert!(err.contains(r#"error: Uncaught PermissionDenied: Requires net access to "127.0.0.1:4553""#)); - assert!(out.is_empty()); - } - - handle.abort(); -} - -#[tokio::test] -async fn http2_request_url() { - // TLS streams require the presence of an ambient local task set to gracefully - // close dropped connections in the background. - LocalSet::new() - .run_until(async { - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--unstable") - .arg("--quiet") - .arg("--allow-net") - .arg("--allow-read") - .arg("./run/http2_request_url.ts") - .arg("4506") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let stdout = child.stdout.as_mut().unwrap(); - let mut buffer = [0; 5]; - let read = stdout.read(&mut buffer).unwrap(); - assert_eq!(read, 5); - let msg = std::str::from_utf8(&buffer).unwrap(); - assert_eq!(msg, "READY"); - - let cert = reqwest::Certificate::from_pem(include_bytes!( - "../testdata/tls/RootCA.crt" - )) - .unwrap(); - - let client = reqwest::Client::builder() - .add_root_certificate(cert) - .http2_prior_knowledge() - .build() - .unwrap(); - - let res = client.get("http://127.0.0.1:4506").send().await.unwrap(); - assert_eq!(200, res.status()); - - let body = res.text().await.unwrap(); - assert_eq!(body, "http://127.0.0.1:4506/"); - - child.kill().unwrap(); - child.wait().unwrap(); - }) - .await; -} - -#[cfg(not(windows))] -#[test] -fn set_raw_should_not_panic_on_no_tty() { - let output = util::deno_cmd() - .arg("eval") - .arg("Deno.stdin.setRaw(true)") - // stdin set to piped so it certainly does not refer to TTY - .stdin(std::process::Stdio::piped()) - // stderr is piped so we can capture output. - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - let stderr = std::str::from_utf8(&output.stderr).unwrap().trim(); - assert!(stderr.contains("BadResource")); -} - -#[test] -fn timeout_clear() { - // https://github.com/denoland/deno/issues/7599 - - use std::time::Duration; - use std::time::Instant; - - let source_code = r#" -const handle = setTimeout(() => { - console.log("timeout finish"); -}, 10000); -clearTimeout(handle); -console.log("finish"); -"#; - - let mut p = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("-") - .stdin(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let stdin = p.stdin.as_mut().unwrap(); - stdin.write_all(source_code.as_bytes()).unwrap(); - let start = Instant::now(); - let status = p.wait().unwrap(); - let end = Instant::now(); - assert!(status.success()); - // check that program did not run for 10 seconds - // for timeout to clear - assert!(end - start < Duration::new(10, 0)); -} - -#[test] -fn broken_stdout() { - let (reader, writer) = os_pipe::pipe().unwrap(); - // drop the reader to create a broken pipe - drop(reader); - - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("eval") - .arg("console.log(3.14)") - .stdout(writer) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - - assert!(!output.status.success()); - let stderr = std::str::from_utf8(output.stderr.as_ref()).unwrap().trim(); - assert!(stderr.contains("Uncaught BrokenPipe")); - assert!(!stderr.contains("panic")); -} - -itest!(error_cause { - args: "run run/error_cause.ts", - output: "run/error_cause.ts.out", - exit_code: 1, -}); - -itest!(error_cause_recursive_aggregate { - args: "run error_cause_recursive_aggregate.ts", - output: "error_cause_recursive_aggregate.ts.out", - exit_code: 1, -}); - -itest!(error_cause_recursive_tail { - args: "run error_cause_recursive_tail.ts", - output: "error_cause_recursive_tail.ts.out", - exit_code: 1, -}); - -itest!(error_cause_recursive { - args: "run run/error_cause_recursive.ts", - output: "run/error_cause_recursive.ts.out", - exit_code: 1, -}); - -#[test] -fn websocket() { - let _g = util::http_server(); - - let script = util::testdata_path().join("run/websocket_test.ts"); - let root_ca = util::testdata_path().join("tls/RootCA.pem"); - let status = util::deno_cmd() - .arg("test") - .arg("--unstable") - .arg("--allow-net") - .arg("--cert") - .arg(root_ca) - .arg(script) - .spawn() - .unwrap() - .wait() - .unwrap(); - - assert!(status.success()); -} - -#[ignore] -#[test] -fn websocketstream() { - let _g = util::http_server(); - - let script = util::testdata_path().join("run/websocketstream_test.ts"); - let root_ca = util::testdata_path().join("tls/RootCA.pem"); - let status = util::deno_cmd() - .arg("test") - .arg("--unstable") - .arg("--allow-net") - .arg("--cert") - .arg(root_ca) - .arg(script) - .spawn() - .unwrap() - .wait() - .unwrap(); - - assert!(status.success()); -} - -#[test] -fn websocketstream_ping() { - use deno_runtime::deno_websocket::tokio_tungstenite::tungstenite; - let _g = util::http_server(); - - let script = util::testdata_path().join("run/websocketstream_ping_test.ts"); - let root_ca = util::testdata_path().join("tls/RootCA.pem"); - let mut child = util::deno_cmd() - .arg("test") - .arg("--unstable") - .arg("--allow-net") - .arg("--cert") - .arg(root_ca) - .arg(script) - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let server = std::net::TcpListener::bind("127.0.0.1:4513").unwrap(); - let (stream, _) = server.accept().unwrap(); - let mut socket = tungstenite::accept(stream).unwrap(); - socket - .write_message(tungstenite::Message::Text(String::from("A"))) - .unwrap(); - socket - .write_message(tungstenite::Message::Ping(vec![])) - .unwrap(); - socket - .write_message(tungstenite::Message::Text(String::from("B"))) - .unwrap(); - let message = socket.read_message().unwrap(); - assert_eq!(message, tungstenite::Message::Pong(vec![])); - socket - .write_message(tungstenite::Message::Text(String::from("C"))) - .unwrap(); - socket.close(None).unwrap(); - - assert!(child.wait().unwrap().success()); -} - -#[test] -fn websocket_server_multi_field_connection_header() { - let script = util::testdata_path() - .join("run/websocket_server_multi_field_connection_header_test.ts"); - let root_ca = util::testdata_path().join("tls/RootCA.pem"); - let mut child = util::deno_cmd() - .arg("run") - .arg("--unstable") - .arg("--allow-net") - .arg("--cert") - .arg(root_ca) - .arg(script) - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stdout = child.stdout.as_mut().unwrap(); - let mut buffer = [0; 5]; - let read = stdout.read(&mut buffer).unwrap(); - assert_eq!(read, 5); - let msg = std::str::from_utf8(&buffer).unwrap(); - assert_eq!(msg, "READY"); - - let req = http::request::Builder::new() - .header(http::header::CONNECTION, "keep-alive, Upgrade") - .uri("ws://localhost:4319") - .body(()) - .unwrap(); - let (mut socket, _) = - deno_runtime::deno_websocket::tokio_tungstenite::tungstenite::connect(req) - .unwrap(); - let message = socket.read_message().unwrap(); - assert_eq!(message, deno_runtime::deno_websocket::tokio_tungstenite::tungstenite::Message::Close(None)); - socket.close(None).unwrap(); - assert!(child.wait().unwrap().success()); -} - -// TODO(bartlomieju): this should use `deno run`, not `deno test`; but the -// test hangs then. https://github.com/denoland/deno/issues/14283 -#[test] -#[ignore] -fn websocket_server_idletimeout() { - let script = - util::testdata_path().join("run/websocket_server_idletimeout.ts"); - let root_ca = util::testdata_path().join("tls/RootCA.pem"); - let mut child = util::deno_cmd() - .arg("test") - .arg("--unstable") - .arg("--allow-net") - .arg("--cert") - .arg(root_ca) - .arg(script) - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap(); - - let stdout = child.stdout.as_mut().unwrap(); - let mut buffer = [0; 5]; - let read = stdout.read(&mut buffer).unwrap(); - assert_eq!(read, 5); - let msg = std::str::from_utf8(&buffer).unwrap(); - assert_eq!(msg, "READY"); - - let req = http::request::Builder::new() - .uri("ws://localhost:4509") - .body(()) - .unwrap(); - let (_ws, _request) = - deno_runtime::deno_websocket::tokio_tungstenite::tungstenite::connect(req) - .unwrap(); - - assert!(child.wait().unwrap().success()); -} - -itest!(auto_discover_lockfile { - args: "run run/auto_discover_lockfile/main.ts", - output: "run/auto_discover_lockfile/main.out", - http_server: true, - exit_code: 10, -}); - -itest!(no_lock_flag { - args: "run --no-lock run/no_lock_flag/main.ts", - output: "run/no_lock_flag/main.out", - http_server: true, - exit_code: 0, -}); - -// Check https://github.com/denoland/deno_std/issues/2882 -itest!(flash_shutdown { - args: "run --unstable --allow-net run/flash_shutdown/main.ts", - exit_code: 0, -}); - -itest!(permission_args { - args: "run run/001_hello.js --allow-net", - output: "run/permission_args.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], -}); - -itest!(permission_args_quiet { - args: "run --quiet run/001_hello.js --allow-net", - output: "run/001_hello.js.out", -}); diff --git a/cli/tests/integration/task_tests.rs b/cli/tests/integration/task_tests.rs deleted file mode 100644 index bc6ddee3b1..0000000000 --- a/cli/tests/integration/task_tests.rs +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::itest; - -// Most of the tests for this are in deno_task_shell. -// These tests are intended to only test integration. - -itest!(task_no_args { - args: "task -q --config task/deno.json", - output: "task/task_no_args.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], - exit_code: 1, -}); - -itest!(task_cwd { - args: "task -q --config task/deno.json --cwd .. echo_cwd", - output: "task/task_cwd.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], - exit_code: 0, -}); - -itest!(task_init_cwd { - args: "task -q --config task/deno.json --cwd .. echo_init_cwd", - output: "task/task_init_cwd.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], - exit_code: 0, -}); - -itest!(task_init_cwd_already_set { - args: "task -q --config task/deno.json echo_init_cwd", - output: "task/task_init_cwd_already_set.out", - envs: vec![ - ("NO_COLOR".to_string(), "1".to_string()), - ("INIT_CWD".to_string(), "HELLO".to_string()) - ], - exit_code: 0, -}); - -itest!(task_cwd_resolves_config_from_specified_dir { - args: "task -q --cwd task", - output: "task/task_no_args.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], - exit_code: 1, -}); - -itest!(task_non_existent { - args: "task --config task/deno.json non_existent", - output: "task/task_non_existent.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], - exit_code: 1, -}); - -#[test] -fn task_emoji() { - // this bug only appears when using a pty/tty - let args = "task --config task/deno.json echo_emoji"; - use test_util::PtyData::*; - test_util::test_pty2(args, vec![Output("Task echo_emoji echo 🔥\r\n🔥")]); -} - -itest!(task_boolean_logic { - args: "task -q --config task/deno.json boolean_logic", - output: "task/task_boolean_logic.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], -}); - -itest!(task_exit_code_5 { - args: "task --config task/deno.json exit_code_5", - output: "task/task_exit_code_5.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], - exit_code: 5, -}); - -itest!(task_additional_args { - args: "task -q --config task/deno.json echo 2", - output: "task/task_additional_args.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], -}); - -itest!(task_additional_args_no_shell_expansion { - args_vec: vec![ - "task", - "-q", - "--config", - "task/deno.json", - "echo", - "$(echo 5)" - ], - output: "task/task_additional_args_no_shell_expansion.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], -}); - -itest!(task_additional_args_nested_strings { - args_vec: vec![ - "task", - "-q", - "--config", - "task/deno.json", - "echo", - "string \"quoted string\"" - ], - output: "task/task_additional_args_nested_strings.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], -}); - -itest!(task_additional_args_no_logic { - args_vec: vec![ - "task", - "-q", - "--config", - "task/deno.json", - "echo", - "||", - "echo", - "5" - ], - output: "task/task_additional_args_no_logic.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], -}); - -itest!(task_deno_exe_no_env { - args_vec: vec!["task", "-q", "--config", "task/deno.json", "deno_echo"], - output: "task/task_deno_exe_no_env.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], - env_clear: true, -}); - -itest!(task_piped_stdin { - args_vec: vec!["task", "-q", "--config", "task/deno.json", "piped"], - output: "task/task_piped_stdin.out", - envs: vec![("NO_COLOR".to_string(), "1".to_string())], -}); diff --git a/cli/tests/integration/test_tests.rs b/cli/tests/integration/test_tests.rs deleted file mode 100644 index 02c4256b12..0000000000 --- a/cli/tests/integration/test_tests.rs +++ /dev/null @@ -1,449 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::itest; -use deno_core::url::Url; -use test_util as util; - -#[test] -fn no_color() { - let (out, _) = util::run_and_collect_output( - false, - "test test/no_color.ts", - None, - Some(vec![("NO_COLOR".to_owned(), "true".to_owned())]), - false, - ); - // ANSI escape codes should be stripped. - assert!(out.contains("success ... ok")); - assert!(out.contains("fail ... FAILED")); - assert!(out.contains("ignored ... ignored")); - assert!(out.contains("FAILED | 1 passed | 1 failed | 1 ignored")); -} - -itest!(overloads { - args: "test test/overloads.ts", - exit_code: 0, - output: "test/overloads.out", -}); - -itest!(meta { - args: "test test/meta.ts", - exit_code: 0, - output: "test/meta.out", -}); - -itest!(pass { - args: "test test/pass.ts", - exit_code: 0, - output: "test/pass.out", -}); - -itest!(ignore { - args: "test test/ignore.ts", - exit_code: 0, - output: "test/ignore.out", -}); - -itest!(ignore_permissions { - args: "test --unstable test/ignore_permissions.ts", - exit_code: 0, - output: "test/ignore_permissions.out", -}); - -itest!(fail { - args: "test test/fail.ts", - exit_code: 1, - output: "test/fail.out", -}); - -itest!(collect { - args: "test --ignore=test/collect/ignore test/collect", - exit_code: 0, - output: "test/collect.out", -}); - -itest!(test_with_config { - args: "test --config test/collect/deno.jsonc test/collect", - exit_code: 0, - output: "test/collect.out", -}); - -itest!(test_with_config2 { - args: "test --config test/collect/deno2.jsonc test/collect", - exit_code: 0, - output: "test/collect2.out", -}); - -itest!(test_with_malformed_config { - args: "test --config test/collect/deno.malformed.jsonc", - exit_code: 1, - output: "test/collect_with_malformed_config.out", -}); - -itest!(parallel_flag { - args: "test test/short-pass.ts --parallel", - exit_code: 0, - output: "test/short-pass.out", -}); - -itest!(parallel_flag_with_env_variable { - args: "test test/short-pass.ts --parallel", - envs: vec![("DENO_JOBS".to_owned(), "2".to_owned())], - exit_code: 0, - output: "test/short-pass.out", -}); - -itest!(jobs_flag { - args: "test test/short-pass.ts --jobs", - exit_code: 0, - output: "test/short-pass-jobs-flag-warning.out", -}); - -itest!(jobs_flag_with_numeric_value { - args: "test test/short-pass.ts --jobs=2", - exit_code: 0, - output: "test/short-pass-jobs-flag-warning.out", -}); - -itest!(load_unload { - args: "test test/load_unload.ts", - exit_code: 0, - output: "test/load_unload.out", -}); - -itest!(interval { - args: "test test/interval.ts", - exit_code: 0, - output: "test/interval.out", -}); - -itest!(doc { - args: "test --doc --allow-all test/doc.ts", - exit_code: 1, - output: "test/doc.out", -}); - -itest!(doc_only { - args: "test --doc --allow-all test/doc_only", - exit_code: 0, - output: "test/doc_only.out", -}); - -itest!(markdown { - args: "test --doc --allow-all test/markdown.md", - exit_code: 1, - output: "test/markdown.out", -}); - -itest!(markdown_windows { - args: "test --doc --allow-all test/markdown_windows.md", - exit_code: 1, - output: "test/markdown_windows.out", -}); - -itest!(markdown_full_block_names { - args: "test --doc --allow-all test/markdown_full_block_names.md", - exit_code: 1, - output: "test/markdown_full_block_names.out", -}); - -itest!(markdown_ignore_html_comment { - args: "test --doc --allow-all test/markdown_with_comment.md", - exit_code: 1, - output: "test/markdown_with_comment.out", -}); - -itest!(text { - args: "test --doc --allow-all test/text.md", - exit_code: 0, - output: "test/text.out", -}); - -itest!(quiet { - args: "test --quiet test/quiet.ts", - exit_code: 0, - output: "test/quiet.out", -}); - -itest!(fail_fast { - args: "test --fail-fast test/fail_fast.ts", - exit_code: 1, - output: "test/fail_fast.out", -}); - -itest!(only { - args: "test test/only.ts", - exit_code: 1, - output: "test/only.out", -}); - -itest!(no_check { - args: "test --no-check test/no_check.ts", - exit_code: 1, - output: "test/no_check.out", -}); - -itest!(no_run { - args: "test --unstable --no-run test/no_run.ts", - output: "test/no_run.out", - exit_code: 1, -}); - -itest!(allow_all { - args: "test --unstable --allow-all test/allow_all.ts", - exit_code: 0, - output: "test/allow_all.out", -}); - -itest!(allow_none { - args: "test --unstable test/allow_none.ts", - exit_code: 1, - output: "test/allow_none.out", -}); - -itest!(ops_sanitizer_unstable { - args: "test --unstable --trace-ops test/ops_sanitizer_unstable.ts", - exit_code: 1, - output: "test/ops_sanitizer_unstable.out", -}); - -itest!(ops_sanitizer_timeout_failure { - args: "test test/ops_sanitizer_timeout_failure.ts", - output: "test/ops_sanitizer_timeout_failure.out", -}); - -itest!(ops_sanitizer_multiple_timeout_tests { - args: "test --trace-ops test/ops_sanitizer_multiple_timeout_tests.ts", - exit_code: 1, - output: "test/ops_sanitizer_multiple_timeout_tests.out", -}); - -itest!(ops_sanitizer_multiple_timeout_tests_no_trace { - args: "test test/ops_sanitizer_multiple_timeout_tests.ts", - exit_code: 1, - output: "test/ops_sanitizer_multiple_timeout_tests_no_trace.out", -}); - -// TODO(@littledivy): re-enable this test, recent optimizations made output non deterministic. -// https://github.com/denoland/deno/issues/14268 -// -// itest!(ops_sanitizer_missing_details { -// args: "test --allow-write --allow-read test/ops_sanitizer_missing_details.ts", -// exit_code: 1, -// output: "test/ops_sanitizer_missing_details.out", -// }); - -itest!(ops_sanitizer_nexttick { - args: "test test/ops_sanitizer_nexttick.ts", - output: "test/ops_sanitizer_nexttick.out", -}); - -itest!(resource_sanitizer { - args: "test --allow-read test/resource_sanitizer.ts", - exit_code: 1, - output: "test/resource_sanitizer.out", -}); - -itest!(exit_sanitizer { - args: "test test/exit_sanitizer.ts", - output: "test/exit_sanitizer.out", - exit_code: 1, -}); - -itest!(clear_timeout { - args: "test test/clear_timeout.ts", - exit_code: 0, - output: "test/clear_timeout.out", -}); - -itest!(finally_timeout { - args: "test test/finally_timeout.ts", - exit_code: 1, - output: "test/finally_timeout.out", -}); - -itest!(unresolved_promise { - args: "test test/unresolved_promise.ts", - exit_code: 1, - output: "test/unresolved_promise.out", -}); - -itest!(unhandled_rejection { - args: "test test/unhandled_rejection.ts", - exit_code: 1, - output: "test/unhandled_rejection.out", -}); - -itest!(filter { - args: "test --filter=foo test/filter", - exit_code: 0, - output: "test/filter.out", -}); - -itest!(shuffle { - args: "test --shuffle test/shuffle", - exit_code: 0, - output_str: Some("[WILDCARD]"), -}); - -itest!(shuffle_with_seed { - args: "test --shuffle=42 test/shuffle", - exit_code: 0, - output: "test/shuffle.out", -}); - -itest!(aggregate_error { - args: "test --quiet test/aggregate_error.ts", - exit_code: 1, - output: "test/aggregate_error.out", -}); - -itest!(steps_passing_steps { - args: "test test/steps/passing_steps.ts", - exit_code: 0, - output: "test/steps/passing_steps.out", -}); - -itest!(steps_failing_steps { - args: "test test/steps/failing_steps.ts", - exit_code: 1, - output: "test/steps/failing_steps.out", -}); - -itest!(steps_ignored_steps { - args: "test test/steps/ignored_steps.ts", - exit_code: 0, - output: "test/steps/ignored_steps.out", -}); - -itest!(steps_invalid_usage { - args: "test test/steps/invalid_usage.ts", - exit_code: 1, - output: "test/steps/invalid_usage.out", -}); - -itest!(steps_output_within { - args: "test test/steps/output_within.ts", - exit_code: 0, - output: "test/steps/output_within.out", -}); - -itest!(no_prompt_by_default { - args: "test --quiet test/no_prompt_by_default.ts", - exit_code: 1, - output: "test/no_prompt_by_default.out", -}); - -itest!(no_prompt_with_denied_perms { - args: "test --quiet --allow-read test/no_prompt_with_denied_perms.ts", - exit_code: 1, - output: "test/no_prompt_with_denied_perms.out", -}); - -itest!(test_with_custom_jsx { - args: "test --quiet --allow-read test/hello_world.ts --config=test/deno_custom_jsx.json", - exit_code: 0, - output: "test/hello_world.out", -}); - -#[test] -fn captured_output() { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("test") - .arg("--allow-run") - .arg("--allow-read") - .arg("--unstable") - .arg("test/captured_output.ts") - .env("NO_COLOR", "1") - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - - let output_start = "------- output -------"; - let output_end = "----- output end -----"; - assert!(output.status.success()); - let output_text = String::from_utf8(output.stdout).unwrap(); - let start = output_text.find(output_start).unwrap() + output_start.len(); - let end = output_text.find(output_end).unwrap(); - // replace zero width space that may appear in test output due - // to test runner output flusher - let output_text = output_text[start..end] - .replace('\u{200B}', "") - .trim() - .to_string(); - let mut lines = output_text.lines().collect::>(); - // the output is racy on either stdout or stderr being flushed - // from the runtime into the rust code, so sort it... the main - // thing here to ensure is that we're capturing the output in - // this block on stdout - lines.sort_unstable(); - assert_eq!(lines.join(" "), "0 1 2 3 4 5 6 7 8 9"); -} - -#[test] -fn recursive_permissions_pledge() { - let output = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("test") - .arg("test/recursive_permissions_pledge.js") - .stderr(std::process::Stdio::piped()) - .stdout(std::process::Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - assert!(String::from_utf8(output.stderr).unwrap().contains( - "pledge test permissions called before restoring previous pledge" - )); -} - -#[test] -fn file_protocol() { - let file_url = - Url::from_file_path(util::testdata_path().join("test/file_protocol.ts")) - .unwrap() - .to_string(); - - (util::CheckOutputIntegrationTest { - args_vec: vec!["test", &file_url], - exit_code: 0, - output: "test/file_protocol.out", - ..Default::default() - }) - .run(); -} - -itest!(uncaught_errors { - args: "test --quiet test/uncaught_errors_1.ts test/uncaught_errors_2.ts test/uncaught_errors_3.ts", - output: "test/uncaught_errors.out", - exit_code: 1, -}); - -itest!(check_local_by_default { - args: "test --quiet test/check_local_by_default.ts", - output: "test/check_local_by_default.out", - http_server: true, -}); - -itest!(check_local_by_default2 { - args: "test --quiet test/check_local_by_default2.ts", - output: "test/check_local_by_default2.out", - http_server: true, - exit_code: 1, -}); - -itest!(non_error_thrown { - args: "test --quiet test/non_error_thrown.ts", - output: "test/non_error_thrown.out", - exit_code: 1, -}); - -itest!(parallel_output { - args: "test --parallel --reload test/parallel_output.ts", - output: "test/parallel_output.out", - exit_code: 1, -}); diff --git a/cli/tests/integration/upgrade_tests.rs b/cli/tests/integration/upgrade_tests.rs deleted file mode 100644 index 959d46fb42..0000000000 --- a/cli/tests/integration/upgrade_tests.rs +++ /dev/null @@ -1,193 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use std::process::{Command, Stdio}; -use test_util as util; -use test_util::TempDir; - -// Warning: this test requires internet access. -// TODO(#7412): reenable. test is flaky -#[test] -#[ignore] -fn upgrade_in_tmpdir() { - let temp_dir = TempDir::new(); - let exe_path = temp_dir.path().join("deno"); - let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); - assert!(exe_path.exists()); - let _mtime1 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); - let status = Command::new(&exe_path) - .arg("upgrade") - .arg("--force") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - let _mtime2 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); - // TODO(ry) assert!(mtime1 < mtime2); -} - -// Warning: this test requires internet access. -// TODO(#7412): reenable. test is flaky -#[test] -#[ignore] -fn upgrade_with_space_in_path() { - let temp_dir = TempDir::new_with_prefix("directory with spaces"); - let exe_path = temp_dir.path().join("deno"); - let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); - assert!(exe_path.exists()); - let status = Command::new(&exe_path) - .arg("upgrade") - .arg("--force") - .env("TMP", temp_dir.path()) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); -} - -// Warning: this test requires internet access. -// TODO(#7412): reenable. test is flaky -#[test] -#[ignore] -fn upgrade_with_version_in_tmpdir() { - let temp_dir = TempDir::new(); - let exe_path = temp_dir.path().join("deno"); - let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); - assert!(exe_path.exists()); - let _mtime1 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); - let status = Command::new(&exe_path) - .arg("upgrade") - .arg("--force") - .arg("--version") - .arg("1.11.5") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - let upgraded_deno_version = String::from_utf8( - Command::new(&exe_path).arg("-V").output().unwrap().stdout, - ) - .unwrap(); - assert!(upgraded_deno_version.contains("1.11.5")); - let _mtime2 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); - // TODO(ry) assert!(mtime1 < mtime2); -} - -// Warning: this test requires internet access. -// TODO(#7412): reenable. test is flaky -#[test] -#[ignore] -fn upgrade_with_canary_in_tmpdir() { - let temp_dir = TempDir::new(); - let exe_path = temp_dir.path().join("deno"); - let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); - assert!(exe_path.exists()); - let _mtime1 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); - let status = Command::new(&exe_path) - .arg("upgrade") - .arg("--canary") - .arg("--version") - .arg("e6685f0f01b8a11a5eaff020f5babcfde76b3038") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - let upgraded_deno_version = String::from_utf8( - Command::new(&exe_path).arg("-V").output().unwrap().stdout, - ) - .unwrap(); - assert!(upgraded_deno_version.contains("e6685f0")); - let _mtime2 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); - // TODO(ry) assert!(mtime1 < mtime2); -} - -// Warning: this test requires internet access. -// TODO(#7412): reenable. test is flaky -#[test] -#[ignore] -fn upgrade_with_out_in_tmpdir() { - let temp_dir = TempDir::new(); - let exe_path = temp_dir.path().join("deno"); - let new_exe_path = temp_dir.path().join("foo"); - let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); - assert!(exe_path.exists()); - let mtime1 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); - let status = Command::new(&exe_path) - .arg("upgrade") - .arg("--version") - .arg("1.11.5") - .arg("--output") - .arg(new_exe_path.to_str().unwrap()) - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - assert!(new_exe_path.exists()); - let mtime2 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); - assert_eq!(mtime1, mtime2); // Original exe_path was not changed. - - let v = String::from_utf8( - Command::new(&new_exe_path) - .arg("-V") - .output() - .unwrap() - .stdout, - ) - .unwrap(); - assert!(v.contains("1.11.5")); -} - -// Warning: this test requires internet access. -// TODO(#7412): reenable. test is flaky -#[test] -#[ignore] -fn upgrade_invalid_stable_version() { - let temp_dir = TempDir::new(); - let exe_path = temp_dir.path().join("deno"); - let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); - assert!(exe_path.exists()); - let output = Command::new(&exe_path) - .arg("upgrade") - .arg("--version") - .arg("foobar") - .stderr(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - assert_eq!( - "error: Invalid semver passed\n", - util::strip_ansi_codes(&String::from_utf8(output.stderr).unwrap()) - ); -} - -// Warning: this test requires internet access. -// TODO(#7412): reenable. test is flaky -#[test] -#[ignore] -fn upgrade_invalid_canary_version() { - let temp_dir = TempDir::new(); - let exe_path = temp_dir.path().join("deno"); - let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); - assert!(exe_path.exists()); - let output = Command::new(&exe_path) - .arg("upgrade") - .arg("--canary") - .arg("--version") - .arg("foobar") - .stderr(Stdio::piped()) - .spawn() - .unwrap() - .wait_with_output() - .unwrap(); - assert!(!output.status.success()); - assert_eq!( - "error: Invalid commit hash passed\n", - util::strip_ansi_codes(&String::from_utf8(output.stderr).unwrap()) - ); -} diff --git a/cli/tests/integration/vendor_tests.rs b/cli/tests/integration/vendor_tests.rs deleted file mode 100644 index efd57b96d9..0000000000 --- a/cli/tests/integration/vendor_tests.rs +++ /dev/null @@ -1,574 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use deno_core::serde_json; -use deno_core::serde_json::json; -use pretty_assertions::assert_eq; -use std::fmt::Write as _; -use std::path::PathBuf; -use std::process::Stdio; -use test_util as util; -use test_util::TempDir; -use util::http_server; -use util::new_deno_dir; - -#[test] -fn output_dir_exists() { - let t = TempDir::new(); - t.write("mod.ts", ""); - t.create_dir_all("vendor"); - t.write("vendor/mod.ts", ""); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("mod.ts") - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - concat!( - "error: Output directory was not empty. Please specify an empty ", - "directory or use --force to ignore this error and potentially ", - "overwrite its contents.", - ), - ); - assert!(!output.status.success()); - - // ensure it errors when using the `--output` arg too - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("--output") - .arg("vendor") - .arg("mod.ts") - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - concat!( - "error: Output directory was not empty. Please specify an empty ", - "directory or use --force to ignore this error and potentially ", - "overwrite its contents.", - ), - ); - assert!(!output.status.success()); - - // now use `--force` - let status = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("mod.ts") - .arg("--force") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); -} - -#[test] -fn standard_test() { - let _server = http_server(); - let t = TempDir::new(); - let vendor_dir = t.path().join("vendor2"); - t.write( - "my_app.ts", - "import {Logger} from 'http://localhost:4545/vendor/query_reexport.ts?testing'; new Logger().log('outputted');", - ); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .arg("vendor") - .arg("my_app.ts") - .arg("--output") - .arg("vendor2") - .env("NO_COLOR", "1") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - concat!( - "Download http://localhost:4545/vendor/query_reexport.ts?testing\n", - "Download http://localhost:4545/vendor/logger.ts?test\n", - "{}", - ), - success_text("2 modules", "vendor2", true), - ) - ); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); - assert!(output.status.success()); - - assert!(vendor_dir.exists()); - assert!(!t.path().join("vendor").exists()); - let import_map: serde_json::Value = - serde_json::from_str(&t.read_to_string("vendor2/import_map.json")).unwrap(); - assert_eq!( - import_map, - json!({ - "imports": { - "http://localhost:4545/vendor/query_reexport.ts?testing": "./localhost_4545/vendor/query_reexport.ts", - "http://localhost:4545/": "./localhost_4545/", - }, - "scopes": { - "./localhost_4545/": { - "./localhost_4545/vendor/logger.ts?test": "./localhost_4545/vendor/logger.ts" - } - } - }), - ); - - // try running the output with `--no-remote` - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--no-remote") - .arg("--check") - .arg("--quiet") - .arg("--import-map") - .arg("vendor2/import_map.json") - .arg("my_app.ts") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!(String::from_utf8_lossy(&output.stderr).trim(), ""); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "outputted"); - assert!(output.status.success()); -} - -#[test] -fn import_map_output_dir() { - let _server = http_server(); - let t = TempDir::new(); - t.write("mod.ts", ""); - t.create_dir_all("vendor"); - t.write( - "vendor/import_map.json", - // will be ignored - "{ \"imports\": { \"https://localhost:4545/\": \"./localhost/\" }}", - ); - t.write( - "deno.json", - "{ \"import_map\": \"./vendor/import_map.json\" }", - ); - t.write( - "my_app.ts", - "import {Logger} from 'http://localhost:4545/vendor/logger.ts'; new Logger().log('outputted');", - ); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("--force") - .arg("--import-map") - .arg("vendor/import_map.json") - .arg("my_app.ts") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - concat!( - "Ignoring import map. Specifying an import map file ({}) in the deno ", - "vendor output directory is not supported. If you wish to use an ", - "import map while vendoring, please specify one located outside this ", - "directory.\n", - "Download http://localhost:4545/vendor/logger.ts\n", - "{}", - ), - PathBuf::from("vendor").join("import_map.json").display(), - success_text_updated_deno_json("1 module", "vendor/"), - ) - ); - assert!(output.status.success()); -} - -#[test] -fn remote_module_test() { - let _server = http_server(); - let t = TempDir::new(); - let vendor_dir = t.path().join("vendor"); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("http://localhost:4545/vendor/query_reexport.ts") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - concat!( - "Download http://localhost:4545/vendor/query_reexport.ts\n", - "Download http://localhost:4545/vendor/logger.ts?test\n", - "{}", - ), - success_text("2 modules", "vendor/", true), - ) - ); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); - assert!(output.status.success()); - assert!(vendor_dir.exists()); - assert!(vendor_dir - .join("localhost_4545/vendor/query_reexport.ts") - .exists()); - assert!(vendor_dir.join("localhost_4545/vendor/logger.ts").exists()); - let import_map: serde_json::Value = - serde_json::from_str(&t.read_to_string("vendor/import_map.json")).unwrap(); - assert_eq!( - import_map, - json!({ - "imports": { - "http://localhost:4545/": "./localhost_4545/", - }, - "scopes": { - "./localhost_4545/": { - "./localhost_4545/vendor/logger.ts?test": "./localhost_4545/vendor/logger.ts", - } - } - }), - ); -} - -#[test] -fn existing_import_map_no_remote() { - let _server = http_server(); - let t = TempDir::new(); - t.write( - "mod.ts", - "import {Logger} from 'http://localhost:4545/vendor/logger.ts';", - ); - let import_map_filename = "imports2.json"; - let import_map_text = - r#"{ "imports": { "http://localhost:4545/vendor/": "./logger/" } }"#; - t.write(import_map_filename, import_map_text); - t.create_dir_all("logger"); - t.write("logger/logger.ts", "export class Logger {}"); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("mod.ts") - .arg("--import-map") - .arg(import_map_filename) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - success_text("0 modules", "vendor/", false) - ); - assert!(output.status.success()); - // it should not have found any remote dependencies because - // the provided import map mapped it to a local directory - assert_eq!(t.read_to_string(import_map_filename), import_map_text); -} - -#[test] -fn existing_import_map_mixed_with_remote() { - let _server = http_server(); - let deno_dir = new_deno_dir(); - let t = TempDir::new(); - t.write( - "mod.ts", - "import {Logger} from 'http://localhost:4545/vendor/logger.ts';", - ); - - let status = util::deno_cmd_with_deno_dir(&deno_dir) - .current_dir(t.path()) - .arg("vendor") - .arg("mod.ts") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - - assert_eq!( - t.read_to_string("vendor/import_map.json"), - r#"{ - "imports": { - "http://localhost:4545/": "./localhost_4545/" - } -} -"#, - ); - - // make the import map specific to support vendoring mod.ts in the next step - t.write( - "vendor/import_map.json", - r#"{ - "imports": { - "http://localhost:4545/vendor/logger.ts": "./localhost_4545/vendor/logger.ts" - } -} -"#, - ); - - t.write( - "mod.ts", - concat!( - "import {Logger} from 'http://localhost:4545/vendor/logger.ts';\n", - "import {Logger as OtherLogger} from 'http://localhost:4545/vendor/mod.ts';\n", - ), - ); - - // now vendor with the existing import map in a separate vendor directory - let deno = util::deno_cmd_with_deno_dir(&deno_dir) - .env("NO_COLOR", "1") - .current_dir(t.path()) - .arg("vendor") - .arg("mod.ts") - .arg("--import-map") - .arg("vendor/import_map.json") - .arg("--output") - .arg("vendor2") - .stderr(Stdio::piped()) - .stdout(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - concat!("Download http://localhost:4545/vendor/mod.ts\n", "{}",), - success_text("1 module", "vendor2", true), - ) - ); - assert!(output.status.success()); - - // tricky scenario here where the output directory now contains a mapping - // back to the previous vendor location - assert_eq!( - t.read_to_string("vendor2/import_map.json"), - r#"{ - "imports": { - "http://localhost:4545/vendor/logger.ts": "../vendor/localhost_4545/vendor/logger.ts", - "http://localhost:4545/": "./localhost_4545/" - }, - "scopes": { - "./localhost_4545/": { - "./localhost_4545/vendor/logger.ts": "../vendor/localhost_4545/vendor/logger.ts" - } - } -} -"#, - ); - - // ensure it runs - let status = util::deno_cmd() - .current_dir(t.path()) - .arg("run") - .arg("--check") - .arg("--no-remote") - .arg("--import-map") - .arg("vendor2/import_map.json") - .arg("mod.ts") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); -} - -#[test] -fn dynamic_import() { - let _server = http_server(); - let t = TempDir::new(); - t.write( - "mod.ts", - "import {Logger} from 'http://localhost:4545/vendor/dynamic.ts'; new Logger().log('outputted');", - ); - - let status = util::deno_cmd() - .current_dir(t.path()) - .arg("vendor") - .arg("mod.ts") - .spawn() - .unwrap() - .wait() - .unwrap(); - assert!(status.success()); - let import_map: serde_json::Value = - serde_json::from_str(&t.read_to_string("vendor/import_map.json")).unwrap(); - assert_eq!( - import_map, - json!({ - "imports": { - "http://localhost:4545/": "./localhost_4545/", - } - }), - ); - - // try running the output with `--no-remote` - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--allow-read=.") - .arg("--no-remote") - .arg("--check") - .arg("--quiet") - .arg("--import-map") - .arg("vendor/import_map.json") - .arg("mod.ts") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!(String::from_utf8_lossy(&output.stderr).trim(), ""); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "outputted"); - assert!(output.status.success()); -} - -#[test] -fn dynamic_non_analyzable_import() { - let _server = http_server(); - let t = TempDir::new(); - t.write( - "mod.ts", - "import {Logger} from 'http://localhost:4545/vendor/dynamic_non_analyzable.ts'; new Logger().log('outputted');", - ); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("vendor") - .arg("--reload") - .arg("mod.ts") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - // todo(https://github.com/denoland/deno_graph/issues/138): it should warn about - // how it couldn't analyze the dynamic import - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - "Download http://localhost:4545/vendor/dynamic_non_analyzable.ts\n{}", - success_text("1 module", "vendor/", true), - ) - ); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); - assert!(output.status.success()); -} - -#[test] -fn update_existing_config_test() { - let _server = http_server(); - let t = TempDir::new(); - t.write( - "my_app.ts", - "import {Logger} from 'http://localhost:4545/vendor/logger.ts'; new Logger().log('outputted');", - ); - t.write("deno.json", "{\n}"); - - let deno = util::deno_cmd() - .current_dir(t.path()) - .arg("vendor") - .arg("my_app.ts") - .arg("--output") - .arg("vendor2") - .env("NO_COLOR", "1") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!( - String::from_utf8_lossy(&output.stderr).trim(), - format!( - "Download http://localhost:4545/vendor/logger.ts\n{}", - success_text_updated_deno_json("1 module", "vendor2",) - ) - ); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); - assert!(output.status.success()); - - // try running the output with `--no-remote` and not specifying a `--vendor` - let deno = util::deno_cmd() - .current_dir(t.path()) - .env("NO_COLOR", "1") - .arg("run") - .arg("--no-remote") - .arg("--check") - .arg("--quiet") - .arg("my_app.ts") - .stdout(Stdio::piped()) - .stderr(Stdio::piped()) - .spawn() - .unwrap(); - let output = deno.wait_with_output().unwrap(); - assert_eq!(String::from_utf8_lossy(&output.stderr).trim(), ""); - assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "outputted"); - assert!(output.status.success()); -} - -fn success_text(module_count: &str, dir: &str, has_import_map: bool) -> String { - let mut text = format!("Vendored {} into {} directory.", module_count, dir); - if has_import_map { - let f = format!( - concat!( - "\n\nTo use vendored modules, specify the `--import-map {}import_map.json` flag when ", - r#"invoking Deno subcommands or add an `"importMap": ""` "#, - "entry to a deno.json file.", - ), - if dir != "vendor/" { - format!("{}{}", dir.trim_end_matches('/'), if cfg!(windows) { '\\' } else {'/'}) - } else { - dir.to_string() - } - ); - write!(text, "{}", f).unwrap(); - } - text -} - -fn success_text_updated_deno_json(module_count: &str, dir: &str) -> String { - format!( - concat!( - "Vendored {} into {} directory.\n\n", - "Updated your local Deno configuration file with a reference to the ", - "new vendored import map at {}import_map.json. Invoking Deno subcommands will ", - "now automatically resolve using the vendored modules. You may override ", - "this by providing the `--import-map ` flag or by ", - "manually editing your Deno configuration file.", - ), - module_count, - dir, - if dir != "vendor/" { - format!( - "{}{}", - dir.trim_end_matches('/'), - if cfg!(windows) { '\\' } else { '/' } - ) - } else { - dir.to_string() - } - ) -} diff --git a/cli/tests/integration/watcher_tests.rs b/cli/tests/integration/watcher_tests.rs deleted file mode 100644 index cd3dc40cfc..0000000000 --- a/cli/tests/integration/watcher_tests.rs +++ /dev/null @@ -1,1234 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -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; - -const CLEAR_SCREEN: &str = r#"[2J"#; - -// Helper function to skip watcher output that contains "Restarting" -// phrase. -fn skip_restarting_line( - stderr_lines: &mut impl Iterator, -) -> String { - loop { - let msg = stderr_lines.next().unwrap(); - if !msg.contains("Restarting") { - return msg; - } - } -} - -fn read_all_lints(stderr_lines: &mut impl Iterator) -> String { - let mut str = String::new(); - for t in stderr_lines { - let t = util::strip_ansi_codes(&t); - if t.starts_with("Watcher File change detected") { - continue; - } - if t.starts_with("Watcher") { - break; - } - if t.starts_with('(') { - str.push_str(&t); - str.push('\n'); - } - } - str -} - -fn wait_for( - condition: impl Fn(&str) -> bool, - lines: &mut impl Iterator, -) { - loop { - let msg = lines.next().unwrap(); - if condition(&msg) { - break; - } - } -} - -fn wait_contains(s: &str, lines: &mut impl Iterator) { - wait_for(|msg| msg.contains(s), lines) -} - -fn read_line(s: &str, lines: &mut impl Iterator) -> String { - lines.find(|m| m.contains(s)).unwrap() -} - -fn check_alive_then_kill(mut child: std::process::Child) { - assert!(child.try_wait().unwrap().is_none()); - child.kill().unwrap(); -} - -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!("STERR: {}", line); - line - }); - (stdout_lines, stderr_lines) -} - -#[test] -fn lint_watch_test() { - let t = TempDir::new(); - let badly_linted_original = - util::testdata_path().join("lint/watch/badly_linted.js"); - let badly_linted_output = - util::testdata_path().join("lint/watch/badly_linted.js.out"); - let badly_linted_fixed1 = - util::testdata_path().join("lint/watch/badly_linted_fixed1.js"); - let badly_linted_fixed1_output = - util::testdata_path().join("lint/watch/badly_linted_fixed1.js.out"); - let badly_linted_fixed2 = - util::testdata_path().join("lint/watch/badly_linted_fixed2.js"); - let badly_linted_fixed2_output = - util::testdata_path().join("lint/watch/badly_linted_fixed2.js.out"); - let badly_linted = t.path().join("badly_linted.js"); - - std::fs::copy(&badly_linted_original, &badly_linted).unwrap(); - - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("lint") - .arg(&badly_linted) - .arg("--watch") - .arg("--unstable") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); - let next_line = stderr_lines.next().unwrap(); - assert_contains!(&next_line, "Lint started"); - let mut output = read_all_lints(&mut stderr_lines); - let expected = std::fs::read_to_string(badly_linted_output).unwrap(); - assert_eq!(output, expected); - - // Change content of the file again to be badly-linted1 - 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); - 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-linted1 - std::fs::copy(&badly_linted_fixed2, &badly_linted).unwrap(); - - output = read_all_lints(&mut stderr_lines); - let expected = std::fs::read_to_string(badly_linted_fixed2_output).unwrap(); - assert_eq!(output, expected); - - // the watcher process is still alive - assert!(child.try_wait().unwrap().is_none()); - - child.kill().unwrap(); - drop(t); -} - -#[test] -fn lint_watch_without_args_test() { - let t = TempDir::new(); - let badly_linted_original = - util::testdata_path().join("lint/watch/badly_linted.js"); - let badly_linted_output = - util::testdata_path().join("lint/watch/badly_linted.js.out"); - let badly_linted_fixed1 = - util::testdata_path().join("lint/watch/badly_linted_fixed1.js"); - let badly_linted_fixed1_output = - util::testdata_path().join("lint/watch/badly_linted_fixed1.js.out"); - let badly_linted_fixed2 = - util::testdata_path().join("lint/watch/badly_linted_fixed2.js"); - let badly_linted_fixed2_output = - util::testdata_path().join("lint/watch/badly_linted_fixed2.js.out"); - let badly_linted = t.path().join("badly_linted.js"); - - std::fs::copy(&badly_linted_original, &badly_linted).unwrap(); - - let mut child = util::deno_cmd() - .current_dir(t.path()) - .arg("lint") - .arg("--watch") - .arg("--unstable") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); - - let next_line = stderr_lines.next().unwrap(); - assert_contains!(&next_line, "Lint started"); - let mut output = read_all_lints(&mut stderr_lines); - let expected = std::fs::read_to_string(badly_linted_output).unwrap(); - assert_eq!(output, expected); - - // Change content of the file again to be badly-linted1 - std::fs::copy(&badly_linted_fixed1, &badly_linted).unwrap(); - - output = read_all_lints(&mut stderr_lines); - 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-linted1 - 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); - let expected = std::fs::read_to_string(badly_linted_fixed2_output).unwrap(); - assert_eq!(output, expected); - - // the watcher process is still alive - assert!(child.try_wait().unwrap().is_none()); - - child.kill().unwrap(); - drop(t); -} - -#[test] -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"); - let badly_linted_fixed1 = - util::testdata_path().join("lint/watch/badly_linted_fixed1.js"); - let badly_linted_fixed2 = - util::testdata_path().join("lint/watch/badly_linted_fixed2.js"); - - let badly_linted_1 = t.path().join("badly_linted_1.js"); - let badly_linted_2 = t.path().join("badly_linted_2.js"); - std::fs::copy(&badly_linted_fixed0, &badly_linted_1).unwrap(); - std::fs::copy(&badly_linted_fixed1, &badly_linted_2).unwrap(); - - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("lint") - .arg(t.path()) - .arg("--watch") - .arg("--unstable") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); - - assert_contains!(read_line("Checked", &mut stderr_lines), "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!(child.try_wait().unwrap().is_none()); - - child.kill().unwrap(); - drop(t); -} - -#[test] -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"); - let badly_formatted_original = fmt_testdata_path.join("badly_formatted.mjs"); - let badly_formatted = t.path().join("badly_formatted.js"); - std::fs::copy(&badly_formatted_original, &badly_formatted).unwrap(); - - let mut child = util::deno_cmd() - .current_dir(&fmt_testdata_path) - .arg("fmt") - .arg(&badly_formatted) - .arg("--watch") - .arg("--unstable") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); - - let next_line = stderr_lines.next().unwrap(); - assert_contains!(&next_line, "Fmt started"); - assert_contains!( - skip_restarting_line(&mut stderr_lines), - "badly_formatted.js" - ); - assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); - - let expected = std::fs::read_to_string(fixed.clone()).unwrap(); - let actual = std::fs::read_to_string(badly_formatted.clone()).unwrap(); - assert_eq!(actual, expected); - - // 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), - "badly_formatted.js" - ); - assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); - - // 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!(actual, expected); - check_alive_then_kill(child); -} - -#[test] -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"); - let badly_formatted_original = fmt_testdata_path.join("badly_formatted.mjs"); - let badly_formatted = t.path().join("badly_formatted.js"); - std::fs::copy(&badly_formatted_original, &badly_formatted).unwrap(); - - let mut child = util::deno_cmd() - .current_dir(t.path()) - .arg("fmt") - .arg("--watch") - .arg("--unstable") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); - - let next_line = stderr_lines.next().unwrap(); - assert_contains!(&next_line, "Fmt started"); - assert_contains!( - skip_restarting_line(&mut stderr_lines), - "badly_formatted.js" - ); - assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); - - let expected = std::fs::read_to_string(fixed.clone()).unwrap(); - let actual = std::fs::read_to_string(badly_formatted.clone()).unwrap(); - assert_eq!(actual, expected); - - // 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), - "badly_formatted.js" - ); - assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); - - // 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!(actual, expected); - check_alive_then_kill(child); -} - -#[test] -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"); - let badly_formatted_1 = t.path().join("badly_formatted_1.js"); - let badly_formatted_2 = t.path().join("badly_formatted_2.js"); - std::fs::copy(&badly_formatted_original, &badly_formatted_1).unwrap(); - std::fs::copy(&badly_formatted_original, &badly_formatted_2).unwrap(); - - let mut child = util::deno_cmd() - .current_dir(&fmt_testdata_path) - .arg("fmt") - .arg(t.path()) - .arg("--watch") - .arg("--check") - .arg("--unstable") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); - - assert_contains!( - read_line("error", &mut stderr_lines), - "Found 2 not formatted files in 2 files" - ); - - // Change content of the file again to be badly formatted - std::fs::copy(&badly_formatted_original, &badly_formatted_1).unwrap(); - - assert_contains!( - read_line("error", &mut stderr_lines), - "Found 2 not formatted files in 2 files" - ); - - check_alive_then_kill(child); -} - -#[test] -fn bundle_js_watch() { - use std::path::PathBuf; - // Test strategy extends this of test bundle_js by adding watcher - let t = TempDir::new(); - let file_to_watch = t.path().join("file_to_watch.ts"); - write(&file_to_watch, "console.log('Hello world');").unwrap(); - assert!(file_to_watch.is_file()); - let t = TempDir::new(); - let bundle = t.path().join("mod6.bundle.js"); - let mut deno = util::deno_cmd() - .current_dir(util::testdata_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() - .unwrap(); - - let (_stdout_lines, mut stderr_lines) = child_lines(&mut deno); - - 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"); - let file = PathBuf::from(&bundle); - assert!(file.is_file()); - wait_contains("Bundle finished", &mut stderr_lines); - - 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, 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"); - let file = PathBuf::from(&bundle); - assert!(file.is_file()); - wait_contains("Bundle finished", &mut stderr_lines); - - // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax - write(&file_to_watch, "syntax error ^^").unwrap(); - - assert_contains!(stderr_lines.next().unwrap(), "File change detected!"); - assert_contains!(stderr_lines.next().unwrap(), "error: "); - wait_contains("Bundle failed", &mut stderr_lines); - 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() { - let t = TempDir::new(); - let file_to_watch = t.path().join("file_to_watch.ts"); - write(&file_to_watch, "syntax error ^^").unwrap(); - let target_file = t.path().join("target.js"); - - let mut deno = util::deno_cmd() - .current_dir(util::testdata_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() - .unwrap(); - let (_stdout_lines, mut stderr_lines) = child_lines(&mut deno); - - let next_line = stderr_lines.next().unwrap(); - assert_contains!(&next_line, "Bundle started"); - assert_contains!(stderr_lines.next().unwrap(), "error:"); - assert_contains!(stderr_lines.next().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, 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"); - - wait_contains("Bundle finished", &mut stderr_lines); - - // bundled file is created - assert!(target_file.is_file()); - check_alive_then_kill(deno); -} - -#[test] -fn run_watch_no_dynamic() { - let t = TempDir::new(); - let file_to_watch = t.path().join("file_to_watch.js"); - write(&file_to_watch, "console.log('Hello world');").unwrap(); - - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--watch") - .arg("--unstable") - .arg("-L") - .arg("debug") - .arg(&file_to_watch) - .env("NO_COLOR", "1") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - - wait_contains("Hello world", &mut stdout_lines); - wait_for( - |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), - &mut stderr_lines, - ); - - // Change content of the file - write(&file_to_watch, "console.log('Hello world2');").unwrap(); - - wait_contains("Restarting", &mut stderr_lines); - wait_contains("Hello world2", &mut stdout_lines); - wait_for( - |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), - &mut stderr_lines, - ); - - // Add dependency - let another_file = t.path().join("another_file.js"); - write(&another_file, "export const foo = 0;").unwrap(); - write( - &file_to_watch, - "import { foo } from './another_file.js'; console.log(foo);", - ) - .unwrap(); - - wait_contains("Restarting", &mut stderr_lines); - wait_contains("0", &mut stdout_lines); - wait_for( - |m| m.contains("Watching paths") && m.contains("another_file.js"), - &mut stderr_lines, - ); - - // Confirm that restarting occurs when a new file is updated - write(&another_file, "export const foo = 42;").unwrap(); - - wait_contains("Restarting", &mut stderr_lines); - wait_contains("42", &mut stdout_lines); - wait_for( - |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), - &mut stderr_lines, - ); - - // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax - write(&file_to_watch, "syntax error ^^").unwrap(); - - wait_contains("Restarting", &mut stderr_lines); - wait_contains("error:", &mut stderr_lines); - wait_for( - |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), - &mut stderr_lines, - ); - - // Then restore the file - write( - &file_to_watch, - "import { foo } from './another_file.js'; console.log(foo);", - ) - .unwrap(); - - wait_contains("Restarting", &mut stderr_lines); - wait_contains("42", &mut stdout_lines); - wait_for( - |m| m.contains("Watching paths") && m.contains("another_file.js"), - &mut stderr_lines, - ); - - // Update the content of the imported file with invalid syntax - write(&another_file, "syntax error ^^").unwrap(); - - wait_contains("Restarting", &mut stderr_lines); - wait_contains("error:", &mut stderr_lines); - wait_for( - |m| m.contains("Watching paths") && m.contains("another_file.js"), - &mut stderr_lines, - ); - - // Modify the imported file and make sure that restarting occurs - write(&another_file, "export const foo = 'modified!';").unwrap(); - - wait_contains("Restarting", &mut stderr_lines); - wait_contains("modified!", &mut stdout_lines); - wait_contains("Watching paths", &mut stderr_lines); - check_alive_then_kill(child); -} - -// TODO(bartlomieju): this test became flaky on macOS runner; it is unclear -// 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() { - let t = TempDir::new(); - let file_to_watch = t.path().join("file_to_watch.js"); - write(&file_to_watch, "console.log('Hello world');").unwrap(); - - let external_file_to_watch = t.path().join("external_file_to_watch.txt"); - write(&external_file_to_watch, "Hello world").unwrap(); - - let mut watch_arg = "--watch=".to_owned(); - let external_file_to_watch_str = external_file_to_watch - .clone() - .into_os_string() - .into_string() - .unwrap(); - watch_arg.push_str(&external_file_to_watch_str); - - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg(watch_arg) - .arg("-L") - .arg("debug") - .arg("--unstable") - .arg(&file_to_watch) - .env("NO_COLOR", "1") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .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( - |m| { - m.contains("Watching paths") && m.contains("external_file_to_watch.txt") - }, - &mut stderr_lines, - ); - - // Change content of the external file - write(&external_file_to_watch, "Hello world2").unwrap(); - - wait_contains("Restarting", &mut stderr_lines); - wait_contains("Process finished", &mut stderr_lines); - check_alive_then_kill(child); -} - -#[test] -fn run_watch_load_unload_events() { - let t = TempDir::new(); - let file_to_watch = t.path().join("file_to_watch.js"); - write( - &file_to_watch, - r#" - setInterval(() => {}, 0); - window.addEventListener("load", () => { - console.log("load"); - }); - - window.addEventListener("unload", () => { - console.log("unload"); - }); - "#, - ) - .unwrap(); - - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--watch") - .arg("--unstable") - .arg("-L") - .arg("debug") - .arg(&file_to_watch) - .env("NO_COLOR", "1") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - 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( - |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), - &mut stderr_lines, - ); - - // Change content of the file, this time without an interval to keep it alive. - write( - &file_to_watch, - r#" - window.addEventListener("load", () => { - console.log("load"); - }); - - window.addEventListener("unload", () => { - console.log("unload"); - }); - "#, - ) - .unwrap(); - - // Wait for the restart - wait_contains("Restarting", &mut stderr_lines); - - // Confirm that the unload event was dispatched from the first run - wait_contains("unload", &mut stdout_lines); - - // Followed by the load event of the second run - wait_contains("load", &mut stdout_lines); - - // Which is then unloaded as there is nothing keeping it alive. - wait_contains("unload", &mut stdout_lines); - 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() { - let t = TempDir::new(); - let file_to_watch = t.path().join("file_to_watch.js"); - write(&file_to_watch, "syntax error ^^").unwrap(); - - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--watch") - .arg("--unstable") - .arg("-L") - .arg("debug") - .arg(&file_to_watch) - .env("NO_COLOR", "1") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .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( - |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), - &mut stderr_lines, - ); - - // Make sure the watcher actually restarts and works fine with the proper syntax - write(&file_to_watch, "console.log(42);").unwrap(); - - wait_contains("Restarting", &mut stderr_lines); - wait_contains("42", &mut stdout_lines); - wait_contains("Process finished", &mut stderr_lines); - check_alive_then_kill(child); -} - -#[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); - write(&absolute_path, filecontent).unwrap(); - let relative_path = absolute_path - .strip_prefix(util::testdata_path()) - .unwrap() - .to_owned(); - assert!(relative_path.is_relative()); - relative_path - } - let temp_directory = TempDir::new_in(&util::testdata_path()); - 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::testdata_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() - .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"); - - check_alive_then_kill(child); -} - -#[test] -fn run_watch_error_messages() { - let t = TempDir::new(); - let file_to_watch = t.path().join("file_to_watch.js"); - write( - &file_to_watch, - "throw SyntaxError(`outer`, {cause: TypeError(`inner`)})", - ) - .unwrap(); - - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--watch") - .arg(&file_to_watch) - .env("NO_COLOR", "1") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .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); - - check_alive_then_kill(child); -} - -#[test] -fn test_watch() { - let t = TempDir::new(); - - let mut child = util::deno_cmd() - .current_dir(util::testdata_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() - .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); - - 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"); - write(&foo_file, "export default function foo() { 1 + 1 }").unwrap(); - write(&bar_file, "export default function bar() { 2 + 2 }").unwrap(); - write( - &foo_test, - "import foo from './foo.js'; Deno.test('foo', foo);", - ) - .unwrap(); - write( - &bar_test, - "import bar from './bar.js'; Deno.test('bar', bar);", - ) - .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); - - // Change content of the file - write( - &foo_test, - "import foo from './foo.js'; Deno.test('foobar', foo);", - ) - .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); - - // 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); - - // 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); - - // 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_contains!(stderr_lines.next().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); - - // 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 - write( - &foo_file, - "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); - - // 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); - - // Test that circular dependencies work fine - write( - &foo_file, - "import './bar.js'; export default function foo() { 1 + 1 }", - ) - .unwrap(); - write( - &bar_file, - "import './foo.js'; export default function bar() { 2 + 2 }", - ) - .unwrap(); - check_alive_then_kill(child); -} - -#[flaky_test] -fn test_watch_doc() { - let t = TempDir::new(); - - let mut child = util::deno_cmd() - .current_dir(util::testdata_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() - .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); - - let foo_file = t.path().join("foo.ts"); - write( - &foo_file, - r#" - export default function foo() {} - "#, - ) - .unwrap(); - - write( - &foo_file, - r#" - /** - * ```ts - * import foo from "./foo.ts"; - * ``` - */ - export default function foo() {} - "#, - ) - .unwrap(); - - // 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"); - check_alive_then_kill(child); -} - -#[test] -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(); - let mut child = util::deno_cmd() - .current_dir(util::testdata_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() - .unwrap(); - let (_, mut stderr_lines) = child_lines(&mut child); - let line1 = stderr_lines.next().unwrap(); - assert_contains!(&line1, "Process started"); - let line2 = stderr_lines.next().unwrap(); - assert_contains!(&line2, "error: Module not found"); - assert_contains!(&line2, "nonexistent.js"); - let line3 = stderr_lines.next().unwrap(); - assert_contains!(&line3, " at "); - assert_contains!(&line3, "file_to_watch.js"); - wait_contains("Process finished", &mut stderr_lines); - check_alive_then_kill(child); -} - -// Regression test for https://github.com/denoland/deno/issues/15428. -#[test] -fn test_watch_unload_handler_error_on_drop() { - let t = TempDir::new(); - let file_to_watch = t.path().join("file_to_watch.js"); - write( - &file_to_watch, - r#" - addEventListener("unload", () => { - throw new Error("foo"); - }); - setTimeout(() => { - throw new Error("bar"); - }); - "#, - ) - .unwrap(); - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--watch") - .arg(&file_to_watch) - .env("NO_COLOR", "1") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .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); - check_alive_then_kill(child); -} - -#[test] -fn run_watch_dynamic_imports() { - let t = TempDir::new(); - let file_to_watch = t.path().join("file_to_watch.js"); - write( - &file_to_watch, - r#" - console.log("Hopefully dynamic import will be watched..."); - await import("./imported.js"); - "#, - ) - .unwrap(); - let file_to_watch2 = t.path().join("imported.js"); - write( - &file_to_watch2, - r#" - import "./imported2.js"; - console.log("I'm dynamically imported and I cause restarts!"); - "#, - ) - .unwrap(); - let file_to_watch3 = t.path().join("imported2.js"); - write( - &file_to_watch3, - r#" - console.log("I'm statically imported from the dynamic import"); - "#, - ) - .unwrap(); - - let mut child = util::deno_cmd() - .current_dir(util::testdata_path()) - .arg("run") - .arg("--watch") - .arg("--unstable") - .arg("--allow-read") - .arg("-L") - .arg("debug") - .arg(&file_to_watch) - .env("NO_COLOR", "1") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - - assert_contains!(stderr_lines.next().unwrap(), "Process started"); - - wait_contains( - "Hopefully dynamic import will be watched...", - &mut stdout_lines, - ); - wait_contains( - "I'm statically imported from the dynamic import", - &mut stdout_lines, - ); - wait_contains( - "I'm dynamically imported and I cause restarts!", - &mut stdout_lines, - ); - - wait_contains("finished", &mut stderr_lines); - wait_for( - |m| m.contains("Watching paths") && m.contains("imported2.js"), - &mut stderr_lines, - ); - - write( - &file_to_watch3, - r#" - console.log("I'm statically imported from the dynamic import and I've changed"); - "#, - ) - .unwrap(); - - wait_contains("Restarting", &mut stderr_lines); - wait_contains( - "Hopefully dynamic import will be watched...", - &mut stdout_lines, - ); - wait_contains( - "I'm statically imported from the dynamic import and I've changed", - &mut stdout_lines, - ); - wait_contains( - "I'm dynamically imported and I cause restarts!", - &mut stdout_lines, - ); - - check_alive_then_kill(child); -} - -// https://github.com/denoland/deno/issues/16267 -#[test] -fn run_watch_flash() { - let filename = "watch_flash.js"; - let t = TempDir::new(); - let file_to_watch = t.path().join(filename); - write( - &file_to_watch, - r#" - console.log("Starting flash server..."); - Deno.serve({ - onListen() { - console.error("First server is listening"); - }, - handler: () => {}, - port: 4601, - }); - "#, - ) - .unwrap(); - - let mut child = util::deno_cmd() - .current_dir(t.path()) - .arg("run") - .arg("--watch") - .arg("--unstable") - .arg("--allow-net") - .arg("-L") - .arg("debug") - .arg(&file_to_watch) - .env("NO_COLOR", "1") - .stdout(std::process::Stdio::piped()) - .stderr(std::process::Stdio::piped()) - .spawn() - .unwrap(); - let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); - - wait_contains("Starting flash server...", &mut stdout_lines); - wait_for( - |m| m.contains("Watching paths") && m.contains(filename), - &mut stderr_lines, - ); - - write( - &file_to_watch, - r#" - console.log("Restarting flash server..."); - Deno.serve({ - onListen() { - console.error("Second server is listening"); - }, - handler: () => {}, - port: 4601, - }); - "#, - ) - .unwrap(); - - wait_contains("File change detected! Restarting!", &mut stderr_lines); - wait_contains("Restarting flash server...", &mut stdout_lines); - wait_contains("Second server is listening", &mut stderr_lines); - - check_alive_then_kill(child); -} diff --git a/cli/tests/integration/worker_tests.rs b/cli/tests/integration/worker_tests.rs deleted file mode 100644 index b864c15da5..0000000000 --- a/cli/tests/integration/worker_tests.rs +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -use crate::itest; - -itest!(workers { - args: "test --reload --location http://127.0.0.1:4545/ -A --unstable workers/test.ts", - output: "workers/test.ts.out", - http_server: true, -}); - -itest!(worker_error { - args: "run -A workers/worker_error.ts", - output: "workers/worker_error.ts.out", - exit_code: 1, -}); - -itest!(worker_nested_error { - args: "run -A workers/worker_nested_error.ts", - output: "workers/worker_nested_error.ts.out", - exit_code: 1, -}); - -itest!(worker_async_error { - args: "run -A --quiet --reload workers/worker_async_error.ts", - output: "workers/worker_async_error.ts.out", - http_server: true, - exit_code: 1, -}); - -itest!(worker_message_handler_error { - args: "run -A --quiet --reload workers/worker_message_handler_error.ts", - output: "workers/worker_message_handler_error.ts.out", - http_server: true, - exit_code: 1, -}); - -itest!(nonexistent_worker { - args: "run --allow-read workers/nonexistent_worker.ts", - output: "workers/nonexistent_worker.out", - exit_code: 1, -}); - -itest!(_084_worker_custom_inspect { - args: "run --allow-read workers/custom_inspect/main.ts", - output: "workers/custom_inspect/main.out", -}); - -itest!(error_worker_permissions_local { - args: "run --reload workers/error_worker_permissions_local.ts", - output: "workers/error_worker_permissions_local.ts.out", - exit_code: 1, -}); - -itest!(error_worker_permissions_remote { - args: "run --reload workers/error_worker_permissions_remote.ts", - http_server: true, - output: "workers/error_worker_permissions_remote.ts.out", - exit_code: 1, -}); - -itest!(worker_permissions_remote_remote { - args: "run --quiet --reload --allow-net=localhost:4545 workers/permissions_remote_remote.ts", - output: "workers/permissions_remote_remote.ts.out", - http_server: true, - exit_code: 1, -}); - -itest!(worker_permissions_dynamic_remote { - args: "run --quiet --reload --allow-net --unstable workers/permissions_dynamic_remote.ts", - output: "workers/permissions_dynamic_remote.ts.out", - http_server: true, - exit_code: 1, -}); - -itest!(worker_permissions_data_remote { - args: "run --quiet --reload --allow-net=localhost:4545 workers/permissions_data_remote.ts", - output: "workers/permissions_data_remote.ts.out", - http_server: true, - exit_code: 1, -}); - -itest!(worker_permissions_blob_remote { - args: "run --quiet --reload --allow-net=localhost:4545 workers/permissions_blob_remote.ts", - output: "workers/permissions_blob_remote.ts.out", - http_server: true, - exit_code: 1, -}); - -itest!(worker_permissions_data_local { - args: "run --quiet --reload --allow-net=localhost:4545 workers/permissions_data_local.ts", - output: "workers/permissions_data_local.ts.out", - http_server: true, - exit_code: 1, -}); - -itest!(worker_permissions_blob_local { - args: "run --quiet --reload --allow-net=localhost:4545 workers/permissions_blob_local.ts", - output: "workers/permissions_blob_local.ts.out", - http_server: true, - exit_code: 1, -}); - -itest!(worker_terminate_tla_crash { - args: "run --quiet --reload workers/terminate_tla_crash.js", - output: "workers/terminate_tla_crash.js.out", -}); - -itest!(worker_error_event { - args: "run --quiet -A workers/error_event.ts", - output: "workers/error_event.ts.out", - exit_code: 1, -}); diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs deleted file mode 100644 index ae6bf98f6f..0000000000 --- a/cli/tests/integration_tests.rs +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. - -// The tests exist in a sub folder instead of as separate files in -// this directory so that cargo doesn't compile each file as a new crate. - -mod integration; diff --git a/cli/tests/js_unit_tests.rs b/cli/tests/js_unit_tests.rs new file mode 100644 index 0000000000..3d887600c3 --- /dev/null +++ b/cli/tests/js_unit_tests.rs @@ -0,0 +1,45 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +use test_util as util; + +mod js { + use super::*; + + #[test] + fn js_unit_tests_lint() { + let status = util::deno_cmd() + .arg("lint") + .arg("--unstable") + .arg(util::tests_path().join("unit")) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + + #[test] + fn js_unit_tests() { + let _g = util::http_server(); + + // Note that the unit tests are not safe for concurrency and must be run with a concurrency limit + // of one because there are some chdir tests in there. + // TODO(caspervonb) split these tests into two groups: parallel and serial. + let mut deno = util::deno_cmd() + .current_dir(util::root_path()) + .arg("test") + .arg("--unstable") + .arg("--location=http://js-unit-tests/foo/bar") + .arg("--no-prompt") + .arg("-A") + .arg(util::tests_path().join("unit")) + .spawn() + .expect("failed to spawn script"); + + let status = deno.wait().expect("failed to wait for the child process"); + assert_eq!(Some(0), status.code()); + assert!(status.success()); + } +} diff --git a/cli/tests/lint_tests.rs b/cli/tests/lint_tests.rs new file mode 100644 index 0000000000..36bf412923 --- /dev/null +++ b/cli/tests/lint_tests.rs @@ -0,0 +1,139 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +use test_util as util; + +mod lint { + use super::*; + + #[test] + fn ignore_unexplicit_files() { + let output = util::deno_cmd() + .current_dir(util::root_path()) + .env("NO_COLOR", "1") + .arg("lint") + .arg("--unstable") + .arg("--ignore=./") + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + assert_eq!( + String::from_utf8_lossy(&output.stderr), + "error: No target files found.\n" + ); + } + + itest!(all { + args: "lint lint/without_config/file1.js lint/without_config/file2.ts lint/without_config/ignored_file.ts", + output: "lint/expected.out", + exit_code: 1, +}); + + itest!(quiet { + args: "lint --quiet lint/without_config/file1.js", + output: "lint/expected_quiet.out", + exit_code: 1, + }); + + itest!(json { + args: + "lint --json lint/without_config/file1.js lint/without_config/file2.ts lint/without_config/ignored_file.ts lint/without_config/malformed.js", + output: "lint/expected_json.out", + exit_code: 1, +}); + + itest!(compact { + args: + "lint --compact lint/without_config/file1.js lint/without_config/ignored_file.tss", + output: "lint/expected_compact.out", + exit_code: 1, +}); + + itest!(ignore { + args: + "lint --ignore=lint/without_config/file1.js,lint/without_config/malformed.js,lint/without_config/lint_with_config/ lint/without_config/", + output: "lint/expected_ignore.out", + exit_code: 1, +}); + + itest!(glob { + args: "lint --ignore=lint/without_config/malformed.js,lint/with_config/ lint/without_config/", + output: "lint/expected_glob.out", + exit_code: 1, +}); + + itest!(stdin { + args: "lint -", + input: Some("let _a: any;"), + output: "lint/expected_from_stdin.out", + exit_code: 1, + }); + + itest!(stdin_json { + args: "lint --json -", + input: Some("let _a: any;"), + output: "lint/expected_from_stdin_json.out", + exit_code: 1, + }); + + itest!(rules { + args: "lint --rules", + output: "lint/expected_rules.out", + exit_code: 0, + }); + + // Make sure that the rules are printed if quiet option is enabled. + itest!(rules_quiet { + args: "lint --rules -q", + output: "lint/expected_rules.out", + exit_code: 0, + }); + + itest!(lint_with_config { + args: "lint --config lint/Deno.jsonc lint/with_config/", + output: "lint/with_config.out", + exit_code: 1, + }); + + itest!(lint_with_report_config { + args: "lint --config lint/Deno.compact.format.jsonc lint/with_config/", + output: "lint/with_report_config_compact.out", + exit_code: 1, + }); + + // Check if CLI flags take precedence + itest!(lint_with_report_config_override { + args: + "lint --config lint/Deno.compact.format.jsonc lint/with_config/ --json", + output: "lint/with_report_config_override.out", + exit_code: 1, + }); + + itest!(lint_with_config_and_flags { + args: "lint --config lint/Deno.jsonc --ignore=lint/with_config/a.ts", + output: "lint/with_config_and_flags.out", + exit_code: 1, + }); + + itest!(lint_with_config_without_tags { + args: "lint --config lint/Deno.no_tags.jsonc lint/with_config/", + output: "lint/with_config_without_tags.out", + exit_code: 1, + }); + + itest!(lint_with_malformed_config { + args: "lint --config lint/Deno.malformed.jsonc", + output: "lint/with_malformed_config.out", + exit_code: 1, + }); + + itest!(lint_with_malformed_config2 { + args: "lint --config lint/Deno.malformed2.jsonc", + output: "lint/with_malformed_config2.out", + exit_code: 1, + }); +} diff --git a/cli/tests/lsp_tests.rs b/cli/tests/lsp_tests.rs new file mode 100644 index 0000000000..541ab8e8d0 --- /dev/null +++ b/cli/tests/lsp_tests.rs @@ -0,0 +1,6276 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod lsp { + use deno_ast::ModuleSpecifier; + use deno_core::serde::de::DeserializeOwned; + use deno_core::serde::Deserialize; + use deno_core::serde::Serialize; + use deno_core::serde_json; + use deno_core::serde_json::json; + use deno_core::serde_json::Value; + use deno_core::url::Url; + use pretty_assertions::assert_eq; + use std::collections::HashSet; + use std::fs; + use test_util::deno_exe_path; + use test_util::http_server; + use test_util::lsp::LspClient; + use test_util::testdata_path; + use test_util::TempDir; + use tower_lsp::lsp_types as lsp; + + fn load_fixture(path: &str) -> Value { + load_fixture_as(path) + } + + fn load_fixture_as(path: &str) -> T + where + T: DeserializeOwned, + { + let fixture_str = load_fixture_str(path); + serde_json::from_str::(&fixture_str).unwrap() + } + + fn load_fixture_str(path: &str) -> String { + let fixtures_path = testdata_path().join("lsp"); + let path = fixtures_path.join(path); + fs::read_to_string(path).unwrap() + } + + fn init(init_path: &str) -> LspClient { + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", load_fixture(init_path)) + .unwrap(); + client.write_notification("initialized", json!({})).unwrap(); + client + } + + fn did_open( + client: &mut LspClient, + params: V, + ) -> Vec + where + V: Serialize, + { + client + .write_notification("textDocument/didOpen", params) + .unwrap(); + + handle_configuration_request( + client, + json!([{ + "enable": true, + "codeLens": { + "test": true + } + }]), + ); + read_diagnostics(client).0 + } + + fn handle_configuration_request(client: &mut LspClient, result: Value) { + let (id, method, _) = client.read_request::().unwrap(); + assert_eq!(method, "workspace/configuration"); + client.write_response(id, result).unwrap(); + } + + fn read_diagnostics(client: &mut LspClient) -> CollectedDiagnostics { + // diagnostics come in batches of three unless they're cancelled + let mut diagnostics = vec![]; + for _ in 0..3 { + let (method, response) = client + .read_notification::() + .unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + diagnostics.push(response.unwrap()); + } + CollectedDiagnostics(diagnostics) + } + + fn shutdown(client: &mut LspClient) { + client + .write_request::<_, _, Value>("shutdown", json!(null)) + .unwrap(); + client.write_notification("exit", json!(null)).unwrap(); + } + + pub fn ensure_directory_specifier( + mut specifier: ModuleSpecifier, + ) -> ModuleSpecifier { + let path = specifier.path(); + if !path.ends_with('/') { + let new_path = format!("{}/", path); + specifier.set_path(&new_path); + } + specifier + } + + struct TestSession { + client: LspClient, + open_file_count: usize, + } + + impl TestSession { + pub fn from_file(init_path: &str) -> Self { + Self::from_client(init(init_path)) + } + + pub fn from_client(client: LspClient) -> Self { + Self { + client, + open_file_count: 0, + } + } + + pub fn did_open(&mut self, params: V) -> CollectedDiagnostics + where + V: Serialize, + { + self + .client + .write_notification("textDocument/didOpen", params) + .unwrap(); + + let (id, method, _) = self.client.read_request::().unwrap(); + assert_eq!(method, "workspace/configuration"); + self + .client + .write_response( + id, + json!([{ + "enable": true, + "codeLens": { + "test": true + } + }]), + ) + .unwrap(); + + self.open_file_count += 1; + self.read_diagnostics() + } + + pub fn read_diagnostics(&mut self) -> CollectedDiagnostics { + let mut all_diagnostics = Vec::new(); + for _ in 0..self.open_file_count { + all_diagnostics.extend(read_diagnostics(&mut self.client).0); + } + CollectedDiagnostics(all_diagnostics) + } + + pub fn shutdown_and_exit(&mut self) { + shutdown(&mut self.client); + } + } + + #[derive(Debug, Clone)] + struct CollectedDiagnostics(Vec); + + impl CollectedDiagnostics { + /// Gets the diagnostics that the editor will see after all the publishes. + pub fn viewed(&self) -> Vec { + self + .viewed_messages() + .into_iter() + .flat_map(|m| m.diagnostics) + .collect() + } + + /// Gets the messages that the editor will see after all the publishes. + pub fn viewed_messages(&self) -> Vec { + // go over the publishes in reverse order in order to get + // the final messages that will be shown in the editor + let mut messages = Vec::new(); + let mut had_specifier = HashSet::new(); + for message in self.0.iter().rev() { + if had_specifier.insert(message.uri.clone()) { + messages.insert(0, message.clone()); + } + } + messages + } + + pub fn with_source(&self, source: &str) -> lsp::PublishDiagnosticsParams { + self + .viewed_messages() + .iter() + .find(|p| { + p.diagnostics + .iter() + .any(|d| d.source == Some(source.to_string())) + }) + .map(ToOwned::to_owned) + .unwrap() + } + + pub fn with_file_and_source( + &self, + specifier: &str, + source: &str, + ) -> lsp::PublishDiagnosticsParams { + let specifier = ModuleSpecifier::parse(specifier).unwrap(); + self + .viewed_messages() + .iter() + .find(|p| { + p.uri == specifier + && p + .diagnostics + .iter() + .any(|d| d.source == Some(source.to_string())) + }) + .map(ToOwned::to_owned) + .unwrap() + } + } + + #[test] + fn lsp_startup_shutdown() { + let mut client = init("initialize_params.json"); + shutdown(&mut client); + } + + #[test] + fn lsp_init_tsconfig() { + let temp_dir = TempDir::new(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + let tsconfig = + serde_json::to_vec_pretty(&load_fixture("lib.tsconfig.json")).unwrap(); + fs::write(temp_dir.path().join("lib.tsconfig.json"), tsconfig).unwrap(); + + params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); + if let Some(Value::Object(mut map)) = params.initialization_options { + map.insert("config".to_string(), json!("./lib.tsconfig.json")); + params.initialization_options = Some(Value::Object(map)); + } + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + client.write_notification("initialized", json!({})).unwrap(); + + let diagnostics = did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "location.pathname;\n" + } + }), + ); + + let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); + assert_eq!(diagnostics.count(), 0); + + shutdown(&mut client); + } + + #[test] + fn lsp_tsconfig_types() { + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + let temp_dir = TempDir::new(); + let tsconfig = + serde_json::to_vec_pretty(&load_fixture("types.tsconfig.json")).unwrap(); + fs::write(temp_dir.path().join("types.tsconfig.json"), tsconfig).unwrap(); + let a_dts = load_fixture_str("a.d.ts"); + fs::write(temp_dir.path().join("a.d.ts"), a_dts).unwrap(); + + params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); + if let Some(Value::Object(mut map)) = params.initialization_options { + map.insert("config".to_string(), json!("./types.tsconfig.json")); + params.initialization_options = Some(Value::Object(map)); + } + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + client.write_notification("initialized", json!({})).unwrap(); + + let diagnostics = did_open( + &mut client, + json!({ + "textDocument": { + "uri": Url::from_file_path(temp_dir.path().join("test.ts")).unwrap(), + "languageId": "typescript", + "version": 1, + "text": "console.log(a);\n" + } + }), + ); + + let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); + assert_eq!(diagnostics.count(), 0); + + shutdown(&mut client); + } + + #[test] + fn lsp_tsconfig_bad_config_path() { + let mut client = init("initialize_params_bad_config_option.json"); + let (method, maybe_params) = client.read_notification().unwrap(); + assert_eq!(method, "window/showMessage"); + assert_eq!(maybe_params, Some(lsp::ShowMessageParams { + typ: lsp::MessageType::WARNING, + message: "The path to the configuration file (\"bad_tsconfig.json\") is not resolvable.".to_string() + })); + let diagnostics = did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Deno.args);\n" + } + }), + ); + let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); + assert_eq!(diagnostics.count(), 0); + } + + #[test] + fn lsp_triple_slash_types() { + let temp_dir = TempDir::new(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + let a_dts = load_fixture_str("a.d.ts"); + fs::write(temp_dir.path().join("a.d.ts"), a_dts).unwrap(); + + params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + client.write_notification("initialized", json!({})).unwrap(); + + let diagnostics = did_open( + &mut client, + json!({ + "textDocument": { + "uri": Url::from_file_path(temp_dir.path().join("test.ts")).unwrap(), + "languageId": "typescript", + "version": 1, + "text": "/// \n\nconsole.log(a);\n" + } + }), + ); + + let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); + assert_eq!(diagnostics.count(), 0); + + shutdown(&mut client); + } + + #[test] + fn lsp_import_map() { + let temp_dir = TempDir::new(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + let import_map = + serde_json::to_vec_pretty(&load_fixture("import-map.json")).unwrap(); + fs::write(temp_dir.path().join("import-map.json"), import_map).unwrap(); + fs::create_dir(temp_dir.path().join("lib")).unwrap(); + fs::write( + temp_dir.path().join("lib").join("b.ts"), + r#"export const b = "b";"#, + ) + .unwrap(); + + params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); + if let Some(Value::Object(mut map)) = params.initialization_options { + map.insert("importMap".to_string(), json!("import-map.json")); + params.initialization_options = Some(Value::Object(map)); + } + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + client.write_notification("initialized", json!({})).unwrap(); + let uri = Url::from_file_path(temp_dir.path().join("a.ts")).unwrap(); + + let diagnostics = did_open( + &mut client, + json!({ + "textDocument": { + "uri": uri, + "languageId": "typescript", + "version": 1, + "text": "import { b } from \"/~/b.ts\";\n\nconsole.log(b);\n" + } + }), + ); + + let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); + assert_eq!(diagnostics.count(), 0); + + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": uri + }, + "position": { + "line": 2, + "character": 12 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value":"(alias) const b: \"b\"\nimport b" + }, + "" + ], + "range": { + "start": { + "line": 2, + "character": 12 + }, + "end": { + "line": 2, + "character": 13 + } + } + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_import_map_data_url() { + let mut client = init("initialize_params_import_map.json"); + let diagnostics = did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import example from \"example\";\n" + } + }), + ); + + let mut diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); + // This indicates that the import map from initialize_params_import_map.json + // is applied correctly. + assert!(diagnostics.any(|diagnostic| diagnostic.code + == Some(lsp::NumberOrString::String("no-cache".to_string())) + && diagnostic + .message + .contains("https://deno.land/x/example/mod.ts"))); + shutdown(&mut client); + } + + #[test] + fn lsp_import_map_config_file() { + let temp_dir = TempDir::new(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + + let deno_import_map_jsonc = + serde_json::to_vec_pretty(&load_fixture("deno.import_map.jsonc")) + .unwrap(); + fs::write( + temp_dir.path().join("deno.import_map.jsonc"), + deno_import_map_jsonc, + ) + .unwrap(); + + params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); + if let Some(Value::Object(mut map)) = params.initialization_options { + map.insert("config".to_string(), json!("./deno.import_map.jsonc")); + params.initialization_options = Some(Value::Object(map)); + } + let import_map = + serde_json::to_vec_pretty(&load_fixture("import-map.json")).unwrap(); + fs::write(temp_dir.path().join("import-map.json"), import_map).unwrap(); + fs::create_dir(temp_dir.path().join("lib")).unwrap(); + fs::write( + temp_dir.path().join("lib").join("b.ts"), + r#"export const b = "b";"#, + ) + .unwrap(); + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + client.write_notification("initialized", json!({})).unwrap(); + let uri = Url::from_file_path(temp_dir.path().join("a.ts")).unwrap(); + + let diagnostics = did_open( + &mut client, + json!({ + "textDocument": { + "uri": uri, + "languageId": "typescript", + "version": 1, + "text": "import { b } from \"/~/b.ts\";\n\nconsole.log(b);\n" + } + }), + ); + + let diagnostics = diagnostics.into_iter().flat_map(|x| x.diagnostics); + assert_eq!(diagnostics.count(), 0); + + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": uri + }, + "position": { + "line": 2, + "character": 12 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value":"(alias) const b: \"b\"\nimport b" + }, + "" + ], + "range": { + "start": { + "line": 2, + "character": 12 + }, + "end": { + "line": 2, + "character": 13 + } + } + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_deno_task() { + let temp_dir = TempDir::new(); + let workspace_root = temp_dir.path().canonicalize().unwrap(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + fs::write( + workspace_root.join("deno.jsonc"), + r#"{ + "tasks": { + "build": "deno test", + "some:test": "deno bundle mod.ts" + } + }"#, + ) + .unwrap(); + + params.root_uri = Some(Url::from_file_path(workspace_root).unwrap()); + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>("deno/task", json!(null)) + .unwrap(); + + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!([ + { + "name": "build", + "detail": "deno test" + }, + { + "name": "some:test", + "detail": "deno bundle mod.ts" + } + ])) + ); + } + + #[test] + fn lsp_import_assertions() { + let mut client = init("initialize_params_import_map.json"); + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": "file:///a/test.json", + "languageId": "json", + "version": 1, + "text": "{\"a\":1}" + } + }), + ) + .unwrap(); + handle_configuration_request( + &mut client, + json!([{ + "enable": true, + "codeLens": { + "test": true + } + }]), + ); + + let diagnostics = CollectedDiagnostics(did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/a.ts", + "languageId": "typescript", + "version": 1, + "text": "import a from \"./test.json\";\n\nconsole.log(a);\n" + } + }), + )); + + assert_eq!( + json!( + diagnostics + .with_file_and_source("file:///a/a.ts", "deno") + .diagnostics + ), + json!([ + { + "range": { + "start": { + "line": 0, + "character": 14 + }, + "end": { + "line": 0, + "character": 27 + } + }, + "severity": 1, + "code": "no-assert-type", + "source": "deno", + "message": "The module is a JSON module and not being imported with an import assertion. Consider adding `assert { type: \"json\" }` to the import statement." + } + ]) + ); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeAction", + load_fixture("code_action_params_import_assertion.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_response_import_assertion.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_import_map_import_completions() { + let temp_dir = TempDir::new(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + let import_map = + serde_json::to_vec_pretty(&load_fixture("import-map-completions.json")) + .unwrap(); + fs::write(temp_dir.path().join("import-map.json"), import_map).unwrap(); + fs::create_dir(temp_dir.path().join("lib")).unwrap(); + fs::write( + temp_dir.path().join("lib").join("b.ts"), + r#"export const b = "b";"#, + ) + .unwrap(); + + params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); + if let Some(Value::Object(mut map)) = params.initialization_options { + map.insert("importMap".to_string(), json!("import-map.json")); + params.initialization_options = Some(Value::Object(map)); + } + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + client.write_notification("initialized", json!({})).unwrap(); + let uri = Url::from_file_path(temp_dir.path().join("a.ts")).unwrap(); + + did_open( + &mut client, + json!({ + "textDocument": { + "uri": uri, + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"/~/b.ts\";\nimport * as b from \"\"" + } + }), + ); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": uri + }, + "position": { + "line": 1, + "character": 20 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "\"" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "isIncomplete": false, + "items": [ + { + "label": ".", + "kind": 19, + "detail": "(local)", + "sortText": "1", + "insertText": ".", + "commitCharacters": ["\"", "'"], + }, + { + "label": "..", + "kind": 19, + "detail": "(local)", + "sortText": "1", + "insertText": "..", + "commitCharacters": ["\"", "'"], + }, + { + "label": "std", + "kind": 19, + "detail": "(import map)", + "sortText": "std", + "insertText": "std", + "commitCharacters": ["\"", "'"], + }, + { + "label": "fs", + "kind": 17, + "detail": "(import map)", + "sortText": "fs", + "insertText": "fs", + "commitCharacters": ["\"", "'"], + }, + { + "label": "/~", + "kind": 19, + "detail": "(import map)", + "sortText": "/~", + "insertText": "/~", + "commitCharacters": ["\"", "'"], + } + ] + })) + ); + + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": uri, + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 1, + "character": 20 + }, + "end": { + "line": 1, + "character": 20 + } + }, + "text": "/~/" + } + ] + }), + ) + .unwrap(); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": uri + }, + "position": { + "line": 1, + "character": 23 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "/" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "isIncomplete": false, + "items": [ + { + "label": "b.ts", + "kind": 9, + "detail": "(import map)", + "sortText": "1", + "filterText": "/~/b.ts", + "textEdit": { + "range": { + "start": { + "line": 1, + "character": 20 + }, + "end": { + "line": 1, + "character": 23 + } + }, + "newText": "/~/b.ts" + }, + "commitCharacters": ["\"", "'"], + } + ] + })) + ); + + shutdown(&mut client); + } + + #[test] + fn lsp_hover() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Deno.args);\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "const Deno.args: string[]" + }, + "Returns the script arguments to the program.\n\nGive the following command line invocation of Deno:\n\n```sh\ndeno run --allow-read https://deno.land/std/examples/cat.ts /etc/passwd\n```\n\nThen `Deno.args` will contain:\n\n```\n[ \"/etc/passwd\" ]\n```\n\nIf you are looking for a structured way to parse arguments, there is the\n[`std/flags`](https://deno.land/std/flags) module as part of the Deno\nstandard library.", + "\n\n*@category* - Runtime Environment", + ], + "range": { + "start": { + "line": 0, + "character": 17 + }, + "end": { + "line": 0, + "character": 21 + } + } + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_hover_asset() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + let (_, maybe_error) = client + .write_request::<_, _, Value>( + "textDocument/definition", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 14 + } + }), + ) + .unwrap(); + assert!(maybe_error.is_none()); + let (_, maybe_error) = client + .write_request::<_, _, Value>( + "deno/virtualTextDocument", + json!({ + "textDocument": { + "uri": "deno:/asset/lib.deno.shared_globals.d.ts" + } + }), + ) + .unwrap(); + assert!(maybe_error.is_none()); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "deno:/asset/lib.es2015.symbol.wellknown.d.ts" + }, + "position": { + "line": 109, + "character": 13 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "interface Date", + }, + "Enables basic storage and retrieval of dates and times." + ], + "range": { + "start": { + "line": 109, + "character": 10, + }, + "end": { + "line": 109, + "character": 14, + } + } + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_hover_disabled() { + let mut client = init("initialize_params_disabled.json"); + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ) + .unwrap(); + + handle_configuration_request(&mut client, json!([{ "enable": false }])); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + shutdown(&mut client); + } + + #[test] + fn lsp_inlay_hints() { + let mut client = init("initialize_params_hints.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": r#"function a(b: string) { + return b; + } + + a("foo"); + + enum C { + A, + } + + parseInt("123", 8); + + const d = Date.now(); + + class E { + f = Date.now(); + } + + ["a"].map((v) => v + v); + "# + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/inlayHint", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 19, + "character": 0, + } + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + json!(maybe_res), + json!([ + { + "position": { + "line": 0, + "character": 21 + }, + "label": ": string", + "kind": 1, + "paddingLeft": true + }, + { + "position": { + "line": 4, + "character": 10 + }, + "label": "b:", + "kind": 2, + "paddingRight": true + }, + { + "position": { + "line": 7, + "character": 11 + }, + "label": "= 0", + "paddingLeft": true + }, + { + "position": { + "line": 10, + "character": 17 + }, + "label": "string:", + "kind": 2, + "paddingRight": true + }, + { + "position": { + "line": 10, + "character": 24 + }, + "label": "radix:", + "kind": 2, + "paddingRight": true + }, + { + "position": { + "line": 12, + "character": 15 + }, + "label": ": number", + "kind": 1, + "paddingLeft": true + }, + { + "position": { + "line": 15, + "character": 11 + }, + "label": ": number", + "kind": 1, + "paddingLeft": true + }, + { + "position": { + "line": 18, + "character": 18 + }, + "label": "callbackfn:", + "kind": 2, + "paddingRight": true + }, + { + "position": { + "line": 18, + "character": 20 + }, + "label": ": string", + "kind": 1, + "paddingLeft": true + }, + { + "position": { + "line": 18, + "character": 21 + }, + "label": ": string", + "kind": 1, + "paddingLeft": true + } + ]) + ); + } + + #[test] + fn lsp_inlay_hints_not_enabled() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": r#"function a(b: string) { + return b; + } + + a("foo"); + + enum C { + A, + } + + parseInt("123", 8); + + const d = Date.now(); + + class E { + f = Date.now(); + } + + ["a"].map((v) => v + v); + "# + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/inlayHint", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 19, + "character": 0, + } + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(json!(maybe_res), json!(null)); + } + + #[test] + fn lsp_workspace_enable_paths() { + let mut params: lsp::InitializeParams = serde_json::from_value( + load_fixture("initialize_params_workspace_enable_paths.json"), + ) + .unwrap(); + // we aren't actually writing anything to the tempdir in this test, but we + // just need a legitimate file path on the host system so that logic that + // tries to convert to and from the fs paths works on all env + let temp_dir = TempDir::new(); + + let root_specifier = + ensure_directory_specifier(Url::from_file_path(temp_dir.path()).unwrap()); + + params.root_uri = Some(root_specifier.clone()); + params.workspace_folders = Some(vec![lsp::WorkspaceFolder { + uri: root_specifier.clone(), + name: "project".to_string(), + }]); + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + client.write_notification("initialized", json!({})).unwrap(); + + handle_configuration_request( + &mut client, + json!([{ + "enable": false, + "enablePaths": [ + "./worker" + ], + }]), + ); + + did_open( + &mut client, + json!({ + "textDocument": { + "uri": root_specifier.join("./file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + + did_open( + &mut client, + json!({ + "textDocument": { + "uri": root_specifier.join("./other/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + + did_open( + &mut client, + json!({ + "textDocument": { + "uri": root_specifier.join("./worker/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + + did_open( + &mut client, + json!({ + "textDocument": { + "uri": root_specifier.join("./worker/subdir/file.ts").unwrap(), + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": root_specifier.join("./file.ts").unwrap(), + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": root_specifier.join("./other/file.ts").unwrap(), + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": root_specifier.join("./worker/file.ts").unwrap(), + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "(method) DateConstructor.now(): number", + }, + "" + ], + "range": { + "start": { + "line": 0, + "character": 17, + }, + "end": { + "line": 0, + "character": 20, + } + } + })) + ); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": root_specifier.join("./worker/subdir/file.ts").unwrap(), + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "(method) DateConstructor.now(): number", + }, + "" + ], + "range": { + "start": { + "line": 0, + "character": 17, + }, + "end": { + "line": 0, + "character": 20, + } + } + })) + ); + + shutdown(&mut client); + } + + #[test] + fn lsp_hover_unstable_disabled() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Deno.dlopen);\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "any" + } + ], + "range": { + "start": { + "line": 0, + "character": 17 + }, + "end": { + "line": 0, + "character": 23 + } + } + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_hover_unstable_enabled() { + let mut client = init("initialize_params_unstable.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Deno.ppid);\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents":[ + { + "language":"typescript", + "value":"const Deno.ppid: number" + }, + "The process ID of parent process of this instance of the Deno CLI.\n\n```ts\nconsole.log(Deno.ppid);\n```", + "\n\n*@category* - Runtime Environment", + ], + "range":{ + "start":{ + "line":0, + "character":17 + }, + "end":{ + "line":0, + "character":21 + } + } + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_hover_change_mbc() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "const a = `编写软件很难`;\nconst b = `👍🦕😃`;\nconsole.log(a, b);\n" + } + }), + ); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 1, + "character": 11 + }, + "end": { + "line": 1, + // the LSP uses utf16 encoded characters indexes, so + // after the deno emoiji is character index 15 + "character": 15 + } + }, + "text": "" + } + ] + }), + ) + .unwrap(); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 2, + "character": 15 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "const b: \"😃\"", + }, + "", + ], + "range": { + "start": { + "line": 2, + "character": 15, + }, + "end": { + "line": 2, + "character": 16, + }, + } + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_hover_closed_document() { + let temp_dir_guard = TempDir::new(); + let temp_dir = temp_dir_guard.path(); + let a_path = temp_dir.join("a.ts"); + fs::write(a_path, r#"export const a = "a";"#).unwrap(); + let b_path = temp_dir.join("b.ts"); + fs::write(&b_path, r#"export * from "./a.ts";"#).unwrap(); + let b_specifier = Url::from_file_path(b_path).unwrap(); + let c_path = temp_dir.join("c.ts"); + fs::write(&c_path, "import { a } from \"./b.ts\";\nconsole.log(a);\n") + .unwrap(); + let c_specifier = Url::from_file_path(c_path).unwrap(); + + let mut client = init("initialize_params.json"); + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": b_specifier, + "languageId": "typescript", + "version": 1, + "text": r#"export * from "./a.ts";"# + } + }), + ) + .unwrap(); + let (id, method, _) = client.read_request::().unwrap(); + assert_eq!(method, "workspace/configuration"); + client + .write_response(id, json!([{ "enable": true }])) + .unwrap(); + + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": c_specifier, + "languageId": "typescript", + "version": 1, + "text": "import { a } from \"./b.ts\";\nconsole.log(a);\n", + } + }), + ) + .unwrap(); + let (id, method, _) = client.read_request::().unwrap(); + assert_eq!(method, "workspace/configuration"); + client + .write_response(id, json!([{ "enable": true }])) + .unwrap(); + + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": c_specifier, + }, + "position": { + "line": 0, + "character": 10 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "(alias) const a: \"a\"\nimport a" + }, + "" + ], + "range": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 10 + } + } + })) + ); + client + .write_notification( + "textDocument/didClose", + json!({ + "textDocument": { + "uri": b_specifier, + } + }), + ) + .unwrap(); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": c_specifier, + }, + "position": { + "line": 0, + "character": 10 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "(alias) const a: \"a\"\nimport a" + }, + "" + ], + "range": { + "start": { + "line": 0, + "character": 9 + }, + "end": { + "line": 0, + "character": 10 + } + } + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_hover_dependency() { + let _g = http_server(); + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file_01.ts", + "languageId": "typescript", + "version": 1, + "text": "export const a = \"a\";\n", + } + }), + ); + did_open( + &mut client, + load_fixture("did_open_params_import_hover.json"), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "deno/cache", + json!({ + "referrer": { + "uri": "file:///a/file.ts", + }, + "uris": [], + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "position": { + "line": 0, + "character": 28 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": { + "kind": "markdown", + "value": "**Resolved Dependency**\n\n**Code**: http​://127.0.0.1:4545/xTypeScriptTypes.js\n\n**Types**: http​://127.0.0.1:4545/xTypeScriptTypes.d.ts\n" + }, + "range": { + "start": { + "line": 0, + "character": 19 + }, + "end":{ + "line": 0, + "character": 62 + } + } + })) + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "position": { + "line": 3, + "character": 28 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": { + "kind": "markdown", + "value": "**Resolved Dependency**\n\n**Code**: http​://127.0.0.1:4545/subdir/type_reference.js\n\n**Types**: http​://127.0.0.1:4545/subdir/type_reference.d.ts\n" + }, + "range": { + "start": { + "line": 3, + "character": 19 + }, + "end":{ + "line": 3, + "character": 67 + } + } + })) + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "position": { + "line": 4, + "character": 28 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": { + "kind": "markdown", + "value": "**Resolved Dependency**\n\n**Code**: http​://127.0.0.1:4545/subdir/mod1.ts\n" + }, + "range": { + "start": { + "line": 4, + "character": 19 + }, + "end":{ + "line": 4, + "character": 57 + } + } + })) + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "position": { + "line": 5, + "character": 28 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": { + "kind": "markdown", + "value": "**Resolved Dependency**\n\n**Code**: _(a data url)_\n" + }, + "range": { + "start": { + "line": 5, + "character": 19 + }, + "end":{ + "line": 5, + "character": 132 + } + } + })) + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "position": { + "line": 6, + "character": 28 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": { + "kind": "markdown", + "value": "**Resolved Dependency**\n\n**Code**: file​:///a/file_01.ts\n" + }, + "range": { + "start": { + "line": 6, + "character": 19 + }, + "end":{ + "line": 6, + "character": 33 + } + } + })) + ); + } + + // This tests for a regression covered by denoland/deno#12753 where the lsp was + // unable to resolve dependencies when there was an invalid syntax in the module + #[test] + fn lsp_hover_deps_preserved_when_invalid_parse() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file1.ts", + "languageId": "typescript", + "version": 1, + "text": "export type Foo = { bar(): string };\n" + } + }), + ); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file2.ts", + "languageId": "typescript", + "version": 1, + "text": "import { Foo } from './file1.ts'; declare const f: Foo; f\n" + } + }), + ); + let (maybe_res, maybe_error) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file2.ts" + }, + "position": { + "line": 0, + "character": 56 + } + }), + ) + .unwrap(); + assert!(maybe_error.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "const f: Foo", + }, + "" + ], + "range": { + "start": { + "line": 0, + "character": 56, + }, + "end": { + "line": 0, + "character": 57, + } + } + })) + ); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file2.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 0, + "character": 57 + }, + "end": { + "line": 0, + "character": 58 + } + }, + "text": "." + } + ] + }), + ) + .unwrap(); + let (maybe_res, maybe_error) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file2.ts" + }, + "position": { + "line": 0, + "character": 56 + } + }), + ) + .unwrap(); + assert!(maybe_error.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "const f: Foo", + }, + "" + ], + "range": { + "start": { + "line": 0, + "character": 56, + }, + "end": { + "line": 0, + "character": 57, + } + } + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_hover_typescript_types() { + let _g = http_server(); + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"http://127.0.0.1:4545/xTypeScriptTypes.js\";\n\nconsole.log(a.foo);\n", + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "deno/cache", + json!({ + "referrer": { + "uri": "file:///a/file.ts", + }, + "uris": [ + { + "uri": "http://127.0.0.1:4545/xTypeScriptTypes.js", + } + ], + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 24 + } + }), + ) + .unwrap(); + assert!(maybe_res.is_some()); + assert!(maybe_err.is_none()); + assert_eq!( + json!(maybe_res.unwrap()), + json!({ + "contents": { + "kind": "markdown", + "value": "**Resolved Dependency**\n\n**Code**: http​://127.0.0.1:4545/xTypeScriptTypes.js\n\n**Types**: http​://127.0.0.1:4545/xTypeScriptTypes.d.ts\n" + }, + "range": { + "start": { + "line": 0, + "character": 19 + }, + "end": { + "line": 0, + "character": 62 + } + } + }) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_hover_jsdoc_symbol_link() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/b.ts", + "languageId": "typescript", + "version": 1, + "text": "export function hello() {}\n" + } + }), + ); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import { hello } from \"./b.ts\";\n\nhello();\n\nconst b = \"b\";\n\n/** JSDoc {@link hello} and {@linkcode b} */\nfunction a() {}\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 7, + "character": 10 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": [ + { + "language": "typescript", + "value": "function a(): void" + }, + "JSDoc [hello](file:///a/file.ts#L1,10) and [`b`](file:///a/file.ts#L5,7)" + ], + "range": { + "start": { + "line": 7, + "character": 9 + }, + "end": { + "line": 7, + "character": 10 + } + } + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_goto_type_definition() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "interface A {\n a: string;\n}\n\nexport class B implements A {\n a = \"a\";\n log() {\n console.log(this.a);\n }\n}\n\nconst b = new B();\nb;\n", + } + }), + ); + let (maybe_res, maybe_error) = client + .write_request::<_, _, Value>( + "textDocument/typeDefinition", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 12, + "character": 1 + } + }), + ) + .unwrap(); + assert!(maybe_error.is_none()); + assert_eq!( + maybe_res, + Some(json!([ + { + "targetUri": "file:///a/file.ts", + "targetRange": { + "start": { + "line": 4, + "character": 0 + }, + "end": { + "line": 9, + "character": 1 + } + }, + "targetSelectionRange": { + "start": { + "line": 4, + "character": 13 + }, + "end": { + "line": 4, + "character": 14 + } + } + } + ])) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_call_hierarchy() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "function foo() {\n return false;\n}\n\nclass Bar {\n baz() {\n return foo();\n }\n}\n\nfunction main() {\n const bar = new Bar();\n bar.baz();\n}\n\nmain();" + } + }), + ); + let (maybe_res, maybe_error) = client + .write_request( + "textDocument/prepareCallHierarchy", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 5, + "character": 3 + } + }), + ) + .unwrap(); + assert!(maybe_error.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("prepare_call_hierarchy_response.json")) + ); + let (maybe_res, maybe_error) = client + .write_request( + "callHierarchy/incomingCalls", + load_fixture("incoming_calls_params.json"), + ) + .unwrap(); + assert!(maybe_error.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("incoming_calls_response.json")) + ); + let (maybe_res, maybe_error) = client + .write_request( + "callHierarchy/outgoingCalls", + load_fixture("outgoing_calls_params.json"), + ) + .unwrap(); + assert!(maybe_error.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("outgoing_calls_response.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_large_doc_changes() { + let mut client = init("initialize_params.json"); + did_open(&mut client, load_fixture("did_open_params_large.json")); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 444, + "character": 11 + }, + "end": { + "line": 444, + "character": 14 + } + }, + "text": "+++" + } + ] + }), + ) + .unwrap(); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 445, + "character": 4 + }, + "end": { + "line": 445, + "character": 4 + } + }, + "text": "// " + } + ] + }), + ) + .unwrap(); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 477, + "character": 4 + }, + "end": { + "line": 477, + "character": 9 + } + }, + "text": "error" + } + ] + }), + ) + .unwrap(); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 421, + "character": 30 + } + }), + ) + .unwrap(); + assert!(maybe_res.is_some()); + assert!(maybe_err.is_none()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 444, + "character": 6 + } + }), + ) + .unwrap(); + assert!(maybe_res.is_some()); + assert!(maybe_err.is_none()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 461, + "character": 34 + } + }), + ) + .unwrap(); + assert!(maybe_res.is_some()); + assert!(maybe_err.is_none()); + shutdown(&mut client); + + assert!(client.duration().as_millis() <= 15000); + } + + #[test] + fn lsp_document_symbol() { + let mut client = init("initialize_params.json"); + did_open(&mut client, load_fixture("did_open_params_doc_symbol.json")); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/documentSymbol", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("document_symbol_response.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_folding_range() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "// #region 1\n/*\n * Some comment\n */\nclass Foo {\n bar(a, b) {\n if (a === b) {\n return true;\n }\n return false;\n }\n}\n// #endregion" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/foldingRange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!([ + { + "startLine": 0, + "endLine": 12, + "kind": "region" + }, + { + "startLine": 1, + "endLine": 3, + "kind": "comment" + }, + { + "startLine": 4, + "endLine": 10 + }, + { + "startLine": 5, + "endLine": 9 + }, + { + "startLine": 6, + "endLine": 7 + } + ])) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_rename() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + // this should not rename in comments and strings + "text": "let variable = 'a'; // variable\nconsole.log(variable);\n\"variable\";\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/rename", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 4 + }, + "newName": "variable_modified" + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(load_fixture("rename_response.json"))); + shutdown(&mut client); + } + + #[test] + fn lsp_selection_range() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "class Foo {\n bar(a, b) {\n if (a === b) {\n return true;\n }\n return false;\n }\n}" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/selectionRange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "positions": [ + { + "line": 2, + "character": 8 + } + ] + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("selection_range_response.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_semantic_tokens() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + load_fixture("did_open_params_semantic_tokens.json"), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/semanticTokens/full", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "data": [ + 0, 5, 6, 1, 1, 0, 9, 6, 8, 9, 0, 8, 6, 8, 9, 2, 15, 3, 10, 5, 0, 4, 1, + 6, 1, 0, 12, 7, 2, 16, 1, 8, 1, 7, 41, 0, 4, 1, 6, 0, 0, 2, 5, 11, 16, + 1, 9, 1, 7, 40, 3, 10, 4, 2, 1, 1, 11, 1, 9, 9, 1, 2, 3, 11, 1, 3, 6, 3, + 0, 1, 0, 15, 4, 2, 0, 1, 30, 1, 6, 9, 1, 2, 3, 11,1, 1, 9, 9, 9, 3, 0, + 16, 3, 0, 0, 1, 17, 12, 11, 3, 0, 24, 3, 0, 0, 0, 4, 9, 9, 2 + ] + })) + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/semanticTokens/range", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 6, + "character": 0 + } + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "data": [ + 0, 5, 6, 1, 1, 0, 9, 6, 8, 9, 0, 8, 6, 8, 9, 2, 15, 3, 10, 5, 0, 4, 1, + 6, 1, 0, 12, 7, 2, 16, 1, 8, 1, 7, 41, 0, 4, 1, 6, 0, 0, 2, 5, 11, 16, + 1, 9, 1, 7, 40 + ] + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_code_lens() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "class A {\n a = \"a\";\n\n b() {\n console.log(this.a);\n }\n\n c() {\n this.a = \"c\";\n }\n}\n\nconst a = new A();\na.b();\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeLens", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(load_fixture("code_lens_response.json"))); + let (maybe_res, maybe_err) = client + .write_request( + "codeLens/resolve", + json!({ + "range": { + "start": { + "line": 0, + "character": 6 + }, + "end": { + "line": 0, + "character": 7 + } + }, + "data": { + "specifier": "file:///a/file.ts", + "source": "references" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_lens_resolve_response.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_code_lens_impl() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "interface A {\n b(): void;\n}\n\nclass B implements A {\n b() {\n console.log(\"b\");\n }\n}\n\ninterface C {\n c: string;\n}\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeLens", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_lens_response_impl.json")) + ); + let (maybe_res, maybe_err) = client + .write_request( + "codeLens/resolve", + json!({ + "range": { + "start": { + "line": 0, + "character": 10 + }, + "end": { + "line": 0, + "character": 11 + } + }, + "data": { + "specifier": "file:///a/file.ts", + "source": "implementations" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_lens_resolve_response_impl.json")) + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "codeLens/resolve", + json!({ + "range": { + "start": { + "line": 10, + "character": 10 + }, + "end": { + "line": 10, + "character": 11 + } + }, + "data": { + "specifier": "file:///a/file.ts", + "source": "implementations" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "range": { + "start": { + "line": 10, + "character": 10 + }, + "end": { + "line": 10, + "character": 11 + } + }, + "command": { + "title": "0 implementations", + "command": "" + } + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_code_lens_test() { + let mut client = init("initialize_params_code_lens_test.json"); + did_open( + &mut client, + load_fixture("did_open_params_test_code_lens.json"), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeLens", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_lens_response_test.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_code_lens_test_disabled() { + let mut client = init("initialize_params_code_lens_test_disabled.json"); + client + .write_notification( + "textDocument/didOpen", + load_fixture("did_open_params_test_code_lens.json"), + ) + .unwrap(); + + let (id, method, _) = client.read_request::().unwrap(); + assert_eq!(method, "workspace/configuration"); + client + .write_response( + id, + json!([{ + "enable": true, + "codeLens": { + "test": false + } + }]), + ) + .unwrap(); + + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (method, _) = client.read_notification::().unwrap(); + assert_eq!(method, "textDocument/publishDiagnostics"); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeLens", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!([]))); + shutdown(&mut client); + } + + #[test] + fn lsp_code_lens_non_doc_nav_tree() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Date.now());\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/references", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 3 + }, + "context": { + "includeDeclaration": true + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "deno/virtualTextDocument", + json!({ + "textDocument": { + "uri": "deno:/asset/lib.deno.shared_globals.d.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Vec>( + "textDocument/codeLens", + json!({ + "textDocument": { + "uri": "deno:/asset/lib.deno.shared_globals.d.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let res = maybe_res.unwrap(); + assert!(res.len() > 50); + let (maybe_res, maybe_err) = client + .write_request::<_, _, lsp::CodeLens>( + "codeLens/resolve", + json!({ + "range": { + "start": { + "line": 416, + "character": 12 + }, + "end": { + "line": 416, + "character": 19 + } + }, + "data": { + "specifier": "asset:///lib.deno.shared_globals.d.ts", + "source": "references" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + shutdown(&mut client); + } + + #[test] + fn lsp_nav_tree_updates() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "interface A {\n b(): void;\n}\n\nclass B implements A {\n b() {\n console.log(\"b\");\n }\n}\n\ninterface C {\n c: string;\n}\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeLens", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_lens_response_impl.json")) + ); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 10, + "character": 0 + }, + "end": { + "line": 13, + "character": 0 + } + }, + "text": "" + } + ] + }), + ) + .unwrap(); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeLens", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_lens_response_changed.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_signature_help() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "/**\n * Adds two numbers.\n * @param a This is a first number.\n * @param b This is a second number.\n */\nfunction add(a: number, b: number) {\n return a + b;\n}\n\nadd(" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/signatureHelp", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "character": 4, + "line": 9 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "(", + "isRetrigger": false + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "signatures": [ + { + "label": "add(a: number, b: number): number", + "documentation": { + "kind": "markdown", + "value": "Adds two numbers." + }, + "parameters": [ + { + "label": "a: number", + "documentation": { + "kind": "markdown", + "value": "This is a first number." + } + }, + { + "label": "b: number", + "documentation": { + "kind": "markdown", + "value": "This is a second number." + } + } + ] + } + ], + "activeSignature": 0, + "activeParameter": 0 + })) + ); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 9, + "character": 4 + }, + "end": { + "line": 9, + "character": 4 + } + }, + "text": "123, " + } + ] + }), + ) + .unwrap(); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/signatureHelp", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "character": 8, + "line": 9 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "signatures": [ + { + "label": "add(a: number, b: number): number", + "documentation": { + "kind": "markdown", + "value": "Adds two numbers." + }, + "parameters": [ + { + "label": "a: number", + "documentation": { + "kind": "markdown", + "value": "This is a first number." + } + }, + { + "label": "b: number", + "documentation": { + "kind": "markdown", + "value": "This is a second number." + } + } + ] + } + ], + "activeSignature": 0, + "activeParameter": 1 + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_code_actions() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "export function a(): void {\n await Promise.resolve(\"a\");\n}\n\nexport function b(): void {\n await Promise.resolve(\"b\");\n}\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeAction", + load_fixture("code_action_params.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(load_fixture("code_action_response.json"))); + let (maybe_res, maybe_err) = client + .write_request( + "codeAction/resolve", + load_fixture("code_action_resolve_params.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_resolve_response.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_code_actions_deno_cache() { + let mut session = TestSession::from_file("initialize_params.json"); + let diagnostics = session.did_open(json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"https://deno.land/x/a/mod.ts\";\n\nconsole.log(a);\n" + } + })); + assert_eq!( + diagnostics.with_source("deno"), + load_fixture_as("diagnostics_deno_deps.json") + ); + + let (maybe_res, maybe_err) = session + .client + .write_request( + "textDocument/codeAction", + load_fixture("code_action_params_cache.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_response_cache.json")) + ); + session.shutdown_and_exit(); + } + + #[test] + fn lsp_code_actions_deno_cache_npm() { + let mut session = TestSession::from_file("initialize_params.json"); + let diagnostics = session.did_open(json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import chalk from \"npm:chalk\";\n\nconsole.log(chalk.green);\n" + } + })); + assert_eq!( + diagnostics.with_source("deno"), + load_fixture_as("code_actions/cache_npm/diagnostics.json") + ); + + let (maybe_res, maybe_err) = session + .client + .write_request( + "textDocument/codeAction", + load_fixture("code_actions/cache_npm/cache_action.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_actions/cache_npm/cache_response.json")) + ); + session.shutdown_and_exit(); + } + + #[test] + fn lsp_code_actions_imports() { + let mut session = TestSession::from_file("initialize_params.json"); + session.did_open(json!({ + "textDocument": { + "uri": "file:///a/file00.ts", + "languageId": "typescript", + "version": 1, + "text": "export const abc = \"abc\";\nexport const def = \"def\";\n" + } + })); + session.did_open(json!({ + "textDocument": { + "uri": "file:///a/file01.ts", + "languageId": "typescript", + "version": 1, + "text": "\nconsole.log(abc);\nconsole.log(def)\n" + } + })); + + let (maybe_res, maybe_err) = session + .client + .write_request( + "textDocument/codeAction", + load_fixture("code_action_params_imports.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_response_imports.json")) + ); + let (maybe_res, maybe_err) = session + .client + .write_request( + "codeAction/resolve", + load_fixture("code_action_resolve_params_imports.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_resolve_response_imports.json")) + ); + + session.shutdown_and_exit(); + } + + #[test] + fn lsp_code_actions_refactor() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "var x: { a?: number; b?: string } = {};\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeAction", + load_fixture("code_action_params_refactor.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_response_refactor.json")) + ); + let (maybe_res, maybe_err) = client + .write_request( + "codeAction/resolve", + load_fixture("code_action_resolve_params_refactor.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_resolve_response_refactor.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_code_actions_refactor_no_disabled_support() { + let mut client = init("initialize_params_ca_no_disabled.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "interface A {\n a: string;\n}\n\ninterface B {\n b: string;\n}\n\nclass AB implements A, B {\n a = \"a\";\n b = \"b\";\n}\n\nnew AB().a;\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeAction", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 14, + "character": 0 + } + }, + "context": { + "diagnostics": [], + "only": [ + "refactor" + ] + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_response_no_disabled.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_code_actions_deadlock() { + let mut client = init("initialize_params.json"); + client + .write_notification( + "textDocument/didOpen", + load_fixture("did_open_params_large.json"), + ) + .unwrap(); + let (id, method, _) = client.read_request::().unwrap(); + assert_eq!(method, "workspace/configuration"); + client + .write_response(id, json!([{ "enable": true }])) + .unwrap(); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/semanticTokens/full", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + read_diagnostics(&mut client); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 444, + "character": 11 + }, + "end": { + "line": 444, + "character": 14 + } + }, + "text": "+++" + } + ] + }), + ) + .unwrap(); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 445, + "character": 4 + }, + "end": { + "line": 445, + "character": 4 + } + }, + "text": "// " + } + ] + }), + ) + .unwrap(); + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 477, + "character": 4 + }, + "end": { + "line": 477, + "character": 9 + } + }, + "text": "error" + } + ] + }), + ) + .unwrap(); + // diagnostics only trigger after changes have elapsed in a separate thread, + // so we need to delay the next messages a little bit to attempt to create a + // potential for a deadlock with the codeAction + std::thread::sleep(std::time::Duration::from_millis(50)); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "position": { + "line": 609, + "character": 33, + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/codeAction", + load_fixture("code_action_params_deadlock.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + + read_diagnostics(&mut client); + + shutdown(&mut client); + } + + #[test] + fn lsp_completions() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "Deno." + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 5 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "." + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(lsp::CompletionResponse::List(list)) = maybe_res { + assert!(!list.is_incomplete); + assert!(list.items.len() > 90); + } else { + panic!("unexpected response"); + } + let (maybe_res, maybe_err) = client + .write_request( + "completionItem/resolve", + load_fixture("completion_resolve_params.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("completion_resolve_response.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_completions_optional() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "interface A {\n b?: string;\n}\n\nconst o: A = {};\n\nfunction c(s: string) {}\n\nc(o.)" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + load_fixture("completion_request_params_optional.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "isIncomplete": false, + "items": [ + { + "label": "b?", + "kind": 5, + "sortText": "11", + "filterText": "b", + "insertText": "b", + "commitCharacters": [".", ",", ";", "("], + "data": { + "tsc": { + "specifier": "file:///a/file.ts", + "position": 79, + "name": "b", + "useCodeSnippet": false + } + } + } + ] + })) + ); + let (maybe_res, maybe_err) = client + .write_request( + "completionItem/resolve", + load_fixture("completion_resolve_params_optional.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "label": "b?", + "kind": 5, + "detail": "(property) A.b?: string | undefined", + "documentation": { + "kind": "markdown", + "value": "" + }, + "sortText": "1", + "filterText": "b", + "insertText": "b" + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_completions_auto_import() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/b.ts", + "languageId": "typescript", + "version": 1, + "text": "export const foo = \"foo\";\n", + } + }), + ); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "export {};\n\n", + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 2, + "character": 0, + }, + "context": { + "triggerKind": 1, + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(lsp::CompletionResponse::List(list)) = maybe_res { + assert!(!list.is_incomplete); + if !list.items.iter().any(|item| item.label == "foo") { + panic!("completions items missing 'foo' symbol"); + } + } else { + panic!("unexpected completion response"); + } + let (maybe_res, maybe_err) = client + .write_request( + "completionItem/resolve", + json!({ + "label": "foo", + "kind": 6, + "sortText": "￿16", + "commitCharacters": [ + ".", + ",", + ";", + "(" + ], + "data": { + "tsc": { + "specifier": "file:///a/file.ts", + "position": 12, + "name": "foo", + "source": "./b", + "data": { + "exportName": "foo", + "moduleSpecifier": "./b", + "fileName": "file:///a/b.ts" + }, + "useCodeSnippet": false + } + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "label": "foo", + "kind": 6, + "detail": "const foo: \"foo\"", + "documentation": { + "kind": "markdown", + "value": "" + }, + "sortText": "￿16", + "additionalTextEdits": [ + { + "range": { + "start": { + "line": 0, + "character": 0 + }, + "end": { + "line": 0, + "character": 0 + } + }, + "newText": "import { foo } from \"./b.ts\";\n\n" + } + ], + "commitCharacters": [ + ".", + ",", + ";", + "(" + ] + })) + ); + } + + #[test] + fn lsp_completions_snippet() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/a.tsx", + "languageId": "typescriptreact", + "version": 1, + "text": "function A({ type }: { type: string }) {\n return type;\n}\n\nfunction B() {\n return ( + "deno/cache", + json!({ + "referrer": { + "uri": "file:///a/file.ts", + }, + "uris": [ + { + "uri": "npm:@denotest/cjs-default-export", + }, + { + "uri": "npm:chalk", + } + ] + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + + // check importing a cjs default import + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 2, + "character": 0 + }, + "end": { + "line": 2, + "character": 0 + } + }, + "text": "cjsDefault." + } + ] + }), + ) + .unwrap(); + read_diagnostics(&mut client); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 2, + "character": 11 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "." + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(lsp::CompletionResponse::List(list)) = maybe_res { + assert!(!list.is_incomplete); + assert_eq!(list.items.len(), 3); + assert!(list.items.iter().any(|i| i.label == "default")); + assert!(list.items.iter().any(|i| i.label == "MyClass")); + } else { + panic!("unexpected response"); + } + let (maybe_res, maybe_err) = client + .write_request( + "completionItem/resolve", + load_fixture("completions/npm/resolve_params.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("completions/npm/resolve_response.json")) + ); + + // now check chalk, which is esm + client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "version": 3 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 2, + "character": 0 + }, + "end": { + "line": 2, + "character": 11 + } + }, + "text": "chalk." + } + ] + }), + ) + .unwrap(); + read_diagnostics(&mut client); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 2, + "character": 6 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "." + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(lsp::CompletionResponse::List(list)) = maybe_res { + assert!(!list.is_incomplete); + assert!(list.items.iter().any(|i| i.label == "green")); + assert!(list.items.iter().any(|i| i.label == "red")); + } else { + panic!("unexpected response"); + } + + shutdown(&mut client); + } + + #[test] + fn lsp_completions_registry() { + let _g = http_server(); + let mut client = init("initialize_params_registry.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"http://localhost:4545/x/a@\"" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 46 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "@" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(lsp::CompletionResponse::List(list)) = maybe_res { + assert!(!list.is_incomplete); + assert_eq!(list.items.len(), 3); + } else { + panic!("unexpected response"); + } + let (maybe_res, maybe_err) = client + .write_request( + "completionItem/resolve", + load_fixture("completion_resolve_params_registry.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("completion_resolve_response_registry.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_completions_registry_empty() { + let _g = http_server(); + let mut client = init("initialize_params_registry.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"\"" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 20 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "\"" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("completion_request_response_empty.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_auto_discover_registry() { + let _g = http_server(); + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"http://localhost:4545/x/a@\"" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 46 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "@" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (method, maybe_res) = client.read_notification().unwrap(); + assert_eq!(method, "deno/registryState"); + assert_eq!( + maybe_res, + Some(json!({ + "origin": "http://localhost:4545", + "suggestions": true, + })) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_cache_location() { + let _g = http_server(); + let temp_dir = TempDir::new(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params_registry.json")) + .unwrap(); + + params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); + if let Some(Value::Object(mut map)) = params.initialization_options { + map.insert("cache".to_string(), json!(".cache")); + params.initialization_options = Some(Value::Object(map)); + } + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + client.write_notification("initialized", json!({})).unwrap(); + let mut session = TestSession::from_client(client); + + session.did_open(json!({ + "textDocument": { + "uri": "file:///a/file_01.ts", + "languageId": "typescript", + "version": 1, + "text": "export const a = \"a\";\n", + } + })); + let diagnostics = + session.did_open(load_fixture("did_open_params_import_hover.json")); + assert_eq!(diagnostics.viewed().len(), 7); + let (maybe_res, maybe_err) = session + .client + .write_request::<_, _, Value>( + "deno/cache", + json!({ + "referrer": { + "uri": "file:///a/file.ts", + }, + "uris": [], + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = session + .client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "position": { + "line": 0, + "character": 28 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": { + "kind": "markdown", + "value": "**Resolved Dependency**\n\n**Code**: http​://127.0.0.1:4545/xTypeScriptTypes.js\n\n**Types**: http​://127.0.0.1:4545/xTypeScriptTypes.d.ts\n" + }, + "range": { + "start": { + "line": 0, + "character": 19 + }, + "end":{ + "line": 0, + "character": 62 + } + } + })) + ); + let (maybe_res, maybe_err) = session + .client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "position": { + "line": 7, + "character": 28 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": { + "kind": "markdown", + "value": "**Resolved Dependency**\n\n**Code**: http​://localhost:4545/x/a/mod.ts\n\n\n---\n\n**a**\n\nmod.ts" + }, + "range": { + "start": { + "line": 7, + "character": 19 + }, + "end": { + "line": 7, + "character": 53 + } + } + })) + ); + let cache_path = temp_dir.path().join(".cache"); + assert!(cache_path.is_dir()); + assert!(cache_path.join("gen").is_dir()); + session.shutdown_and_exit(); + } + + /// Sets the TLS root certificate on startup, which allows the LSP to connect to + /// the custom signed test server and be able to retrieve the registry config + /// and cache files. + #[test] + fn lsp_tls_cert() { + let _g = http_server(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params_tls_cert.json")) + .unwrap(); + + params.root_uri = Some(Url::from_file_path(testdata_path()).unwrap()); + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + client.write_notification("initialized", json!({})).unwrap(); + let mut session = TestSession::from_client(client); + + session.did_open(json!({ + "textDocument": { + "uri": "file:///a/file_01.ts", + "languageId": "typescript", + "version": 1, + "text": "export const a = \"a\";\n", + } + })); + let diagnostics = + session.did_open(load_fixture("did_open_params_tls_cert.json")); + let diagnostics = diagnostics.viewed(); + assert_eq!(diagnostics.len(), 7); + let (maybe_res, maybe_err) = session + .client + .write_request::<_, _, Value>( + "deno/cache", + json!({ + "referrer": { + "uri": "file:///a/file.ts", + }, + "uris": [], + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = session + .client + .write_request( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "position": { + "line": 0, + "character": 28 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": { + "kind": "markdown", + "value": "**Resolved Dependency**\n\n**Code**: https​://localhost:5545/xTypeScriptTypes.js\n" + }, + "range": { + "start": { + "line": 0, + "character": 19 + }, + "end":{ + "line": 0, + "character": 63 + } + } + })) + ); + let (maybe_res, maybe_err) = session + .client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + }, + "position": { + "line": 7, + "character": 28 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": { + "kind": "markdown", + "value": "**Resolved Dependency**\n\n**Code**: http​://localhost:4545/x/a/mod.ts\n\n\n---\n\n**a**\n\nmod.ts" + }, + "range": { + "start": { + "line": 7, + "character": 19 + }, + "end": { + "line": 7, + "character": 53 + } + } + })) + ); + session.shutdown_and_exit(); + } + + #[test] + fn lsp_diagnostics_warn_redirect() { + let _g = http_server(); + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"http://127.0.0.1:4545/x_deno_warning.js\";\n\nconsole.log(a)\n", + }, + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "deno/cache", + json!({ + "referrer": { + "uri": "file:///a/file.ts", + }, + "uris": [ + { + "uri": "http://127.0.0.1:4545/x_deno_warning.js", + } + ], + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let diagnostics = read_diagnostics(&mut client); + assert_eq!( + diagnostics.with_source("deno"), + lsp::PublishDiagnosticsParams { + uri: Url::parse("file:///a/file.ts").unwrap(), + diagnostics: vec![ + lsp::Diagnostic { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 19 + }, + end: lsp::Position { + line: 0, + character: 60 + } + }, + severity: Some(lsp::DiagnosticSeverity::WARNING), + code: Some(lsp::NumberOrString::String("deno-warn".to_string())), + source: Some("deno".to_string()), + message: "foobar".to_string(), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range { + start: lsp::Position { + line: 0, + character: 19 + }, + end: lsp::Position { + line: 0, + character: 60 + } + }, + severity: Some(lsp::DiagnosticSeverity::INFORMATION), + code: Some(lsp::NumberOrString::String("redirect".to_string())), + source: Some("deno".to_string()), + message: "The import of \"http://127.0.0.1:4545/x_deno_warning.js\" was redirected to \"http://127.0.0.1:4545/lsp/x_deno_warning_redirect.js\".".to_string(), + data: Some(json!({"specifier": "http://127.0.0.1:4545/x_deno_warning.js", "redirect": "http://127.0.0.1:4545/lsp/x_deno_warning_redirect.js"})), + ..Default::default() + } + ], + version: Some(1), + } + ); + shutdown(&mut client); + } + + #[test] + fn lsp_redirect_quick_fix() { + let _g = http_server(); + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"http://127.0.0.1:4545/x_deno_warning.js\";\n\nconsole.log(a)\n", + }, + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "deno/cache", + json!({ + "referrer": { + "uri": "file:///a/file.ts", + }, + "uris": [ + { + "uri": "http://127.0.0.1:4545/x_deno_warning.js", + } + ], + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let diagnostics = read_diagnostics(&mut client) + .with_source("deno") + .diagnostics; + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeAction", + json!(json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "range": { + "start": { + "line": 0, + "character": 19 + }, + "end": { + "line": 0, + "character": 60 + } + }, + "context": { + "diagnostics": diagnostics, + "only": [ + "quickfix" + ] + } + })), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_redirect_response.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_diagnostics_deprecated() { + let mut client = init("initialize_params.json"); + let diagnostics = did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "/** @deprecated */\nexport const a = \"a\";\n\na;\n", + }, + }), + ); + assert_eq!( + json!(diagnostics), + json!([ + { + "uri": "file:///a/file.ts", + "diagnostics": [], + "version": 1 + }, + { + "uri": "file:///a/file.ts", + "diagnostics": [], + "version": 1 + }, + { + "uri": "file:///a/file.ts", + "diagnostics": [ + { + "range": { + "start": { + "line": 3, + "character": 0 + }, + "end": { + "line": 3, + "character": 1 + } + }, + "severity": 4, + "code": 6385, + "source": "deno-ts", + "message": "'a' is deprecated.", + "relatedInformation": [], + "tags": [ + 2 + ] + } + ], + "version": 1 + } + ]) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_diagnostics_deno_types() { + let mut client = init("initialize_params.json"); + client + .write_notification( + "textDocument/didOpen", + load_fixture("did_open_params_deno_types.json"), + ) + .unwrap(); + let (id, method, _) = client.read_request::().unwrap(); + assert_eq!(method, "workspace/configuration"); + client + .write_response(id, json!([{ "enable": true }])) + .unwrap(); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/documentSymbol", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + } + }), + ) + .unwrap(); + assert!(maybe_res.is_some()); + assert!(maybe_err.is_none()); + let diagnostics = read_diagnostics(&mut client); + assert_eq!(diagnostics.viewed().len(), 5); + shutdown(&mut client); + } + + #[test] + fn lsp_diagnostics_refresh_dependents() { + let mut session = TestSession::from_file("initialize_params.json"); + session.did_open(json!({ + "textDocument": { + "uri": "file:///a/file_00.ts", + "languageId": "typescript", + "version": 1, + "text": "export const a = \"a\";\n", + }, + })); + session.did_open(json!({ + "textDocument": { + "uri": "file:///a/file_01.ts", + "languageId": "typescript", + "version": 1, + "text": "export * from \"./file_00.ts\";\n", + }, + })); + let diagnostics = session.did_open(json!({ + "textDocument": { + "uri": "file:///a/file_02.ts", + "languageId": "typescript", + "version": 1, + "text": "import { a, b } from \"./file_01.ts\";\n\nconsole.log(a, b);\n" + } + })); + assert_eq!( + json!(diagnostics.with_file_and_source("file:///a/file_02.ts", "deno-ts")), + json!({ + "uri": "file:///a/file_02.ts", + "diagnostics": [ + { + "range": { + "start": { + "line": 0, + "character": 12 + }, + "end": { + "line": 0, + "character": 13 + } + }, + "severity": 1, + "code": 2305, + "source": "deno-ts", + "message": "Module '\"./file_01.ts\"' has no exported member 'b'." + } + ], + "version": 1 + }) + ); + + // fix the code causing the diagnostic + session + .client + .write_notification( + "textDocument/didChange", + json!({ + "textDocument": { + "uri": "file:///a/file_00.ts", + "version": 2 + }, + "contentChanges": [ + { + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 0 + } + }, + "text": "export const b = \"b\";\n" + } + ] + }), + ) + .unwrap(); + let diagnostics = session.read_diagnostics(); + assert_eq!(diagnostics.viewed().len(), 0); // no diagnostics now + + session.shutdown_and_exit(); + assert_eq!(session.client.queue_len(), 0); + } + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + pub struct PerformanceAverage { + pub name: String, + pub count: u32, + pub average_duration: u32, + } + + #[derive(Deserialize)] + #[serde(rename_all = "camelCase")] + struct PerformanceAverages { + averages: Vec, + } + + #[test] + fn lsp_performance() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console.log(Deno.args);\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 19 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, PerformanceAverages>( + "deno/performance", + json!(null), + ) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(res) = maybe_res { + assert_eq!(res.averages.len(), 13); + } else { + panic!("unexpected result"); + } + shutdown(&mut client); + } + + #[test] + fn lsp_format_no_changes() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console;\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/formatting", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "options": { + "tabSize": 2, + "insertSpaces": true + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + client.assert_no_notification("window/showMessage"); + shutdown(&mut client); + } + + #[test] + fn lsp_format_error() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "console test test\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/formatting", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "options": { + "tabSize": 2, + "insertSpaces": true + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + shutdown(&mut client); + } + + #[test] + fn lsp_format_mbc() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "const bar = '👍🇺🇸😃'\nconsole.log('hello deno')\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/formatting", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "options": { + "tabSize": 2, + "insertSpaces": true + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!(load_fixture("formatting_mbc_response.json"))) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_format_exclude_with_config() { + let temp_dir = TempDir::new(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + let deno_fmt_jsonc = + serde_json::to_vec_pretty(&load_fixture("deno.fmt.exclude.jsonc")) + .unwrap(); + fs::write(temp_dir.path().join("deno.fmt.jsonc"), deno_fmt_jsonc).unwrap(); + + params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); + if let Some(Value::Object(mut map)) = params.initialization_options { + map.insert("config".to_string(), json!("./deno.fmt.jsonc")); + params.initialization_options = Some(Value::Object(map)); + } + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + let file_uri = + ModuleSpecifier::from_file_path(temp_dir.path().join("ignored.ts")) + .unwrap() + .to_string(); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": file_uri, + "languageId": "typescript", + "version": 1, + "text": "function myFunc(){}" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/formatting", + json!({ + "textDocument": { + "uri": file_uri + }, + "options": { + "tabSize": 2, + "insertSpaces": true + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + shutdown(&mut client); + } + + #[test] + fn lsp_format_exclude_default_config() { + let temp_dir = TempDir::new(); + let workspace_root = temp_dir.path().canonicalize().unwrap(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + let deno_jsonc = + serde_json::to_vec_pretty(&load_fixture("deno.fmt.exclude.jsonc")) + .unwrap(); + fs::write(workspace_root.join("deno.jsonc"), deno_jsonc).unwrap(); + + params.root_uri = + Some(Url::from_file_path(workspace_root.clone()).unwrap()); + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + let file_uri = + ModuleSpecifier::from_file_path(workspace_root.join("ignored.ts")) + .unwrap() + .to_string(); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": file_uri, + "languageId": "typescript", + "version": 1, + "text": "function myFunc(){}" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/formatting", + json!({ + "textDocument": { + "uri": file_uri + }, + "options": { + "tabSize": 2, + "insertSpaces": true + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + shutdown(&mut client); + } + + #[test] + fn lsp_format_json() { + let mut client = init("initialize_params.json"); + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": "file:///a/file.json", + "languageId": "json", + "version": 1, + "text": "{\"key\":\"value\"}" + } + }), + ) + .unwrap(); + + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/formatting", + json!({ + "textDocument": { + "uri": "file:///a/file.json" + }, + "options": { + "tabSize": 2, + "insertSpaces": true + } + }), + ) + .unwrap(); + + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!([ + { + "range": { + "start": { + "line": 0, + "character": 1 + }, + "end": { + "line": 0, + "character": 1 + } + }, + "newText": " " + }, + { + "range": { + "start": { "line": 0, "character": 7 }, + "end": { "line": 0, "character": 7 } + }, + "newText": " " + }, + { + "range": { + "start": { "line": 0, "character": 14 }, + "end": { "line": 0, "character": 15 } + }, + "newText": " }\n" + } + ])) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_json_no_diagnostics() { + let mut client = init("initialize_params.json"); + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": "file:///a/file.json", + "languageId": "json", + "version": 1, + "text": "{\"key\":\"value\"}" + } + }), + ) + .unwrap(); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/semanticTokens/full", + json!({ + "textDocument": { + "uri": "file:///a/file.json" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.json" + }, + "position": { + "line": 0, + "character": 3 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + + shutdown(&mut client); + } + + #[test] + fn lsp_format_markdown() { + let mut client = init("initialize_params.json"); + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": "file:///a/file.md", + "languageId": "markdown", + "version": 1, + "text": "# Hello World" + } + }), + ) + .unwrap(); + + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/formatting", + json!({ + "textDocument": { + "uri": "file:///a/file.md" + }, + "options": { + "tabSize": 2, + "insertSpaces": true + } + }), + ) + .unwrap(); + + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!([ + { + "range": { + "start": { "line": 0, "character": 1 }, + "end": { "line": 0, "character": 3 } + }, + "newText": "" + }, + { + "range": { + "start": { "line": 0, "character": 15 }, + "end": { "line": 0, "character": 15 } + }, + "newText": "\n" + } + ])) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_format_with_config() { + let temp_dir = TempDir::new(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + let deno_fmt_jsonc = + serde_json::to_vec_pretty(&load_fixture("deno.fmt.jsonc")).unwrap(); + fs::write(temp_dir.path().join("deno.fmt.jsonc"), deno_fmt_jsonc).unwrap(); + + params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); + if let Some(Value::Object(mut map)) = params.initialization_options { + map.insert("config".to_string(), json!("./deno.fmt.jsonc")); + params.initialization_options = Some(Value::Object(map)); + } + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "export async function someVeryLongFunctionName() {\nconst response = fetch(\"http://localhost:4545/some/non/existent/path.json\");\nconsole.log(response.text());\nconsole.log(\"finished!\")\n}" + } + }), + ) + .unwrap(); + + // The options below should be ignored in favor of configuration from config file. + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/formatting", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "options": { + "tabSize": 2, + "insertSpaces": true + } + }), + ) + .unwrap(); + + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!([{ + "range": { + "start": { + "line": 1, + "character": 0 + }, + "end": { + "line": 1, + "character": 0 + } + }, + "newText": "\t" + }, + { + "range": { + "start": { + "line": 1, + "character": 23 + }, + "end": { + "line": 1, + "character": 24 + } + }, + "newText": "\n\t\t'" + }, + { + "range": { + "start": { + "line": 1, + "character": 73 + }, + "end": { + "line": 1, + "character": 74 + } + }, + "newText": "',\n\t" + }, + { + "range": { + "start": { + "line": 2, + "character": 0 + }, + "end": { + "line": 2, + "character": 0 + } + }, + "newText": "\t" + }, + { + "range": { + "start": { + "line": 3, + "character": 0 + }, + "end": { + "line": 3, + "character": 0 + } + }, + "newText": "\t" + }, + { + "range": { + "start": { + "line": 3, + "character": 12 + }, + "end": { + "line": 3, + "character": 13 + } + }, + "newText": "'" + }, + { + "range": { + "start": { + "line": 3, + "character": 22 + }, + "end": { + "line": 3, + "character": 24 + } + }, + "newText": "');" + }, + { + "range": { + "start": { + "line": 4, + "character": 1 + }, + "end": { + "line": 4, + "character": 1 + } + }, + "newText": "\n" + }] + )) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_markdown_no_diagnostics() { + let mut client = init("initialize_params.json"); + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": "file:///a/file.md", + "languageId": "markdown", + "version": 1, + "text": "# Hello World" + } + }), + ) + .unwrap(); + + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/semanticTokens/full", + json!({ + "textDocument": { + "uri": "file:///a/file.md" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.md" + }, + "position": { + "line": 0, + "character": 3 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!(maybe_res, Some(json!(null))); + + shutdown(&mut client); + } + + #[test] + fn lsp_configuration_did_change() { + let _g = http_server(); + let mut client = init("initialize_params_did_config_change.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "import * as a from \"http://localhost:4545/x/a@\"" + } + }), + ); + client + .write_notification( + "workspace/didChangeConfiguration", + json!({ + "settings": {} + }), + ) + .unwrap(); + let (id, method, _) = client.read_request::().unwrap(); + assert_eq!(method, "workspace/configuration"); + client + .write_response( + id, + json!([{ + "enable": true, + "codeLens": { + "implementations": true, + "references": true + }, + "importMap": null, + "lint": true, + "suggest": { + "autoImports": true, + "completeFunctionCalls": false, + "names": true, + "paths": true, + "imports": { + "hosts": { + "http://localhost:4545/": true + } + } + }, + "unstable": false + }]), + ) + .unwrap(); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/completion", + json!({ + "textDocument": { + "uri": "file:///a/file.ts" + }, + "position": { + "line": 0, + "character": 46 + }, + "context": { + "triggerKind": 2, + "triggerCharacter": "@" + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + if let Some(lsp::CompletionResponse::List(list)) = maybe_res { + assert!(!list.is_incomplete); + assert_eq!(list.items.len(), 3); + } else { + panic!("unexpected response"); + } + let (maybe_res, maybe_err) = client + .write_request( + "completionItem/resolve", + load_fixture("completion_resolve_params_registry.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("completion_resolve_response_registry.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_workspace_symbol() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "export class A {\n fieldA: string;\n fieldB: string;\n}\n", + } + }), + ); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file_01.ts", + "languageId": "typescript", + "version": 1, + "text": "export class B {\n fieldC: string;\n fieldD: string;\n}\n", + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "workspace/symbol", + json!({ + "query": "field" + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!([ + { + "name": "fieldA", + "kind": 8, + "location": { + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 1, + "character": 17 + } + } + }, + "containerName": "A" + }, + { + "name": "fieldB", + "kind": 8, + "location": { + "uri": "file:///a/file.ts", + "range": { + "start": { + "line": 2, + "character": 2 + }, + "end": { + "line": 2, + "character": 17 + } + } + }, + "containerName": "A" + }, + { + "name": "fieldC", + "kind": 8, + "location": { + "uri": "file:///a/file_01.ts", + "range": { + "start": { + "line": 1, + "character": 2 + }, + "end": { + "line": 1, + "character": 17 + } + } + }, + "containerName": "B" + }, + { + "name": "fieldD", + "kind": 8, + "location": { + "uri": "file:///a/file_01.ts", + "range": { + "start": { + "line": 2, + "character": 2 + }, + "end": { + "line": 2, + "character": 17 + } + } + }, + "containerName": "B" + } + ])) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_code_actions_ignore_lint() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": "let message = 'Hello, Deno!';\nconsole.log(message);\n" + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeAction", + load_fixture("code_action_ignore_lint_params.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_ignore_lint_response.json")) + ); + shutdown(&mut client); + } + + /// This test exercises updating an existing deno-lint-ignore-file comment. + #[test] + fn lsp_code_actions_update_ignore_lint() { + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.ts", + "languageId": "typescript", + "version": 1, + "text": +"#!/usr/bin/env -S deno run +// deno-lint-ignore-file camelcase +let snake_case = 'Hello, Deno!'; +console.log(snake_case); +", + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request( + "textDocument/codeAction", + load_fixture("code_action_update_ignore_lint_params.json"), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(load_fixture("code_action_update_ignore_lint_response.json")) + ); + shutdown(&mut client); + } + + #[test] + fn lsp_lint_with_config() { + let temp_dir = TempDir::new(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + let deno_lint_jsonc = + serde_json::to_vec_pretty(&load_fixture("deno.lint.jsonc")).unwrap(); + fs::write(temp_dir.path().join("deno.lint.jsonc"), deno_lint_jsonc) + .unwrap(); + + params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); + if let Some(Value::Object(mut map)) = params.initialization_options { + map.insert("config".to_string(), json!("./deno.lint.jsonc")); + params.initialization_options = Some(Value::Object(map)); + } + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + let mut session = TestSession::from_client(client); + + let diagnostics = session.did_open(load_fixture("did_open_lint.json")); + let diagnostics = diagnostics.viewed(); + assert_eq!(diagnostics.len(), 1); + assert_eq!( + diagnostics[0].code, + Some(lsp::NumberOrString::String("ban-untagged-todo".to_string())) + ); + session.shutdown_and_exit(); + } + + #[test] + fn lsp_lint_exclude_with_config() { + let temp_dir = TempDir::new(); + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + let deno_lint_jsonc = + serde_json::to_vec_pretty(&load_fixture("deno.lint.exclude.jsonc")) + .unwrap(); + fs::write(temp_dir.path().join("deno.lint.jsonc"), deno_lint_jsonc) + .unwrap(); + + params.root_uri = Some(Url::from_file_path(temp_dir.path()).unwrap()); + if let Some(Value::Object(mut map)) = params.initialization_options { + map.insert("config".to_string(), json!("./deno.lint.jsonc")); + params.initialization_options = Some(Value::Object(map)); + } + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + let diagnostics = did_open( + &mut client, + json!({ + "textDocument": { + "uri": ModuleSpecifier::from_file_path(temp_dir.path().join("ignored.ts")).unwrap().to_string(), + "languageId": "typescript", + "version": 1, + "text": "// TODO: fixme\nexport async function non_camel_case() {\nconsole.log(\"finished!\")\n}" + } + }), + ); + let diagnostics = diagnostics + .into_iter() + .flat_map(|x| x.diagnostics) + .collect::>(); + assert_eq!(diagnostics, Vec::new()); + shutdown(&mut client); + } + + #[test] + fn lsp_jsx_import_source_pragma() { + let _g = http_server(); + let mut client = init("initialize_params.json"); + did_open( + &mut client, + json!({ + "textDocument": { + "uri": "file:///a/file.tsx", + "languageId": "typescriptreact", + "version": 1, + "text": +"/** @jsxImportSource http://localhost:4545/jsx */ + +function A() { + return \"hello\"; +} + +export function B() { + return ; +} +", + } + }), + ); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "deno/cache", + json!({ + "referrer": { + "uri": "file:///a/file.tsx", + }, + "uris": [ + { + "uri": "http://127.0.0.1:4545/jsx/jsx-runtime", + } + ], + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let (maybe_res, maybe_err) = client + .write_request::<_, _, Value>( + "textDocument/hover", + json!({ + "textDocument": { + "uri": "file:///a/file.tsx" + }, + "position": { + "line": 0, + "character": 25 + } + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert_eq!( + maybe_res, + Some(json!({ + "contents": { + "kind": "markdown", + "value": "**Resolved Dependency**\n\n**Code**: http​://localhost:4545/jsx/jsx-runtime\n", + }, + "range": { + "start": { + "line": 0, + "character": 21 + }, + "end": { + "line": 0, + "character": 46 + } + } + })) + ); + shutdown(&mut client); + } + + #[derive(Debug, Clone, Deserialize, PartialEq)] + #[serde(rename_all = "camelCase")] + struct TestData { + id: String, + label: String, + steps: Option>, + range: Option, + } + + #[derive(Debug, Deserialize, PartialEq)] + #[serde(rename_all = "camelCase")] + enum TestModuleNotificationKind { + Insert, + Replace, + } + + #[derive(Debug, Deserialize)] + #[serde(rename_all = "camelCase")] + struct TestModuleNotificationParams { + text_document: lsp::TextDocumentIdentifier, + kind: TestModuleNotificationKind, + label: String, + tests: Vec, + } + + #[derive(Debug, Deserialize)] + #[serde(rename_all = "camelCase")] + struct EnqueuedTestModule { + text_document: lsp::TextDocumentIdentifier, + ids: Vec, + } + + #[derive(Debug, Deserialize)] + #[serde(rename_all = "camelCase")] + struct TestRunResponseParams { + enqueued: Vec, + } + + #[test] + fn lsp_testing_api() { + let mut params: lsp::InitializeParams = + serde_json::from_value(load_fixture("initialize_params.json")).unwrap(); + let temp_dir = TempDir::new(); + + let root_specifier = + ensure_directory_specifier(Url::from_file_path(temp_dir.path()).unwrap()); + + let module_path = temp_dir.path().join("./test.ts"); + let specifier = ModuleSpecifier::from_file_path(&module_path).unwrap(); + let contents = r#" +Deno.test({ + name: "test a", + fn() { + console.log("test a"); + } +}); +"#; + fs::write(&module_path, contents).unwrap(); + fs::write(temp_dir.path().join("./deno.jsonc"), r#"{}"#).unwrap(); + + params.root_uri = Some(root_specifier); + + let deno_exe = deno_exe_path(); + let mut client = LspClient::new(&deno_exe, false).unwrap(); + client + .write_request::<_, _, Value>("initialize", params) + .unwrap(); + + client.write_notification("initialized", json!({})).unwrap(); + + client + .write_notification( + "textDocument/didOpen", + json!({ + "textDocument": { + "uri": specifier, + "languageId": "typescript", + "version": 1, + "text": contents, + } + }), + ) + .unwrap(); + + handle_configuration_request( + &mut client, + json!([{ + "enable": true, + "codeLens": { + "test": true + } + }]), + ); + + for _ in 0..4 { + let result = client.read_notification::(); + assert!(result.is_ok()); + let (method, notification) = result.unwrap(); + if method.as_str() == "deno/testModule" { + let params: TestModuleNotificationParams = + serde_json::from_value(notification.unwrap()).unwrap(); + assert_eq!(params.text_document.uri, specifier); + assert_eq!(params.kind, TestModuleNotificationKind::Replace); + assert_eq!(params.label, "test.ts"); + assert_eq!(params.tests.len(), 1); + let test = ¶ms.tests[0]; + assert_eq!(test.label, "test a"); + assert!(test.steps.is_none()); + assert_eq!( + test.range, + Some(lsp::Range { + start: lsp::Position { + line: 1, + character: 5, + }, + end: lsp::Position { + line: 1, + character: 9, + } + }) + ); + } + } + + let (maybe_res, maybe_err) = client + .write_request::<_, _, TestRunResponseParams>( + "deno/testRun", + json!({ + "id": 1, + "kind": "run", + }), + ) + .unwrap(); + assert!(maybe_err.is_none()); + assert!(maybe_res.is_some()); + let res = maybe_res.unwrap(); + assert_eq!(res.enqueued.len(), 1); + assert_eq!(res.enqueued[0].text_document.uri, specifier); + assert_eq!(res.enqueued[0].ids.len(), 1); + let id = res.enqueued[0].ids[0].clone(); + + let res = client.read_notification::(); + assert!(res.is_ok()); + let (method, notification) = res.unwrap(); + assert_eq!(method, "deno/testRunProgress"); + assert_eq!( + notification, + Some(json!({ + "id": 1, + "message": { + "type": "started", + "test": { + "textDocument": { + "uri": specifier, + }, + "id": id, + }, + } + })) + ); + + let res = client.read_notification::(); + assert!(res.is_ok()); + let (method, notification) = res.unwrap(); + assert_eq!(method, "deno/testRunProgress"); + let notification_value = notification + .as_ref() + .unwrap() + .as_object() + .unwrap() + .get("message") + .unwrap() + .as_object() + .unwrap() + .get("value") + .unwrap() + .as_str() + .unwrap(); + // deno test's output capturing flushes with a zero-width space in order to + // synchronize the output pipes. Occassionally this zero width space + // might end up in the output so strip it from the output comparison here. + assert_eq!(notification_value.replace('\u{200B}', ""), "test a\r\n"); + assert_eq!( + notification, + Some(json!({ + "id": 1, + "message": { + "type": "output", + "value": notification_value, + "test": { + "textDocument": { + "uri": specifier, + }, + "id": id, + }, + } + })) + ); + + let res = client.read_notification::(); + assert!(res.is_ok()); + let (method, notification) = res.unwrap(); + assert_eq!(method, "deno/testRunProgress"); + let notification = notification.unwrap(); + let obj = notification.as_object().unwrap(); + assert_eq!(obj.get("id"), Some(&json!(1))); + let message = obj.get("message").unwrap().as_object().unwrap(); + match message.get("type").and_then(|v| v.as_str()) { + Some("passed") => { + assert_eq!( + message.get("test"), + Some(&json!({ + "textDocument": { + "uri": specifier + }, + "id": id, + })) + ); + assert!(message.contains_key("duration")); + + let res = client.read_notification::(); + assert!(res.is_ok()); + let (method, notification) = res.unwrap(); + assert_eq!(method, "deno/testRunProgress"); + assert_eq!( + notification, + Some(json!({ + "id": 1, + "message": { + "type": "end", + } + })) + ); + } + // sometimes on windows, the messages come out of order, but it actually is + // working, so if we do get the end before the passed, we will simply let + // the test pass + Some("end") => (), + _ => panic!("unexpected message {}", json!(notification)), + } + + shutdown(&mut client); + } +} diff --git a/cli/tests/npm_tests.rs b/cli/tests/npm_tests.rs new file mode 100644 index 0000000000..99e4756208 --- /dev/null +++ b/cli/tests/npm_tests.rs @@ -0,0 +1,1526 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +mod npm { + use super::*; + use pretty_assertions::assert_eq; + use std::process::Stdio; + use test_util as util; + use util::assert_contains; + use util::http_server; + + // NOTE: See how to make test npm packages at ./testdata/npm/README.md + + itest!(esm_module { + args: "run --allow-read --allow-env npm/esm/main.js", + output: "npm/esm/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(esm_module_eval { + args_vec: vec![ + "eval", + "import chalk from 'npm:chalk@5'; console.log(chalk.green('chalk esm loads'));", + ], + output: "npm/esm/main.out", + envs: env_vars(), + http_server: true, +}); + + itest!(esm_module_deno_test { + args: "test --allow-read --allow-env --unstable npm/esm/test.js", + output: "npm/esm/test.out", + envs: env_vars(), + http_server: true, + }); + + itest!(esm_import_cjs_default { + args: "run --allow-read --allow-env --unstable --quiet --check=all npm/esm_import_cjs_default/main.ts", + output: "npm/esm_import_cjs_default/main.out", + envs: env_vars(), + http_server: true, +}); + + itest!(cjs_with_deps { + args: "run --allow-read --allow-env npm/cjs_with_deps/main.js", + output: "npm/cjs_with_deps/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(cjs_sub_path { + args: "run --allow-read npm/cjs_sub_path/main.js", + output: "npm/cjs_sub_path/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(cjs_local_global_decls { + args: "run --allow-read npm/cjs_local_global_decls/main.ts", + output: "npm/cjs_local_global_decls/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(cjs_reexport_collision { + args: "run -A --quiet npm/cjs_reexport_collision/main.ts", + output: "npm/cjs_reexport_collision/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(cjs_this_in_exports { + args: "run --allow-read --quiet npm/cjs_this_in_exports/main.js", + output: "npm/cjs_this_in_exports/main.out", + envs: env_vars(), + http_server: true, + exit_code: 1, + }); + + itest!(translate_cjs_to_esm { + args: "run -A --quiet npm/translate_cjs_to_esm/main.js", + output: "npm/translate_cjs_to_esm/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(compare_globals { + args: "run --allow-read --unstable --check=all npm/compare_globals/main.ts", + output: "npm/compare_globals/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(conditional_exports { + args: "run --allow-read npm/conditional_exports/main.js", + output: "npm/conditional_exports/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(dual_cjs_esm { + args: "run -A --quiet npm/dual_cjs_esm/main.ts", + output: "npm/dual_cjs_esm/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(child_process_fork_test { + args: "run -A --quiet npm/child_process_fork_test/main.ts", + output: "npm/child_process_fork_test/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(cjs_module_export_assignment { + args: "run -A --unstable --quiet --check=all npm/cjs_module_export_assignment/main.ts", + output: "npm/cjs_module_export_assignment/main.out", + envs: env_vars(), + http_server: true, +}); + + itest!(cjs_module_export_assignment_number { + args: "run -A --unstable --quiet --check=all npm/cjs_module_export_assignment_number/main.ts", + output: "npm/cjs_module_export_assignment_number/main.out", + envs: env_vars(), + http_server: true, +}); + + itest!(mixed_case_package_name_global_dir { + args: "run npm/mixed_case_package_name/global.ts", + output: "npm/mixed_case_package_name/global.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + }); + + itest!(mixed_case_package_name_local_dir { + args: + "run --node-modules-dir -A $TESTDATA/npm/mixed_case_package_name/local.ts", + output: "npm/mixed_case_package_name/local.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + temp_cwd: true, + }); + + // FIXME(bartlomieju): npm: specifiers are not handled in dynamic imports + // at the moment + // itest!(dynamic_import { + // args: "run --allow-read --allow-env npm/dynamic_import/main.ts", + // output: "npm/dynamic_import/main.out", + // envs: env_vars(), + // http_server: true, + // }); + + itest!(env_var_re_export_dev { + args: "run --allow-read --allow-env --quiet npm/env_var_re_export/main.js", + output_str: Some("dev\n"), + envs: env_vars(), + http_server: true, + }); + + itest!(env_var_re_export_prod { + args: "run --allow-read --allow-env --quiet npm/env_var_re_export/main.js", + output_str: Some("prod\n"), + envs: { + let mut vars = env_vars(); + vars.push(("NODE_ENV".to_string(), "production".to_string())); + vars + }, + http_server: true, + }); + + itest!(cached_only { + args: "run --cached-only npm/cached_only/main.ts", + output: "npm/cached_only/main.out", + envs: env_vars(), + exit_code: 1, + }); + + itest!(import_map { + args: "run --allow-read --allow-env --import-map npm/import_map/import_map.json npm/import_map/main.js", + output: "npm/import_map/main.out", + envs: env_vars(), + http_server: true, +}); + + itest!(lock_file { + args: "run --allow-read --allow-env --lock npm/lock_file/lock.json npm/lock_file/main.js", + output: "npm/lock_file/main.out", + envs: env_vars(), + http_server: true, + exit_code: 10, +}); + + itest!(sub_paths { + args: "run -A --quiet npm/sub_paths/main.jsx", + output: "npm/sub_paths/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(remote_npm_specifier { + args: "run --quiet npm/remote_npm_specifier/main.ts", + output: "npm/remote_npm_specifier/main.out", + envs: env_vars(), + http_server: true, + exit_code: 1, + }); + + itest!(tarball_with_global_header { + args: "run -A --quiet npm/tarball_with_global_header/main.js", + output: "npm/tarball_with_global_header/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(nonexistent_file { + args: "run -A --quiet npm/nonexistent_file/main.js", + output: "npm/nonexistent_file/main.out", + envs: env_vars(), + http_server: true, + exit_code: 1, + }); + + itest!(invalid_package_name { + args: "run -A --quiet npm/invalid_package_name/main.js", + output: "npm/invalid_package_name/main.out", + envs: env_vars(), + exit_code: 1, + }); + + itest!(require_json { + args: "run -A --quiet npm/require_json/main.js", + output: "npm/require_json/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(error_version_after_subpath { + args: "run -A --quiet npm/error_version_after_subpath/main.js", + output: "npm/error_version_after_subpath/main.out", + envs: env_vars(), + http_server: true, + exit_code: 1, + }); + + itest!(deno_cache { + args: "cache --reload npm:chalk npm:mkdirp", + output: "npm/deno_cache.out", + envs: env_vars(), + http_server: true, + }); + + itest!(check_all { + args: "check --remote npm/check_errors/main.ts", + output: "npm/check_errors/main_all.out", + envs: env_vars(), + http_server: true, + exit_code: 1, + }); + + itest!(check_local { + args: "check npm/check_errors/main.ts", + output: "npm/check_errors/main_local.out", + envs: env_vars(), + http_server: true, + exit_code: 1, + }); + + itest!(types { + args: "check --quiet npm/types/main.ts", + output: "npm/types/main.out", + envs: env_vars(), + http_server: true, + exit_code: 1, + }); + + itest!(types_ambient_module { + args: "check --quiet npm/types_ambient_module/main.ts", + output: "npm/types_ambient_module/main.out", + envs: env_vars(), + http_server: true, + exit_code: 1, + }); + + itest!(types_ambient_module_import_map { + args: "check --quiet --import-map=npm/types_ambient_module/import_map.json npm/types_ambient_module/main_import_map.ts", + output: "npm/types_ambient_module/main_import_map.out", + envs: env_vars(), + http_server: true, + exit_code: 1, +}); + + itest!(no_types_in_conditional_exports { + args: "run --check --unstable npm/no_types_in_conditional_exports/main.ts", + output: "npm/no_types_in_conditional_exports/main.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + }); + + itest!(types_entry_value_not_exists { + args: "run --check=all npm/types_entry_value_not_exists/main.ts", + output: "npm/types_entry_value_not_exists/main.out", + envs: env_vars(), + http_server: true, + exit_code: 0, + }); + + itest!(types_no_types_entry { + args: "run --check=all npm/types_no_types_entry/main.ts", + output: "npm/types_no_types_entry/main.out", + envs: env_vars(), + http_server: true, + exit_code: 0, + }); + + itest!(typescript_file_in_package { + args: "run npm/typescript_file_in_package/main.ts", + output: "npm/typescript_file_in_package/main.out", + envs: env_vars(), + http_server: true, + exit_code: 1, + }); + + #[test] + fn parallel_downloading() { + let (out, _err) = util::run_and_collect_output_with_args( + true, + vec![ + "run", + "--allow-read", + "--allow-env", + "npm/cjs_with_deps/main.js", + ], + None, + // don't use the sync env var + Some(env_vars_no_sync_download()), + true, + ); + assert!(out.contains("chalk cjs loads")); + } + + #[test] + fn cached_only_after_first_run() { + let _server = http_server(); + + let deno_dir = util::new_deno_dir(); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("npm/cached_only_after_first_run/main1.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!(stderr, "Download"); + assert_contains!(stdout, "createChalk: chalk"); + assert!(output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("--cached-only") + .arg("npm/cached_only_after_first_run/main2.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!( + stderr, + "An npm specifier not found in cache: \"ansi-styles\", --cached-only is specified." + ); + assert!(stdout.is_empty()); + assert!(!output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("--cached-only") + .arg("npm/cached_only_after_first_run/main1.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(output.status.success()); + assert!(stderr.is_empty()); + assert_contains!(stdout, "createChalk: chalk"); + } + + #[test] + fn reload_flag() { + let _server = http_server(); + + let deno_dir = util::new_deno_dir(); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("npm/reload/main.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!(stderr, "Download"); + assert_contains!(stdout, "createChalk: chalk"); + assert!(output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("--reload") + .arg("npm/reload/main.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!(stderr, "Download"); + assert_contains!(stdout, "createChalk: chalk"); + assert!(output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("--reload=npm:") + .arg("npm/reload/main.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!(stderr, "Download"); + assert_contains!(stdout, "createChalk: chalk"); + assert!(output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("--reload=npm:chalk") + .arg("npm/reload/main.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!(stderr, "Download"); + assert_contains!(stdout, "createChalk: chalk"); + assert!(output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("--reload=npm:foobar") + .arg("npm/reload/main.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert!(stderr.is_empty()); + assert_contains!(stdout, "createChalk: chalk"); + assert!(output.status.success()); + } + + #[test] + fn no_npm_after_first_run() { + let _server = http_server(); + + let deno_dir = util::new_deno_dir(); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("--no-npm") + .arg("npm/no_npm_after_first_run/main1.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!( + stderr, + "Following npm specifiers were requested: \"chalk@5\"; but --no-npm is specified." + ); + assert!(stdout.is_empty()); + assert!(!output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("npm/no_npm_after_first_run/main1.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!(stderr, "Download"); + assert_contains!(stdout, "createChalk: chalk"); + assert!(output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("--no-npm") + .arg("npm/no_npm_after_first_run/main1.ts") + .env("NO_COLOR", "1") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + let stderr = String::from_utf8_lossy(&output.stderr); + let stdout = String::from_utf8_lossy(&output.stdout); + assert_contains!( + stderr, + "Following npm specifiers were requested: \"chalk@5\"; but --no-npm is specified." + ); + assert!(stdout.is_empty()); + assert!(!output.status.success()); + } + + #[test] + fn deno_run_cjs_module() { + let _server = http_server(); + + let deno_dir = util::new_deno_dir(); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(deno_dir.path()) + .arg("run") + .arg("--allow-read") + .arg("--allow-env") + .arg("--allow-write") + .arg("npm:mkdirp@1.0.4") + .arg("test_dir") + .env("NO_COLOR", "1") + .envs(env_vars()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(output.status.success()); + + assert!(deno_dir.path().join("test_dir").exists()); + } + + itest!(deno_run_cowsay { + args: "run -A --quiet npm:cowsay@1.5.0 Hello", + output: "npm/deno_run_cowsay.out", + envs: env_vars_no_sync_download(), + http_server: true, + }); + + itest!(deno_run_cowsay_explicit { + args: "run -A --quiet npm:cowsay@1.5.0/cowsay Hello", + output: "npm/deno_run_cowsay.out", + envs: env_vars_no_sync_download(), + http_server: true, + }); + + itest!(deno_run_cowthink { + args: "run -A --quiet npm:cowsay@1.5.0/cowthink Hello", + output: "npm/deno_run_cowthink.out", + envs: env_vars_no_sync_download(), + http_server: true, + }); + + itest!(deno_run_bin_esm { + args: "run -A --quiet npm:@denotest/bin/cli-esm this is a test", + output: "npm/deno_run_esm.out", + envs: env_vars(), + http_server: true, + }); + + itest!(deno_run_bin_no_ext { + args: "run -A --quiet npm:@denotest/bin/cli-no-ext this is a test", + output: "npm/deno_run_no_ext.out", + envs: env_vars(), + http_server: true, + }); + + itest!(deno_run_bin_cjs { + args: "run -A --quiet npm:@denotest/bin/cli-cjs this is a test", + output: "npm/deno_run_cjs.out", + envs: env_vars(), + http_server: true, + }); + + itest!(deno_run_non_existent { + args: "run npm:mkdirp@0.5.125", + output: "npm/deno_run_non_existent.out", + envs: env_vars(), + http_server: true, + exit_code: 1, + }); + + itest!(builtin_module_module { + args: "run --allow-read --quiet npm/builtin_module_module/main.js", + output: "npm/builtin_module_module/main.out", + envs: env_vars(), + http_server: true, + }); + + itest!(node_modules_dir_require_added_node_modules_folder { + args: + "run --node-modules-dir -A --quiet $TESTDATA/npm/require_added_nm_folder/main.js", + output: "npm/require_added_nm_folder/main.out", + envs: env_vars(), + http_server: true, + exit_code: 0, + temp_cwd: true, +}); + + itest!(node_modules_dir_with_deps { + args: "run --allow-read --allow-env --node-modules-dir $TESTDATA/npm/cjs_with_deps/main.js", + output: "npm/cjs_with_deps/main.out", + envs: env_vars(), + http_server: true, + temp_cwd: true, +}); + + itest!(node_modules_dir_yargs { + args: "run --allow-read --allow-env --node-modules-dir $TESTDATA/npm/cjs_yargs/main.js", + output: "npm/cjs_yargs/main.out", + envs: env_vars(), + http_server: true, + temp_cwd: true, +}); + + #[test] + fn node_modules_dir_cache() { + let _server = http_server(); + + let deno_dir = util::new_deno_dir(); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(deno_dir.path()) + .arg("cache") + .arg("--node-modules-dir") + .arg("--quiet") + .arg(util::testdata_path().join("npm/dual_cjs_esm/main.ts")) + .envs(env_vars()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(output.status.success()); + + let node_modules = deno_dir.path().join("node_modules"); + assert!(node_modules + .join( + ".deno/@denotest+dual-cjs-esm@1.0.0/node_modules/@denotest/dual-cjs-esm" + ) + .exists()); + assert!(node_modules.join("@denotest/dual-cjs-esm").exists()); + + // now try deleting the folder with the package source in the npm cache dir + let package_global_cache_dir = deno_dir + .path() + .join("npm") + .join("localhost_4545") + .join("npm") + .join("registry") + .join("@denotest") + .join("dual-cjs-esm") + .join("1.0.0"); + assert!(package_global_cache_dir.exists()); + std::fs::remove_dir_all(&package_global_cache_dir).unwrap(); + + // run the output, and it shouldn't bother recreating the directory + // because it already has everything cached locally in the node_modules folder + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(deno_dir.path()) + .arg("run") + .arg("--node-modules-dir") + .arg("--quiet") + .arg("-A") + .arg(util::testdata_path().join("npm/dual_cjs_esm/main.ts")) + .envs(env_vars()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(output.status.success()); + + // this won't exist, but actually the parent directory + // will because it still re-downloads the registry information + assert!(!package_global_cache_dir.exists()); + } + + #[test] + fn ensure_registry_files_local() { + // ensures the registry files all point at local tarballs + let registry_dir_path = util::testdata_path().join("npm").join("registry"); + for entry in std::fs::read_dir(®istry_dir_path).unwrap() { + let entry = entry.unwrap(); + if entry.metadata().unwrap().is_dir() { + let registry_json_path = registry_dir_path + .join(entry.file_name()) + .join("registry.json"); + if registry_json_path.exists() { + let file_text = std::fs::read_to_string(®istry_json_path).unwrap(); + if file_text.contains("https://registry.npmjs.org/") { + panic!( + "file {} contained a reference to the npm registry", + registry_json_path.display(), + ); + } + } + } + } + } + + itest!(compile_errors { + args: "compile -A --quiet npm/cached_only/main.ts", + output_str: Some("error: npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: npm:chalk@5\n"), + exit_code: 1, + envs: env_vars(), + http_server: true, +}); + + itest!(bundle_errors { + args: "bundle --quiet npm/esm/main.js", + output_str: Some("error: npm specifiers have not yet been implemented for this sub command (https://github.com/denoland/deno/issues/15960). Found: npm:chalk@5\n"), + exit_code: 1, + envs: env_vars(), + http_server: true, +}); + + itest!(info_chalk_display { + args: "info --quiet npm/cjs_with_deps/main.js", + output: "npm/cjs_with_deps/main_info.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + }); + + itest!(info_chalk_display_node_modules_dir { + args: "info --quiet --node-modules-dir $TESTDATA/npm/cjs_with_deps/main.js", + output: "npm/cjs_with_deps/main_info.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + temp_cwd: true, + }); + + itest!(info_chalk_json { + args: "info --quiet --json npm/cjs_with_deps/main.js", + output: "npm/cjs_with_deps/main_info_json.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + }); + + itest!(info_chalk_json_node_modules_dir { + args: + "info --quiet --node-modules-dir --json $TESTDATA/npm/cjs_with_deps/main.js", + output: "npm/cjs_with_deps/main_info_json.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + temp_cwd: true, +}); + + itest!(info_cli_chalk_display { + args: "info --quiet npm:chalk@4", + output: "npm/info/chalk.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + }); + + itest!(info_cli_chalk_json { + args: "info --quiet --json npm:chalk@4", + output: "npm/info/chalk_json.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + }); + + #[test] + fn lock_file_missing_top_level_package() { + let _server = http_server(); + + let deno_dir = util::new_deno_dir(); + let temp_dir = util::TempDir::new(); + + // write empty config file + temp_dir.write("deno.json", "{}"); + + // Lock file that is automatically picked up has been intentionally broken, + // by removing "cowsay" package from it. This test ensures that npm resolver + // snapshot can be successfully hydrated in such situation + let lock_file_content = r#"{ + "version": "2", + "remote": {}, + "npm": { + "specifiers": { "cowsay": "cowsay@1.5.0" }, + "packages": { + "ansi-regex@3.0.1": { + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dependencies": {} + }, + "ansi-regex@5.0.1": { + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dependencies": {} + }, + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { "color-convert": "color-convert@2.0.1" } + }, + "camelcase@5.3.1": { + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dependencies": {} + }, + "cliui@6.0.0": { + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "string-width@4.2.3", + "strip-ansi": "strip-ansi@6.0.1", + "wrap-ansi": "wrap-ansi@6.2.0" + } + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { "color-name": "color-name@1.1.4" } + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dependencies": {} + }, + "decamelize@1.2.0": { + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dependencies": {} + }, + "emoji-regex@8.0.0": { + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dependencies": {} + }, + "find-up@4.1.0": { + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "locate-path@5.0.0", + "path-exists": "path-exists@4.0.0" + } + }, + "get-caller-file@2.0.5": { + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dependencies": {} + }, + "get-stdin@8.0.0": { + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dependencies": {} + }, + "is-fullwidth-code-point@2.0.0": { + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dependencies": {} + }, + "is-fullwidth-code-point@3.0.0": { + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dependencies": {} + }, + "locate-path@5.0.0": { + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { "p-locate": "p-locate@4.1.0" } + }, + "p-limit@2.3.0": { + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { "p-try": "p-try@2.2.0" } + }, + "p-locate@4.1.0": { + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { "p-limit": "p-limit@2.3.0" } + }, + "p-try@2.2.0": { + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dependencies": {} + }, + "path-exists@4.0.0": { + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dependencies": {} + }, + "require-directory@2.1.1": { + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dependencies": {} + }, + "require-main-filename@2.0.0": { + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dependencies": {} + }, + "set-blocking@2.0.0": { + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dependencies": {} + }, + "string-width@2.1.1": { + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dependencies": { + "is-fullwidth-code-point": "is-fullwidth-code-point@2.0.0", + "strip-ansi": "strip-ansi@4.0.0" + } + }, + "string-width@4.2.3": { + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "emoji-regex@8.0.0", + "is-fullwidth-code-point": "is-fullwidth-code-point@3.0.0", + "strip-ansi": "strip-ansi@6.0.1" + } + }, + "strip-ansi@4.0.0": { + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dependencies": { "ansi-regex": "ansi-regex@3.0.1" } + }, + "strip-ansi@6.0.1": { + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { "ansi-regex": "ansi-regex@5.0.1" } + }, + "strip-final-newline@2.0.0": { + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dependencies": {} + }, + "which-module@2.0.0": { + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dependencies": {} + }, + "wrap-ansi@6.2.0": { + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "ansi-styles@4.3.0", + "string-width": "string-width@4.2.3", + "strip-ansi": "strip-ansi@6.0.1" + } + }, + "y18n@4.0.3": { + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dependencies": {} + }, + "yargs-parser@18.1.3": { + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "camelcase@5.3.1", + "decamelize": "decamelize@1.2.0" + } + }, + "yargs@15.4.1": { + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": { + "cliui": "cliui@6.0.0", + "decamelize": "decamelize@1.2.0", + "find-up": "find-up@4.1.0", + "get-caller-file": "get-caller-file@2.0.5", + "require-directory": "require-directory@2.1.1", + "require-main-filename": "require-main-filename@2.0.0", + "set-blocking": "set-blocking@2.0.0", + "string-width": "string-width@4.2.3", + "which-module": "which-module@2.0.0", + "y18n": "y18n@4.0.3", + "yargs-parser": "yargs-parser@18.1.3" + } + } + } + } + } + "#; + temp_dir.write("deno.lock", lock_file_content); + let main_contents = r#" + import cowsay from "npm:cowsay"; + console.log(cowsay); + "#; + temp_dir.write("main.ts", main_contents); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--quiet") + .arg("--lock") + .arg("deno.lock") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(!output.status.success()); + + let stderr = String::from_utf8(output.stderr).unwrap(); + assert_eq!( + stderr, + "error: failed reading lockfile 'deno.lock'\n\nCaused by:\n the lockfile is corrupt. You can recreate it with --lock-write\n" + ); + } + + #[test] + fn lock_file_lock_write() { + // https://github.com/denoland/deno/issues/16666 + // Ensure that --lock-write still adds npm packages to the lockfile + let _server = http_server(); + + let deno_dir = util::new_deno_dir(); + let temp_dir = util::TempDir::new(); + + // write empty config file + temp_dir.write("deno.json", "{}"); + + // write a lock file with borked integrity + let lock_file_content = r#"{ + "version": "2", + "remote": {}, + "npm": { + "specifiers": { "cowsay@1.5.0": "cowsay@1.5.0" }, + "packages": { + "ansi-regex@3.0.1": { + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "dependencies": {} + }, + "ansi-regex@5.0.1": { + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dependencies": {} + }, + "ansi-styles@4.3.0": { + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { "color-convert": "color-convert@2.0.1" } + }, + "camelcase@5.3.1": { + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dependencies": {} + }, + "cliui@6.0.0": { + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dependencies": { + "string-width": "string-width@4.2.3", + "strip-ansi": "strip-ansi@6.0.1", + "wrap-ansi": "wrap-ansi@6.2.0" + } + }, + "color-convert@2.0.1": { + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { "color-name": "color-name@1.1.4" } + }, + "color-name@1.1.4": { + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dependencies": {} + }, + "cowsay@1.5.0": { + "integrity": "sha512-8Ipzr54Z8zROr/62C8f0PdhQcDusS05gKTS87xxdji8VbWefWly0k8BwGK7+VqamOrkv3eGsCkPtvlHzrhWsCA==", + "dependencies": { + "get-stdin": "get-stdin@8.0.0", + "string-width": "string-width@2.1.1", + "strip-final-newline": "strip-final-newline@2.0.0", + "yargs": "yargs@15.4.1" + } + }, + "decamelize@1.2.0": { + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "dependencies": {} + }, + "emoji-regex@8.0.0": { + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dependencies": {} + }, + "find-up@4.1.0": { + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dependencies": { + "locate-path": "locate-path@5.0.0", + "path-exists": "path-exists@4.0.0" + } + }, + "get-caller-file@2.0.5": { + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dependencies": {} + }, + "get-stdin@8.0.0": { + "integrity": "sha512-sY22aA6xchAzprjyqmSEQv4UbAAzRN0L2dQB0NlN5acTTK9Don6nhoc3eAbUnpZiCANAMfd/+40kVdKfFygohg==", + "dependencies": {} + }, + "is-fullwidth-code-point@2.0.0": { + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "dependencies": {} + }, + "is-fullwidth-code-point@3.0.0": { + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dependencies": {} + }, + "locate-path@5.0.0": { + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dependencies": { "p-locate": "p-locate@4.1.0" } + }, + "p-limit@2.3.0": { + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { "p-try": "p-try@2.2.0" } + }, + "p-locate@4.1.0": { + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dependencies": { "p-limit": "p-limit@2.3.0" } + }, + "p-try@2.2.0": { + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dependencies": {} + }, + "path-exists@4.0.0": { + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dependencies": {} + }, + "require-directory@2.1.1": { + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dependencies": {} + }, + "require-main-filename@2.0.0": { + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dependencies": {} + }, + "set-blocking@2.0.0": { + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "dependencies": {} + }, + "string-width@2.1.1": { + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dependencies": { + "is-fullwidth-code-point": "is-fullwidth-code-point@2.0.0", + "strip-ansi": "strip-ansi@4.0.0" + } + }, + "string-width@4.2.3": { + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "emoji-regex@8.0.0", + "is-fullwidth-code-point": "is-fullwidth-code-point@3.0.0", + "strip-ansi": "strip-ansi@6.0.1" + } + }, + "strip-ansi@4.0.0": { + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dependencies": { "ansi-regex": "ansi-regex@3.0.1" } + }, + "strip-ansi@6.0.1": { + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { "ansi-regex": "ansi-regex@5.0.1" } + }, + "strip-final-newline@2.0.0": { + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dependencies": {} + }, + "which-module@2.0.0": { + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==", + "dependencies": {} + }, + "wrap-ansi@6.2.0": { + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dependencies": { + "ansi-styles": "ansi-styles@4.3.0", + "string-width": "string-width@4.2.3", + "strip-ansi": "strip-ansi@6.0.1" + } + }, + "y18n@4.0.3": { + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "dependencies": {} + }, + "yargs-parser@18.1.3": { + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dependencies": { + "camelcase": "camelcase@5.3.1", + "decamelize": "decamelize@1.2.0" + } + }, + "yargs@15.4.1": { + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "dependencies": { + "cliui": "cliui@6.0.0", + "decamelize": "decamelize@1.2.0", + "find-up": "find-up@4.1.0", + "get-caller-file": "get-caller-file@2.0.5", + "require-directory": "require-directory@2.1.1", + "require-main-filename": "require-main-filename@2.0.0", + "set-blocking": "set-blocking@2.0.0", + "string-width": "string-width@4.2.3", + "which-module": "which-module@2.0.0", + "y18n": "y18n@4.0.3", + "yargs-parser": "yargs-parser@18.1.3" + } + } + } + } +} +"#; + temp_dir.write("deno.lock", lock_file_content); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("cache") + .arg("--lock-write") + .arg("--quiet") + .arg("npm:cowsay@1.5.0") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(output.status.success()); + assert_eq!(output.status.code(), Some(0)); + + let stdout = String::from_utf8(output.stdout).unwrap(); + assert!(stdout.is_empty()); + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stderr.is_empty()); + assert_eq!( + lock_file_content, + std::fs::read_to_string(temp_dir.path().join("deno.lock")).unwrap() + ); + } + + #[test] + fn auto_discover_lock_file() { + let _server = http_server(); + + let deno_dir = util::new_deno_dir(); + let temp_dir = util::TempDir::new(); + + // write empty config file + temp_dir.write("deno.json", "{}"); + + // write a lock file with borked integrity + let lock_file_content = r#"{ + "version": "2", + "remote": {}, + "npm": { + "specifiers": { "@denotest/bin": "@denotest/bin@1.0.0" }, + "packages": { + "@denotest/bin@1.0.0": { + "integrity": "sha512-foobar", + "dependencies": {} + } + } + } + }"#; + temp_dir.write("deno.lock", lock_file_content); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--unstable") + .arg("-A") + .arg("npm:@denotest/bin/cli-esm") + .arg("test") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(!output.status.success()); + assert_eq!(output.status.code(), Some(10)); + + let stderr = String::from_utf8(output.stderr).unwrap(); + assert!(stderr.contains( + "Integrity check failed for npm package: \"@denotest/bin@1.0.0\"" + )); + } + + #[test] + fn peer_deps_with_copied_folders_and_lockfile() { + let _server = http_server(); + + let deno_dir = util::new_deno_dir(); + let temp_dir = util::TempDir::new(); + + // write empty config file + temp_dir.write("deno.json", "{}"); + let test_folder_path = test_util::testdata_path() + .join("npm") + .join("peer_deps_with_copied_folders"); + let main_contents = + std::fs::read_to_string(test_folder_path.join("main.ts")).unwrap(); + temp_dir.write("./main.ts", main_contents); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(output.status.success()); + + let expected_output = + std::fs::read_to_string(test_folder_path.join("main.out")).unwrap(); + + assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output); + + assert!(temp_dir.path().join("deno.lock").exists()); + let grandchild_path = deno_dir + .path() + .join("npm") + .join("localhost_4545") + .join("npm") + .join("registry") + .join("@denotest") + .join("peer-dep-test-grandchild"); + assert!(grandchild_path.join("1.0.0").exists()); + assert!(grandchild_path.join("1.0.0_1").exists()); // copy folder, which is hardlinked + + // run again + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!(String::from_utf8(output.stderr).unwrap(), "1\n2\n"); + assert!(output.status.success()); + + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--reload") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output); + assert!(output.status.success()); + + // now run with local node modules + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--node-modules-dir") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!(String::from_utf8(output.stderr).unwrap(), "1\n2\n"); + assert!(output.status.success()); + + let deno_folder = temp_dir.path().join("node_modules").join(".deno"); + assert!(deno_folder + .join("@denotest+peer-dep-test-grandchild@1.0.0") + .exists()); + assert!(deno_folder + .join("@denotest+peer-dep-test-grandchild@1.0.0_1") + .exists()); // copy folder + + // now again run with local node modules + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--node-modules-dir") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(output.status.success()); + assert_eq!(String::from_utf8(output.stderr).unwrap(), "1\n2\n"); + + // now ensure it works with reloading + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--node-modules-dir") + .arg("--reload") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert!(output.status.success()); + assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output); + + // now ensure it works with reloading and no lockfile + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(temp_dir.path()) + .arg("run") + .arg("--node-modules-dir") + .arg("--no-lock") + .arg("--reload") + .arg("-A") + .arg("main.ts") + .envs(env_vars()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!(String::from_utf8(output.stderr).unwrap(), expected_output,); + assert!(output.status.success()); + } + + itest!(info_peer_deps { + args: "info --quiet npm/peer_deps_with_copied_folders/main.ts", + output: "npm/peer_deps_with_copied_folders/main_info.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + }); + + itest!(info_peer_deps_json { + args: "info --quiet --json npm/peer_deps_with_copied_folders/main.ts", + output: "npm/peer_deps_with_copied_folders/main_info_json.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + }); + + itest!(create_require { + args: "run --reload npm/create_require/main.ts", + output: "npm/create_require/main.out", + exit_code: 0, + envs: env_vars(), + http_server: true, + }); + + fn env_vars_no_sync_download() -> Vec<(String, String)> { + vec![ + ("DENO_NODE_COMPAT_URL".to_string(), util::std_file_url()), + ("DENO_NPM_REGISTRY".to_string(), util::npm_registry_url()), + ("NO_COLOR".to_string(), "1".to_string()), + ] + } + + fn env_vars() -> Vec<(String, String)> { + let mut env_vars = env_vars_no_sync_download(); + env_vars.push(( + // make downloads determinstic + "DENO_UNSTABLE_NPM_SYNC_DOWNLOAD".to_string(), + "1".to_string(), + )); + env_vars + } +} diff --git a/cli/tests/repl_tests.rs b/cli/tests/repl_tests.rs new file mode 100644 index 0000000000..edc7918f9f --- /dev/null +++ b/cli/tests/repl_tests.rs @@ -0,0 +1,884 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use test_util as util; +use test_util::assert_contains; +use test_util::assert_ends_with; +use test_util::assert_not_contains; + +mod repl { + use super::*; + + #[test] + fn pty_multiline() { + util::with_pty(&["repl"], |mut console| { + console.write_line("(\n1 + 2\n)"); + console.write_line("{\nfoo: \"foo\"\n}"); + console.write_line("`\nfoo\n`"); + console.write_line("`\n\\`\n`"); + console.write_line("'{'"); + console.write_line("'('"); + console.write_line("'['"); + console.write_line("/{/"); + console.write_line("/\\(/"); + console.write_line("/\\[/"); + console.write_line("console.log(\"{test1} abc {test2} def {{test3}}\".match(/{([^{].+?)}/));"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_contains!(output, '3'); + assert_contains!(output, "{ foo: \"foo\" }"); + assert_contains!(output, "\"\\nfoo\\n\""); + assert_contains!(output, "\"\\n`\\n\""); + assert_contains!(output, "\"{\""); + assert_contains!(output, "\"(\""); + assert_contains!(output, "\"[\""); + assert_contains!(output, "/{/"); + assert_contains!(output, "/\\(/"); + assert_contains!(output, "/\\[/"); + assert_contains!(output, "[ \"{test1}\", \"test1\" ]"); + }); + } + + #[test] + fn pty_null() { + util::with_pty(&["repl"], |mut console| { + console.write_line("null"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_contains!(output, "null"); + }); + } + + #[test] + fn pty_unpaired_braces() { + util::with_pty(&["repl"], |mut console| { + console.write_line(")"); + console.write_line("]"); + console.write_line("}"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_contains!(output, "Unexpected token `)`"); + assert_contains!(output, "Unexpected token `]`"); + assert_contains!(output, "Unexpected token `}`"); + }); + } + + #[test] + fn pty_bad_input() { + util::with_pty(&["repl"], |mut console| { + console.write_line("'\\u{1f3b5}'[0]"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_contains!(output, "Unterminated string literal"); + }); + } + + #[test] + fn pty_syntax_error_input() { + util::with_pty(&["repl"], |mut console| { + console.write_line("('\\u')"); + console.write_line("'"); + console.write_line("[{'a'}];"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_contains!( + output, + "Bad character escape sequence, expected 4 hex characters" + ); + assert_contains!(output, "Unterminated string constant"); + assert_contains!(output, "Expected a semicolon"); + }); + } + + #[test] + fn pty_complete_symbol() { + util::with_pty(&["repl"], |mut console| { + console.write_line("Symbol.it\t"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_contains!(output, "Symbol(Symbol.iterator)"); + }); + } + + #[test] + fn pty_complete_declarations() { + util::with_pty(&["repl"], |mut console| { + console.write_line("class MyClass {}"); + console.write_line("My\t"); + console.write_line("let myVar;"); + console.write_line("myV\t"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_contains!(output, "> MyClass"); + assert_contains!(output, "> myVar"); + }); + } + + #[test] + fn pty_complete_primitives() { + util::with_pty(&["repl"], |mut console| { + console.write_line("let func = function test(){}"); + console.write_line("func.appl\t"); + console.write_line("let str = ''"); + console.write_line("str.leng\t"); + console.write_line("false.valueO\t"); + console.write_line("5n.valueO\t"); + console.write_line("let num = 5"); + console.write_line("num.toStrin\t"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_contains!(output, "> func.apply"); + assert_contains!(output, "> str.length"); + assert_contains!(output, "> 5n.valueOf"); + assert_contains!(output, "> false.valueOf"); + assert_contains!(output, "> num.toString"); + }); + } + + #[test] + fn pty_complete_expression() { + util::with_pty(&["repl"], |mut console| { + console.write_text("Deno.\t\t"); + console.write_text("y"); + console.write_line(""); + console.write_line("close();"); + let output = console.read_all_output(); + assert_contains!(output, "Display all"); + assert_contains!(output, "core"); + assert_contains!(output, "args"); + assert_contains!(output, "exit"); + assert_contains!(output, "symlink"); + assert_contains!(output, "permissions"); + }); + } + + #[test] + fn pty_complete_imports() { + util::with_pty(&["repl"], |mut console| { + // single quotes + console.write_line("import './run/001_hel\t'"); + // double quotes + console.write_line("import { output } from \"./run/045_out\t\""); + console.write_line("output('testing output');"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_contains!(output, "Hello World"); + assert_contains!( + output, + // on windows, could contain either (it's flaky) + "\ntesting output", + "testing output\u{1b}", + ); + }); + + // ensure when the directory changes that the suggestions come from the cwd + util::with_pty(&["repl"], |mut console| { + console.write_line("Deno.chdir('./subdir');"); + console.write_line("import '../run/001_hel\t'"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_contains!(output, "Hello World"); + }); + } + + #[test] + fn pty_complete_imports_no_panic_empty_specifier() { + // does not panic when tabbing when empty + util::with_pty(&["repl"], |mut console| { + console.write_line("import '\t';"); + console.write_line("close();"); + }); + } + + #[test] + fn pty_ignore_symbols() { + util::with_pty(&["repl"], |mut console| { + console.write_line("Array.Symbol\t"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_contains!(output, "undefined"); + assert_not_contains!( + output, + "Uncaught TypeError: Array.Symbol is not a function" + ); + }); + } + + #[test] + fn pty_assign_global_this() { + util::with_pty(&["repl"], |mut console| { + console.write_line("globalThis = 42;"); + console.write_line("close();"); + + let output = console.read_all_output(); + assert_not_contains!(output, "panicked"); + }); + } + + #[test] + fn pty_emoji() { + // windows was having issues displaying this + util::with_pty(&["repl"], |mut console| { + console.write_line(r#"console.log('\u{1F995}');"#); + console.write_line("close();"); + + let output = console.read_all_output(); + // only one for the output (since input is escaped) + let emoji_count = output.chars().filter(|c| *c == '🦕').count(); + assert_eq!(emoji_count, 1); + }); + } + + #[test] + fn console_log() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["console.log('hello')", "'world'"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "hello\nundefined\n\"world\"\n"); + assert!(err.is_empty()); + } + + #[test] + fn object_literal() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["{}", "{ foo: 'bar' }"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "{}\n{ foo: \"bar\" }\n"); + assert!(err.is_empty()); + } + + #[test] + fn block_expression() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["{};", "{\"\"}"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "undefined\n\"\"\n"); + assert!(err.is_empty()); + } + + #[test] + fn await_resolve() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["await Promise.resolve('done')"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "\"done\"\n"); + assert!(err.is_empty()); + } + + #[test] + fn await_timeout() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["await new Promise((r) => setTimeout(r, 0, 'done'))"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "\"done\"\n"); + assert!(err.is_empty()); + } + + #[test] + fn let_redeclaration() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["let foo = 0;", "foo", "let foo = 1;", "foo"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "undefined\n0\nundefined\n1\n"); + assert!(err.is_empty()); + } + + #[test] + fn repl_cwd() { + let (_out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["Deno.cwd()"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert!(err.is_empty()); + } + + #[test] + fn typescript() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec![ + "function add(a: number, b: number) { return a + b }", + "const result: number = add(1, 2) as number;", + "result", + ]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "undefined\nundefined\n3\n"); + assert!(err.is_empty()); + } + + #[test] + fn typescript_declarations() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec![ + "namespace Test { export enum Values { A, B, C } }", + "Test.Values.A", + "Test.Values.C", + "interface MyInterface { prop: string; }", + "type MyTypeAlias = string;", + ]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + let expected_end_text = "undefined\n0\n2\nundefined\nundefined\n"; + assert_ends_with!(out, expected_end_text); + assert!(err.is_empty()); + } + + #[test] + fn typescript_decorators() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec![ + "function dec(target) { target.prototype.test = () => 2; }", + "@dec class Test {}", + "new Test().test()", + ]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "undefined\n[Function: Test]\n2\n"); + assert!(err.is_empty()); + } + + #[test] + fn eof() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["1 + 2"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "3\n"); + assert!(err.is_empty()); + } + + #[test] + fn strict() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec![ + "let a = {};", + "Object.preventExtensions(a);", + "a.c = 1;", + ]), + None, + false, + ); + assert_contains!( + out, + "Uncaught TypeError: Cannot add property c, object is not extensible" + ); + assert!(err.is_empty()); + } + + #[test] + fn close_command() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["close()", "'ignored'"]), + None, + false, + ); + + assert_not_contains!(out, "ignored"); + assert!(err.is_empty()); + } + + #[test] + fn function() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["Deno.writeFileSync"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "[Function: writeFileSync]\n"); + assert!(err.is_empty()); + } + + #[test] + #[ignore] + fn multiline() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["(\n1 + 2\n)"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "3\n"); + assert!(err.is_empty()); + } + + #[test] + fn import() { + let (out, _) = util::run_and_collect_output( + true, + "repl", + Some(vec!["import('./subdir/auto_print_hello.ts')"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_contains!(out, "hello!\n"); + } + + #[test] + fn import_declarations() { + let (out, _) = util::run_and_collect_output( + true, + "repl", + Some(vec!["import './subdir/auto_print_hello.ts';"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_contains!(out, "hello!\n"); + } + + #[test] + fn exports_stripped() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["export default 5;", "export class Test {}"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_contains!(out, "5\n"); + assert!(err.is_empty()); + } + + #[test] + fn call_eval_unterminated() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["eval('{')"]), + None, + false, + ); + assert_contains!(out, "Unexpected end of input"); + assert!(err.is_empty()); + } + + #[test] + fn unpaired_braces() { + for right_brace in &[")", "]", "}"] { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec![right_brace]), + None, + false, + ); + assert_contains!(out, "Unexpected token"); + assert!(err.is_empty()); + } + } + + #[test] + fn reference_error() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["not_a_variable"]), + None, + false, + ); + assert_contains!(out, "not_a_variable is not defined"); + assert!(err.is_empty()); + } + + #[test] + fn syntax_error() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec![ + "syntax error", + "2", // ensure it keeps accepting input after + ]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!( + out, + "parse error: Expected ';', '}' or at 1:8\n2\n" + ); + assert!(err.is_empty()); + } + + #[test] + fn syntax_error_jsx() { + // JSX is not supported in the REPL + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["const element =
;"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_contains!(out, "Unexpected token `>`"); + assert!(err.is_empty()); + } + + #[test] + fn type_error() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["console()"]), + None, + false, + ); + assert_contains!(out, "console is not a function"); + assert!(err.is_empty()); + } + + #[test] + fn variable() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["var a = 123;", "a"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "undefined\n123\n"); + assert!(err.is_empty()); + } + + #[test] + fn lexical_scoped_variable() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["let a = 123;", "a"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "undefined\n123\n"); + assert!(err.is_empty()); + } + + #[test] + fn missing_deno_dir() { + use std::fs::{read_dir, remove_dir_all}; + const DENO_DIR: &str = "nonexistent"; + let test_deno_dir = test_util::testdata_path().join(DENO_DIR); + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["1"]), + Some(vec![ + ("DENO_DIR".to_owned(), DENO_DIR.to_owned()), + ("NO_COLOR".to_owned(), "1".to_owned()), + ]), + false, + ); + assert!(read_dir(&test_deno_dir).is_ok()); + remove_dir_all(&test_deno_dir).unwrap(); + assert_ends_with!(out, "1\n"); + assert!(err.is_empty()); + } + + #[test] + fn save_last_eval() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["1", "_"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "1\n1\n"); + assert!(err.is_empty()); + } + + #[test] + fn save_last_thrown() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["throw 1", "_error"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!(out, "Uncaught 1\n1\n"); + assert!(err.is_empty()); + } + + #[test] + fn assign_underscore() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["_ = 1", "2", "_"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + assert_ends_with!( + out, + "Last evaluation result is no longer saved to _.\n1\n2\n1\n" + ); + assert!(err.is_empty()); + } + + #[test] + fn assign_underscore_error() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["_error = 1", "throw 2", "_error"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + println!("{}", out); + assert_ends_with!( + out, + "Last thrown error is no longer saved to _error.\n1\nUncaught 2\n1\n" + ); + assert!(err.is_empty()); + } + + #[test] + fn custom_inspect() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec![ + r#"const o = { + [Symbol.for("Deno.customInspect")]() { + throw new Error('Oops custom inspect error'); + }, + };"#, + "o", + ]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + + assert_contains!(out, "Oops custom inspect error"); + assert!(err.is_empty()); + } + + #[test] + fn eval_flag_valid_input() { + let (out, err) = util::run_and_collect_output_with_args( + true, + vec!["repl", "--eval", "const t = 10;"], + Some(vec!["t * 500;"]), + None, + false, + ); + assert_contains!(out, "5000"); + assert!(err.is_empty()); + } + + #[test] + fn eval_flag_parse_error() { + let (out, err) = util::run_and_collect_output_with_args( + true, + vec!["repl", "--eval", "const %"], + Some(vec!["250 * 10"]), + None, + false, + ); + assert_contains!( + test_util::strip_ansi_codes(&out), + "error in --eval flag. parse error: Unexpected token `%`." + ); + assert_contains!(out, "2500"); // should not prevent input + assert!(err.is_empty()); + } + + #[test] + fn eval_flag_runtime_error() { + let (out, err) = util::run_and_collect_output_with_args( + true, + vec!["repl", "--eval", "throw new Error('Testing')"], + Some(vec!["250 * 10"]), + None, + false, + ); + assert_contains!(out, "error in --eval flag. Uncaught Error: Testing"); + assert_contains!(out, "2500"); // should not prevent input + assert!(err.is_empty()); + } + + #[test] + fn eval_file_flag_valid_input() { + let (out, err) = util::run_and_collect_output_with_args( + true, + vec!["repl", "--eval-file=./run/001_hello.js"], + None, + None, + false, + ); + assert_contains!(out, "Hello World"); + assert!(err.is_empty()); + } + + #[test] + fn eval_file_flag_call_defined_function() { + let (out, err) = util::run_and_collect_output_with_args( + true, + vec!["repl", "--eval-file=./tsc/d.ts"], + Some(vec!["v4()"]), + None, + false, + ); + assert_contains!(out, "hello"); + assert!(err.is_empty()); + } + + #[test] + fn eval_file_flag_http_input() { + let (out, err) = util::run_and_collect_output_with_args( + true, + vec!["repl", "--eval-file=http://127.0.0.1:4545/tsc/d.ts"], + Some(vec!["v4()"]), + None, + true, + ); + assert_contains!(out, "hello"); + assert!(err.contains("Download")); + } + + #[test] + fn eval_file_flag_multiple_files() { + let (out, err) = util::run_and_collect_output_with_args( + true, + vec!["repl", "--eval-file=http://127.0.0.1:4545/repl/import_type.ts,./tsc/d.ts,http://127.0.0.1:4545/type_definitions/foo.js"], + Some(vec!["b.method1=v4", "b.method1()+foo.toUpperCase()"]), + None, + true, + ); + assert_contains!(out, "helloFOO"); + assert_contains!(err, "Download"); + } + + #[test] + fn pty_clear_function() { + util::with_pty(&["repl"], |mut console| { + console.write_line("console.log('hello');"); + console.write_line("clear();"); + console.write_line("const clear = 1234 + 2000;"); + console.write_line("clear;"); + console.write_line("close();"); + + let output = console.read_all_output(); + if cfg!(windows) { + // Windows will overwrite what's in the console buffer before + // we read from it. It contains this string repeated many times + // to clear the screen. + assert_contains!(output, "\r\n\u{1b}[K\r\n\u{1b}[K\r\n\u{1b}[K"); + } else { + assert_contains!(output, "hello"); + assert_contains!(output, "[1;1H"); + } + assert_contains!(output, "undefined"); + assert_contains!(output, "const clear = 1234 + 2000;"); + assert_contains!(output, "3234"); + }); + } + + #[test] + fn pty_tab_handler() { + // If the last character is **not** whitespace, we show the completions + util::with_pty(&["repl"], |mut console| { + console.write_line("a\t\t"); + console.write_line("close();"); + let output = console.read_all_output(); + assert_contains!(output, "addEventListener"); + assert_contains!(output, "alert"); + assert_contains!(output, "atob"); + }); + // If the last character is whitespace, we just insert a tab + util::with_pty(&["repl"], |mut console| { + console.write_line("a; \t\t"); // last character is whitespace + console.write_line("close();"); + let output = console.read_all_output(); + assert_not_contains!(output, "addEventListener"); + assert_not_contains!(output, "alert"); + assert_not_contains!(output, "atob"); + }); + } + + #[test] + fn repl_report_error() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec![ + r#"console.log(1); reportError(new Error("foo")); console.log(2);"#, + ]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + + // TODO(nayeemrmn): The REPL should report event errors and rejections. + assert_contains!(out, "1\n2\nundefined\n"); + assert!(err.is_empty()); + } + + #[test] + fn pty_aggregate_error() { + let (out, err) = util::run_and_collect_output( + true, + "repl", + Some(vec!["await Promise.any([])"]), + Some(vec![("NO_COLOR".to_owned(), "1".to_owned())]), + false, + ); + + assert_contains!(out, "AggregateError"); + assert!(err.is_empty()); + } +} diff --git a/cli/tests/run_tests.rs b/cli/tests/run_tests.rs new file mode 100644 index 0000000000..db6e6f685c --- /dev/null +++ b/cli/tests/run_tests.rs @@ -0,0 +1,3681 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; +use deno_core::url; +use deno_runtime::deno_fetch::reqwest; +use std::io::Read; +use std::io::Write; +use std::process::Command; +use std::process::Stdio; +use test_util as util; +use test_util::TempDir; +use tokio::task::LocalSet; +use trust_dns_client::serialize::txt::Lexer; +use trust_dns_client::serialize::txt::Parser; +use util::assert_contains; + +mod run { + use super::*; + + itest!(stdout_write_all { + args: "run --quiet run/stdout_write_all.ts", + output: "run/stdout_write_all.out", + }); + + itest!(stdin_read_all { + args: "run --quiet run/stdin_read_all.ts", + output: "run/stdin_read_all.out", + input: Some("01234567890123456789012345678901234567890123456789"), + }); + + itest!(stdout_write_sync_async { + args: "run --quiet run/stdout_write_sync_async.ts", + output: "run/stdout_write_sync_async.out", + }); + + itest!(_001_hello { + args: "run --reload run/001_hello.js", + output: "run/001_hello.js.out", + }); + + itest!(_002_hello { + args: "run --quiet --reload run/002_hello.ts", + output: "run/002_hello.ts.out", + }); + + itest!(_003_relative_import { + args: "run --quiet --reload run/003_relative_import.ts", + output: "run/003_relative_import.ts.out", + }); + + itest!(_004_set_timeout { + args: "run --quiet --reload run/004_set_timeout.ts", + output: "run/004_set_timeout.ts.out", + }); + + itest!(_005_more_imports { + args: "run --quiet --reload run/005_more_imports.ts", + output: "run/005_more_imports.ts.out", + }); + + itest!(_006_url_imports { + args: "run --quiet --reload run/006_url_imports.ts", + output: "run/006_url_imports.ts.out", + http_server: true, + }); + + itest!(_012_async { + args: "run --quiet --reload run/012_async.ts", + output: "run/012_async.ts.out", + }); + + itest!(_013_dynamic_import { + args: "run --quiet --reload --allow-read run/013_dynamic_import.ts", + output: "run/013_dynamic_import.ts.out", + }); + + itest!(_014_duplicate_import { + args: "run --quiet --reload --allow-read run/014_duplicate_import.ts ", + output: "run/014_duplicate_import.ts.out", + }); + + itest!(_015_duplicate_parallel_import { + args: + "run --quiet --reload --allow-read run/015_duplicate_parallel_import.js", + output: "run/015_duplicate_parallel_import.js.out", + }); + + itest!(_016_double_await { + args: "run --quiet --allow-read --reload run/016_double_await.ts", + output: "run/016_double_await.ts.out", + }); + + itest!(_017_import_redirect { + args: "run --quiet --reload run/017_import_redirect.ts", + output: "run/017_import_redirect.ts.out", + }); + + itest!(_017_import_redirect_nocheck { + args: "run --quiet --reload --no-check run/017_import_redirect.ts", + output: "run/017_import_redirect.ts.out", + }); + + itest!(_017_import_redirect_info { + args: "info --quiet --reload run/017_import_redirect.ts", + output: "run/017_import_redirect_info.out", + }); + + itest!(_018_async_catch { + args: "run --quiet --reload run/018_async_catch.ts", + output: "run/018_async_catch.ts.out", + }); + + itest!(_019_media_types { + args: "run --reload run/019_media_types.ts", + output: "run/019_media_types.ts.out", + http_server: true, + }); + + itest!(_020_json_modules { + args: "run --reload run/020_json_modules.ts", + output: "run/020_json_modules.ts.out", + exit_code: 1, + }); + + itest!(_021_mjs_modules { + args: "run --quiet --reload run/021_mjs_modules.ts", + output: "run/021_mjs_modules.ts.out", + }); + + itest!(_023_no_ext { + args: "run --reload --check run/023_no_ext", + output: "run/023_no_ext.out", + }); + + // TODO(lucacasonato): remove --unstable when permissions goes stable + itest!(_025_hrtime { + args: "run --quiet --allow-hrtime --unstable --reload run/025_hrtime.ts", + output: "run/025_hrtime.ts.out", + }); + + itest!(_025_reload_js_type_error { + args: "run --quiet --reload run/025_reload_js_type_error.js", + output: "run/025_reload_js_type_error.js.out", + }); + + itest!(_026_redirect_javascript { + args: "run --quiet --reload run/026_redirect_javascript.js", + output: "run/026_redirect_javascript.js.out", + http_server: true, + }); + + itest!(_027_redirect_typescript { + args: "run --quiet --reload run/027_redirect_typescript.ts", + output: "run/027_redirect_typescript.ts.out", + http_server: true, + }); + + itest!(_028_args { + args: + "run --quiet --reload run/028_args.ts --arg1 val1 --arg2=val2 -- arg3 arg4", + output: "run/028_args.ts.out", +}); + + itest!(_033_import_map { + args: + "run --quiet --reload --import-map=import_maps/import_map.json import_maps/test.ts", + output: "run/033_import_map.out", +}); + + itest!(_033_import_map_remote { + args: + "run --quiet --reload --import-map=http://127.0.0.1:4545/import_maps/import_map_remote.json --unstable import_maps/test_remote.ts", + output: "run/033_import_map_remote.out", + http_server: true, +}); + + itest!(onload { + args: "run --quiet --reload run/onload/main.ts", + output: "run/onload/main.out", + }); + + itest!(_035_cached_only_flag { + args: "run --reload --check --cached-only http://127.0.0.1:4545/run/019_media_types.ts", + output: "run/035_cached_only_flag.out", + exit_code: 1, + http_server: true, +}); + + itest!(_038_checkjs { + // checking if JS file is run through TS compiler + args: + "run --reload --config run/checkjs.tsconfig.json --check run/038_checkjs.js", + exit_code: 1, + output: "run/038_checkjs.js.out", +}); + + itest!(_042_dyn_import_evalcontext { + args: "run --quiet --allow-read --reload run/042_dyn_import_evalcontext.ts", + output: "run/042_dyn_import_evalcontext.ts.out", + }); + + itest!(_044_bad_resource { + args: "run --quiet --reload --allow-read run/044_bad_resource.ts", + output: "run/044_bad_resource.ts.out", + exit_code: 1, + }); + + // TODO(bartlomieju): remove --unstable once Deno.spawn is stabilized + itest!(_045_proxy { + args: "run -L debug --unstable --allow-net --allow-env --allow-run --allow-read --reload --quiet run/045_proxy_test.ts", + output: "run/045_proxy_test.ts.out", + http_server: true, +}); + + itest!(_046_tsx { + args: "run --quiet --reload run/046_jsx_test.tsx", + output: "run/046_jsx_test.tsx.out", + }); + + itest!(_047_jsx { + args: "run --quiet --reload run/047_jsx_test.jsx", + output: "run/047_jsx_test.jsx.out", + }); + + itest!(_048_media_types_jsx { + args: "run --reload run/048_media_types_jsx.ts", + output: "run/048_media_types_jsx.ts.out", + http_server: true, + }); + + itest!(_052_no_remote_flag { + args: + "run --reload --check --no-remote http://127.0.0.1:4545/run/019_media_types.ts", + output: "run/052_no_remote_flag.out", + exit_code: 1, + http_server: true, +}); + + itest!(_056_make_temp_file_write_perm { + args: + "run --quiet --allow-read --allow-write=./subdir/ run/056_make_temp_file_write_perm.ts", + output: "run/056_make_temp_file_write_perm.out", +}); + + itest!(_058_tasks_microtasks_close { + args: "run --quiet run/058_tasks_microtasks_close.ts", + output: "run/058_tasks_microtasks_close.ts.out", + }); + + itest!(_059_fs_relative_path_perm { + args: "run run/059_fs_relative_path_perm.ts", + output: "run/059_fs_relative_path_perm.ts.out", + exit_code: 1, + }); + + itest!(_070_location { + args: "run --location https://foo/bar?baz#bat run/070_location.ts", + output: "run/070_location.ts.out", + }); + + itest!(_071_location_unset { + args: "run run/071_location_unset.ts", + output: "run/071_location_unset.ts.out", + }); + + itest!(_072_location_relative_fetch { + args: "run --location http://127.0.0.1:4545/ --allow-net run/072_location_relative_fetch.ts", + output: "run/072_location_relative_fetch.ts.out", + http_server: true, +}); + + // tests the beforeunload event + itest!(beforeunload_event { + args: "run run/before_unload.js", + output: "run/before_unload.js.out", + }); + + // tests the serialization of webstorage (both localStorage and sessionStorage) + itest!(webstorage_serialization { + args: "run run/webstorage/serialization.ts", + output: "run/webstorage/serialization.ts.out", + }); + + // tests to ensure that when `--location` is set, all code shares the same + // localStorage cache based on the origin of the location URL. + #[test] + fn webstorage_location_shares_origin() { + let deno_dir = util::new_deno_dir(); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--location") + .arg("https://example.com/a.ts") + .arg("run/webstorage/fixture.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Storage { length: 0 }\n"); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--location") + .arg("https://example.com/b.ts") + .arg("run/webstorage/logger.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Storage { length: 1, hello: \"deno\" }\n"); + } + + // test to ensure that when a --config file is set, but no --location, that + // storage persists against unique configuration files. + #[test] + fn webstorage_config_file() { + let deno_dir = util::new_deno_dir(); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--config") + .arg("run/webstorage/config_a.jsonc") + .arg("run/webstorage/fixture.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Storage { length: 0 }\n"); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--config") + .arg("run/webstorage/config_b.jsonc") + .arg("run/webstorage/logger.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Storage { length: 0 }\n"); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--config") + .arg("run/webstorage/config_a.jsonc") + .arg("run/webstorage/logger.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Storage { length: 1, hello: \"deno\" }\n"); + } + + // tests to ensure `--config` does not effect persisted storage when a + // `--location` is provided. + #[test] + fn webstorage_location_precedes_config() { + let deno_dir = util::new_deno_dir(); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--location") + .arg("https://example.com/a.ts") + .arg("--config") + .arg("run/webstorage/config_a.jsonc") + .arg("run/webstorage/fixture.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Storage { length: 0 }\n"); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--location") + .arg("https://example.com/b.ts") + .arg("--config") + .arg("run/webstorage/config_b.jsonc") + .arg("run/webstorage/logger.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Storage { length: 1, hello: \"deno\" }\n"); + } + + // test to ensure that when there isn't a configuration or location, that the + // main module is used to determine how to persist storage data. + #[test] + fn webstorage_main_module() { + let deno_dir = util::new_deno_dir(); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("run/webstorage/fixture.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Storage { length: 0 }\n"); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("run/webstorage/logger.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Storage { length: 0 }\n"); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("run/webstorage/fixture.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert_eq!(output.stdout, b"Storage { length: 1, hello: \"deno\" }\n"); + } + + itest!(_075_import_local_query_hash { + args: "run run/075_import_local_query_hash.ts", + output: "run/075_import_local_query_hash.ts.out", + }); + + itest!(_077_fetch_empty { + args: "run -A run/077_fetch_empty.ts", + output: "run/077_fetch_empty.ts.out", + exit_code: 1, + }); + + itest!(_078_unload_on_exit { + args: "run run/078_unload_on_exit.ts", + output: "run/078_unload_on_exit.ts.out", + exit_code: 1, + }); + + itest!(_079_location_authentication { + args: + "run --location https://foo:bar@baz/qux run/079_location_authentication.ts", + output: "run/079_location_authentication.ts.out", +}); + + itest!(_081_location_relative_fetch_redirect { + args: "run --location http://127.0.0.1:4546/ --allow-net run/081_location_relative_fetch_redirect.ts", + output: "run/081_location_relative_fetch_redirect.ts.out", + http_server: true, + }); + + itest!(_082_prepare_stack_trace_throw { + args: "run run/082_prepare_stack_trace_throw.js", + output: "run/082_prepare_stack_trace_throw.js.out", + exit_code: 1, + }); + + #[test] + fn _083_legacy_external_source_map() { + let _g = util::http_server(); + let deno_dir = TempDir::new(); + let module_url = url::Url::parse( + "http://localhost:4545/run/083_legacy_external_source_map.ts", + ) + .unwrap(); + // Write a faulty old external source map. + let faulty_map_path = deno_dir.path().join("gen/http/localhost_PORT4545/9576bd5febd0587c5c4d88d57cb3ac8ebf2600c529142abe3baa9a751d20c334.js.map"); + std::fs::create_dir_all(faulty_map_path.parent().unwrap()).unwrap(); + std::fs::write(faulty_map_path, "{\"version\":3,\"file\":\"\",\"sourceRoot\":\"\",\"sources\":[\"http://localhost:4545/083_legacy_external_source_map.ts\"],\"names\":[],\"mappings\":\";AAAA,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC\"}").unwrap(); + let output = Command::new(util::deno_exe_path()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("run") + .arg(module_url.to_string()) + .output() + .unwrap(); + // Before https://github.com/denoland/deno/issues/6965 was fixed, the faulty + // old external source map would cause a panic while formatting the error + // and the exit code would be 101. The external source map should be ignored + // in favor of the inline one. + assert_eq!(output.status.code(), Some(1)); + let out = std::str::from_utf8(&output.stdout).unwrap(); + assert_eq!(out, ""); + } + + itest!(dynamic_import_async_error { + args: "run --allow-read run/dynamic_import_async_error/main.ts", + output: "run/dynamic_import_async_error/main.out", + }); + + itest!(dynamic_import_already_rejected { + args: "run --allow-read run/dynamic_import_already_rejected/main.ts", + output: "run/dynamic_import_already_rejected/main.out", + }); + + itest!(no_check_imports_not_used_as_values { + args: "run --config run/no_check_imports_not_used_as_values/preserve_imports.tsconfig.json --no-check run/no_check_imports_not_used_as_values/main.ts", + output: "run/no_check_imports_not_used_as_values/main.out", + }); + + itest!(_088_dynamic_import_already_evaluating { + args: "run --allow-read run/088_dynamic_import_already_evaluating.ts", + output: "run/088_dynamic_import_already_evaluating.ts.out", + }); + + // TODO(bartlomieju): remove --unstable once Deno.spawn is stabilized + itest!(_089_run_allow_list { + args: "run --unstable --allow-run=curl run/089_run_allow_list.ts", + output: "run/089_run_allow_list.ts.out", + }); + + #[test] + fn _090_run_permissions_request() { + let args = "run --quiet run/090_run_permissions_request.ts"; + use util::PtyData::*; + util::test_pty2(args, vec![ + Output("⚠️ ️Deno requests run access to \"ls\". Run again with --allow-run to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), + Input("y\n"), + Output("⚠️ ️Deno requests run access to \"cat\". Run again with --allow-run to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), + Input("n\n"), + Output("granted\r\n"), + Output("prompt\r\n"), + Output("denied\r\n"), + ]); + } + + itest!(_091_use_define_for_class_fields { + args: "run --check run/091_use_define_for_class_fields.ts", + output: "run/091_use_define_for_class_fields.ts.out", + exit_code: 1, + }); + + itest!(_092_import_map_unmapped_bare_specifier { + args: "run --import-map import_maps/import_map.json run/092_import_map_unmapped_bare_specifier.ts", + output: "run/092_import_map_unmapped_bare_specifier.ts.out", + exit_code: 1, +}); + + itest!(js_import_detect { + args: "run --quiet --reload run/js_import_detect.ts", + output: "run/js_import_detect.ts.out", + exit_code: 0, + }); + + itest!(blob_gc_finalization { + args: "run run/blob_gc_finalization.js", + output: "run/blob_gc_finalization.js.out", + exit_code: 0, + }); + + itest!(fetch_response_finalization { + args: + "run --v8-flags=--expose-gc --allow-net run/fetch_response_finalization.js", + output: "run/fetch_response_finalization.js.out", + http_server: true, + exit_code: 0, +}); + + itest!(import_type { + args: "run --reload run/import_type.ts", + output: "run/import_type.ts.out", + }); + + itest!(import_type_no_check { + args: "run --reload --no-check run/import_type.ts", + output: "run/import_type.ts.out", + }); + + itest!(private_field_presence { + args: "run --reload run/private_field_presence.ts", + output: "run/private_field_presence.ts.out", + }); + + itest!(private_field_presence_no_check { + args: "run --reload --no-check run/private_field_presence.ts", + output: "run/private_field_presence.ts.out", + }); + + // TODO(bartlomieju): remove --unstable once Deno.spawn is stabilized + itest!(lock_write_fetch { + args: + "run --quiet --allow-read --allow-write --allow-env --allow-run --unstable run/lock_write_fetch/main.ts", + output: "run/lock_write_fetch/main.out", + http_server: true, + exit_code: 0, +}); + + itest!(lock_check_ok { + args: + "run --lock=run/lock_check_ok.json http://127.0.0.1:4545/run/003_relative_import.ts", + output: "run/003_relative_import.ts.out", + http_server: true, +}); + + itest!(lock_check_ok2 { + args: "run --lock=run/lock_check_ok2.json run/019_media_types.ts", + output: "run/019_media_types.ts.out", + http_server: true, + }); + + itest!(lock_dynamic_imports { + args: "run --lock=run/lock_dynamic_imports.json --allow-read --allow-net http://127.0.0.1:4545/run/013_dynamic_import.ts", + output: "run/lock_dynamic_imports.out", + exit_code: 10, + http_server: true, +}); + + itest!(lock_check_err { + args: "run --lock=run/lock_check_err.json http://127.0.0.1:4545/run/003_relative_import.ts", + output: "run/lock_check_err.out", + exit_code: 10, + http_server: true, +}); + + itest!(lock_check_err2 { + args: "run --lock=run/lock_check_err2.json run/019_media_types.ts", + output: "run/lock_check_err2.out", + exit_code: 10, + http_server: true, + }); + + itest!(lock_v2_check_ok { + args: + "run --lock=run/lock_v2_check_ok.json http://127.0.0.1:4545/run/003_relative_import.ts", + output: "run/003_relative_import.ts.out", + http_server: true, +}); + + itest!(lock_v2_check_ok2 { + args: "run --lock=run/lock_v2_check_ok2.json run/019_media_types.ts", + output: "run/019_media_types.ts.out", + http_server: true, + }); + + itest!(lock_v2_dynamic_imports { + args: "run --lock=run/lock_v2_dynamic_imports.json --allow-read --allow-net http://127.0.0.1:4545/run/013_dynamic_import.ts", + output: "run/lock_v2_dynamic_imports.out", + exit_code: 10, + http_server: true, +}); + + itest!(lock_v2_check_err { + args: "run --lock=run/lock_v2_check_err.json http://127.0.0.1:4545/run/003_relative_import.ts", + output: "run/lock_v2_check_err.out", + exit_code: 10, + http_server: true, +}); + + itest!(lock_v2_check_err2 { + args: "run --lock=run/lock_v2_check_err2.json run/019_media_types.ts", + output: "run/lock_v2_check_err2.out", + exit_code: 10, + http_server: true, + }); + + itest!(lock_only_http_and_https { + args: "run --lock=run/lock_only_http_and_https/deno.lock run/lock_only_http_and_https/main.ts", + output: "run/lock_only_http_and_https/main.out", + http_server: true, +}); + + itest!(mts_dmts_mjs { + args: "run subdir/import.mts", + output: "run/mts_dmts_mjs.out", + }); + + itest!(mts_dmts_mjs_no_check { + args: "run --no-check subdir/import.mts", + output: "run/mts_dmts_mjs.out", + }); + + itest!(async_error { + exit_code: 1, + args: "run --reload run/async_error.ts", + output: "run/async_error.ts.out", + }); + + itest!(config { + args: + "run --reload --config run/config/tsconfig.json --check run/config/main.ts", + output: "run/config/main.out", +}); + + itest!(config_types { + args: + "run --reload --quiet --config run/config_types/tsconfig.json run/config_types/main.ts", + output: "run/config_types/main.out", +}); + + itest!(config_types_remote { + http_server: true, + args: "run --reload --quiet --config run/config_types/remote.tsconfig.json run/config_types/main.ts", + output: "run/config_types/main.out", + }); + + itest!(empty_typescript { + args: "run --reload --check run/empty.ts", + output_str: Some("Check file:[WILDCARD]/run/empty.ts\n"), + }); + + itest!(error_001 { + args: "run --reload run/error_001.ts", + exit_code: 1, + output: "run/error_001.ts.out", + }); + + itest!(error_002 { + args: "run --reload run/error_002.ts", + exit_code: 1, + output: "run/error_002.ts.out", + }); + + itest!(error_003_typescript { + args: "run --reload --check run/error_003_typescript.ts", + exit_code: 1, + output: "run/error_003_typescript.ts.out", + }); + + // Supposing that we've already attempted to run error_003_typescript.ts + // we want to make sure that JS wasn't emitted. Running again without reload flag + // should result in the same output. + // https://github.com/denoland/deno/issues/2436 + itest!(error_003_typescript2 { + args: "run --check run/error_003_typescript.ts", + exit_code: 1, + output: "run/error_003_typescript.ts.out", + }); + + itest!(error_004_missing_module { + args: "run --reload run/error_004_missing_module.ts", + exit_code: 1, + output: "run/error_004_missing_module.ts.out", + }); + + itest!(error_005_missing_dynamic_import { + args: + "run --reload --allow-read --quiet run/error_005_missing_dynamic_import.ts", + exit_code: 1, + output: "run/error_005_missing_dynamic_import.ts.out", +}); + + itest!(error_006_import_ext_failure { + args: "run --reload run/error_006_import_ext_failure.ts", + exit_code: 1, + output: "run/error_006_import_ext_failure.ts.out", + }); + + itest!(error_007_any { + args: "run --reload run/error_007_any.ts", + exit_code: 1, + output: "run/error_007_any.ts.out", + }); + + itest!(error_008_checkjs { + args: "run --reload run/error_008_checkjs.js", + exit_code: 1, + output: "run/error_008_checkjs.js.out", + }); + + itest!(error_009_extensions_error { + args: "run run/error_009_extensions_error.js", + output: "run/error_009_extensions_error.js.out", + exit_code: 1, + }); + + itest!(error_011_bad_module_specifier { + args: "run --reload run/error_011_bad_module_specifier.ts", + exit_code: 1, + output: "run/error_011_bad_module_specifier.ts.out", + }); + + itest!(error_012_bad_dynamic_import_specifier { + args: "run --reload --check run/error_012_bad_dynamic_import_specifier.ts", + exit_code: 1, + output: "run/error_012_bad_dynamic_import_specifier.ts.out", + }); + + itest!(error_013_missing_script { + args: "run --reload missing_file_name", + exit_code: 1, + output: "run/error_013_missing_script.out", + }); + + itest!(error_014_catch_dynamic_import_error { + args: + "run --reload --allow-read run/error_014_catch_dynamic_import_error.js", + output: "run/error_014_catch_dynamic_import_error.js.out", + }); + + itest!(error_015_dynamic_import_permissions { + args: "run --reload --quiet run/error_015_dynamic_import_permissions.js", + output: "run/error_015_dynamic_import_permissions.out", + exit_code: 1, + http_server: true, + }); + + // We have an allow-net flag but not allow-read, it should still result in error. + itest!(error_016_dynamic_import_permissions2 { + args: + "run --reload --allow-net run/error_016_dynamic_import_permissions2.js", + output: "run/error_016_dynamic_import_permissions2.out", + exit_code: 1, + http_server: true, + }); + + itest!(error_017_hide_long_source_ts { + args: "run --reload --check run/error_017_hide_long_source_ts.ts", + output: "run/error_017_hide_long_source_ts.ts.out", + exit_code: 1, + }); + + itest!(error_018_hide_long_source_js { + args: "run run/error_018_hide_long_source_js.js", + output: "run/error_018_hide_long_source_js.js.out", + exit_code: 1, + }); + + itest!(error_019_stack_function { + args: "run run/error_019_stack_function.ts", + output: "run/error_019_stack_function.ts.out", + exit_code: 1, + }); + + itest!(error_020_stack_constructor { + args: "run run/error_020_stack_constructor.ts", + output: "run/error_020_stack_constructor.ts.out", + exit_code: 1, + }); + + itest!(error_021_stack_method { + args: "run run/error_021_stack_method.ts", + output: "run/error_021_stack_method.ts.out", + exit_code: 1, + }); + + itest!(error_022_stack_custom_error { + args: "run run/error_022_stack_custom_error.ts", + output: "run/error_022_stack_custom_error.ts.out", + exit_code: 1, + }); + + itest!(error_023_stack_async { + args: "run run/error_023_stack_async.ts", + output: "run/error_023_stack_async.ts.out", + exit_code: 1, + }); + + itest!(error_024_stack_promise_all { + args: "run run/error_024_stack_promise_all.ts", + output: "run/error_024_stack_promise_all.ts.out", + exit_code: 1, + }); + + itest!(error_025_tab_indent { + args: "run run/error_025_tab_indent", + output: "run/error_025_tab_indent.out", + exit_code: 1, + }); + + itest!(error_026_remote_import_error { + args: "run run/error_026_remote_import_error.ts", + output: "run/error_026_remote_import_error.ts.out", + exit_code: 1, + http_server: true, + }); + + itest!(error_for_await { + args: "run --reload --check run/error_for_await.ts", + output: "run/error_for_await.ts.out", + exit_code: 1, + }); + + itest!(error_missing_module_named_import { + args: "run --reload run/error_missing_module_named_import.ts", + output: "run/error_missing_module_named_import.ts.out", + exit_code: 1, + }); + + itest!(error_no_check { + args: "run --reload --no-check run/error_no_check.ts", + output: "run/error_no_check.ts.out", + exit_code: 1, + }); + + itest!(error_syntax { + args: "run --reload run/error_syntax.js", + exit_code: 1, + output: "run/error_syntax.js.out", + }); + + itest!(error_syntax_empty_trailing_line { + args: "run --reload run/error_syntax_empty_trailing_line.mjs", + exit_code: 1, + output: "run/error_syntax_empty_trailing_line.mjs.out", + }); + + itest!(error_type_definitions { + args: "run --reload --check run/error_type_definitions.ts", + exit_code: 1, + output: "run/error_type_definitions.ts.out", + }); + + itest!(error_local_static_import_from_remote_ts { + args: "run --reload http://localhost:4545/run/error_local_static_import_from_remote.ts", + exit_code: 1, + http_server: true, + output: "run/error_local_static_import_from_remote.ts.out", + }); + + itest!(error_local_static_import_from_remote_js { + args: "run --reload http://localhost:4545/run/error_local_static_import_from_remote.js", + exit_code: 1, + http_server: true, + output: "run/error_local_static_import_from_remote.js.out", + }); + + itest!(exit_error42 { + exit_code: 42, + args: "run --quiet --reload run/exit_error42.ts", + output: "run/exit_error42.ts.out", + }); + + itest!(set_exit_code_0 { + args: "run --no-check --unstable run/set_exit_code_0.ts", + output_str: Some(""), + exit_code: 0, + }); + + itest!(set_exit_code_1 { + args: "run --no-check --unstable run/set_exit_code_1.ts", + output_str: Some(""), + exit_code: 42, + }); + + itest!(set_exit_code_2 { + args: "run --no-check --unstable run/set_exit_code_2.ts", + output_str: Some(""), + exit_code: 42, + }); + + itest!(op_exit_op_set_exit_code_in_worker { + args: "run --no-check --unstable --allow-read run/op_exit_op_set_exit_code_in_worker.ts", + exit_code: 21, + output_str: Some(""), +}); + + itest!(deno_exit_tampering { + args: "run --no-check --unstable run/deno_exit_tampering.ts", + output_str: Some(""), + exit_code: 42, + }); + + itest!(heapstats { + args: "run --quiet --unstable --v8-flags=--expose-gc run/heapstats.js", + output: "run/heapstats.js.out", + }); + + itest!(finalization_registry { + args: + "run --quiet --unstable --v8-flags=--expose-gc run/finalization_registry.js", + output: "run/finalization_registry.js.out", +}); + + itest!(https_import { + args: "run --quiet --reload --cert tls/RootCA.pem run/https_import.ts", + output: "run/https_import.ts.out", + http_server: true, + }); + + itest!(if_main { + args: "run --quiet --reload run/if_main.ts", + output: "run/if_main.ts.out", + }); + + itest!(import_meta { + args: "run --quiet --reload --import-map=run/import_meta/importmap.json run/import_meta/main.ts", + output: "run/import_meta/main.out", +}); + + itest!(main_module { + args: "run --quiet --allow-read --reload run/main_module/main.ts", + output: "run/main_module/main.out", + }); + + itest!(no_check { + args: "run --quiet --reload --no-check run/006_url_imports.ts", + output: "run/006_url_imports.ts.out", + http_server: true, + }); + + itest!(no_check_decorators { + args: "run --quiet --reload --no-check run/no_check_decorators.ts", + output: "run/no_check_decorators.ts.out", + }); + + itest!(check_remote { + args: "run --quiet --reload --check=all run/no_check_remote.ts", + output: "run/no_check_remote.ts.disabled.out", + exit_code: 1, + http_server: true, + }); + + itest!(no_check_remote { + args: "run --quiet --reload --no-check=remote run/no_check_remote.ts", + output: "run/no_check_remote.ts.enabled.out", + http_server: true, + }); + + itest!(runtime_decorators { + args: "run --quiet --reload --no-check run/runtime_decorators.ts", + output: "run/runtime_decorators.ts.out", + }); + + itest!(seed_random { + args: "run --seed=100 run/seed_random.js", + output: "run/seed_random.js.out", + }); + + itest!(type_definitions { + args: "run --reload run/type_definitions.ts", + output: "run/type_definitions.ts.out", + }); + + itest!(type_definitions_for_export { + args: "run --reload --check run/type_definitions_for_export.ts", + output: "run/type_definitions_for_export.ts.out", + exit_code: 1, + }); + + itest!(type_directives_01 { + args: "run --reload --check=all -L debug run/type_directives_01.ts", + output: "run/type_directives_01.ts.out", + http_server: true, + }); + + itest!(type_directives_02 { + args: "run --reload --check=all -L debug run/type_directives_02.ts", + output: "run/type_directives_02.ts.out", + }); + + itest!(type_directives_js_main { + args: "run --reload -L debug run/type_directives_js_main.js", + output: "run/type_directives_js_main.js.out", + exit_code: 0, + }); + + itest!(type_directives_redirect { + args: "run --reload --check run/type_directives_redirect.ts", + output: "run/type_directives_redirect.ts.out", + http_server: true, + }); + + itest!(type_headers_deno_types { + args: "run --reload --check run/type_headers_deno_types.ts", + output: "run/type_headers_deno_types.ts.out", + http_server: true, + }); + + itest!(ts_type_imports { + args: "run --reload --check run/ts_type_imports.ts", + output: "run/ts_type_imports.ts.out", + exit_code: 1, + }); + + itest!(ts_decorators { + args: "run --reload --check run/ts_decorators.ts", + output: "run/ts_decorators.ts.out", + }); + + itest!(ts_type_only_import { + args: "run --reload --check run/ts_type_only_import.ts", + output: "run/ts_type_only_import.ts.out", + }); + + itest!(swc_syntax_error { + args: "run --reload --check run/swc_syntax_error.ts", + output: "run/swc_syntax_error.ts.out", + exit_code: 1, + }); + + itest!(unbuffered_stderr { + args: "run --reload run/unbuffered_stderr.ts", + output: "run/unbuffered_stderr.ts.out", + }); + + itest!(unbuffered_stdout { + args: "run --quiet --reload run/unbuffered_stdout.ts", + output: "run/unbuffered_stdout.ts.out", + }); + + itest!(v8_flags_run { + args: "run --v8-flags=--expose-gc run/v8_flags.js", + output: "run/v8_flags.js.out", + }); + + itest!(v8_flags_unrecognized { + args: "repl --v8-flags=--foo,bar,--trace-gc,-baz", + output: "run/v8_flags_unrecognized.out", + exit_code: 1, + }); + + itest!(v8_help { + args: "repl --v8-flags=--help", + output: "run/v8_help.out", + }); + + itest!(unsupported_dynamic_import_scheme { + args: "eval import('xxx:')", + output: "run/unsupported_dynamic_import_scheme.out", + exit_code: 1, + }); + + itest!(wasm { + args: "run --quiet run/wasm.ts", + output: "run/wasm.ts.out", + }); + + itest!(wasm_shared { + args: "run --quiet run/wasm_shared.ts", + output: "run/wasm_shared.out", + }); + + itest!(wasm_async { + args: "run run/wasm_async.js", + output: "run/wasm_async.out", + }); + + itest!(wasm_unreachable { + args: "run --allow-read run/wasm_unreachable.js", + output: "run/wasm_unreachable.out", + exit_code: 1, + }); + + itest!(wasm_url { + args: "run --quiet --allow-net=localhost:4545 run/wasm_url.js", + output: "run/wasm_url.out", + exit_code: 1, + http_server: true, + }); + + itest!(weakref { + args: "run --quiet --reload run/weakref.ts", + output: "run/weakref.ts.out", + }); + + itest!(top_level_await_order { + args: "run --allow-read run/top_level_await/order.js", + output: "run/top_level_await/order.out", + }); + + itest!(top_level_await_loop { + args: "run --allow-read run/top_level_await/loop.js", + output: "run/top_level_await/loop.out", + }); + + itest!(top_level_await_circular { + args: "run --allow-read run/top_level_await/circular.js", + output: "run/top_level_await/circular.out", + exit_code: 1, + }); + + // Regression test for https://github.com/denoland/deno/issues/11238. + itest!(top_level_await_nested { + args: "run --allow-read run/top_level_await/nested/main.js", + output: "run/top_level_await/nested.out", + }); + + itest!(top_level_await_unresolved { + args: "run run/top_level_await/unresolved.js", + output: "run/top_level_await/unresolved.out", + exit_code: 1, + }); + + itest!(top_level_await { + args: "run --allow-read run/top_level_await/top_level_await.js", + output: "run/top_level_await/top_level_await.out", + }); + + itest!(top_level_await_ts { + args: "run --quiet --allow-read run/top_level_await/top_level_await.ts", + output: "run/top_level_await/top_level_await.out", + }); + + itest!(top_level_for_await { + args: "run --quiet run/top_level_await/top_level_for_await.js", + output: "run/top_level_await/top_level_for_await.out", + }); + + itest!(top_level_for_await_ts { + args: "run --quiet run/top_level_await/top_level_for_await.ts", + output: "run/top_level_await/top_level_for_await.out", + }); + + itest!(unstable_disabled { + args: "run --reload --check run/unstable.ts", + exit_code: 1, + output: "run/unstable_disabled.out", + }); + + itest!(unstable_enabled { + args: "run --quiet --reload --unstable run/unstable.ts", + output: "run/unstable_enabled.out", + }); + + itest!(unstable_disabled_js { + args: "run --reload run/unstable.js", + output: "run/unstable_disabled_js.out", + }); + + itest!(unstable_enabled_js { + args: "run --quiet --reload --unstable run/unstable.ts", + output: "run/unstable_enabled_js.out", + }); + + itest!(unstable_worker { + args: "run --reload --unstable --quiet --allow-read run/unstable_worker.ts", + output: "run/unstable_worker.ts.out", + }); + + itest!(import_compression { + args: "run --quiet --reload --allow-net run/import_compression/main.ts", + output: "run/import_compression/main.out", + http_server: true, + }); + + itest!(disallow_http_from_https_js { + args: "run --quiet --reload --cert tls/RootCA.pem https://localhost:5545/run/disallow_http_from_https.js", + output: "run/disallow_http_from_https_js.out", + http_server: true, + exit_code: 1, +}); + + itest!(disallow_http_from_https_ts { + args: "run --quiet --reload --cert tls/RootCA.pem https://localhost:5545/run/disallow_http_from_https.ts", + output: "run/disallow_http_from_https_ts.out", + http_server: true, + exit_code: 1, +}); + + itest!(dynamic_import_conditional { + args: "run --quiet --reload run/dynamic_import_conditional.js", + output: "run/dynamic_import_conditional.js.out", + }); + + itest!(tsx_imports { + args: "run --reload --check run/tsx_imports/tsx_imports.ts", + output: "run/tsx_imports/tsx_imports.ts.out", + }); + + itest!(fix_dynamic_import_errors { + args: "run --reload run/fix_dynamic_import_errors.js", + output: "run/fix_dynamic_import_errors.js.out", + }); + + itest!(fix_emittable_skipped { + args: "run --reload run/fix_emittable_skipped.js", + output: "run/fix_emittable_skipped.ts.out", + }); + + itest!(fix_js_import_js { + args: "run --quiet --reload run/fix_js_import_js.ts", + output: "run/fix_js_import_js.ts.out", + }); + + itest!(fix_js_imports { + args: "run --quiet --reload run/fix_js_imports.ts", + output: "run/fix_js_imports.ts.out", + }); + + itest!(fix_tsc_file_exists { + args: "run --quiet --reload tsc/test.js", + output: "run/fix_tsc_file_exists.out", + }); + + itest!(fix_worker_dispatchevent { + args: "run --quiet --reload run/fix_worker_dispatchevent.ts", + output: "run/fix_worker_dispatchevent.ts.out", + }); + + itest!(es_private_fields { + args: "run --quiet --reload run/es_private_fields.js", + output: "run/es_private_fields.js.out", + }); + + itest!(cjs_imports { + args: "run --quiet --reload run/cjs_imports/main.ts", + output: "run/cjs_imports/main.out", + }); + + itest!(ts_import_from_js { + args: "run --quiet --reload run/ts_import_from_js/main.js", + output: "run/ts_import_from_js/main.out", + http_server: true, + }); + + itest!(jsx_import_from_ts { + args: "run --quiet --reload run/jsx_import_from_ts.ts", + output: "run/jsx_import_from_ts.ts.out", + }); + + itest!(jsx_import_source_pragma { + args: "run --reload run/jsx_import_source_pragma.tsx", + output: "run/jsx_import_source.out", + http_server: true, + }); + + itest!(jsx_import_source_pragma_with_config { + args: + "run --reload --config jsx/deno-jsx.jsonc --no-lock run/jsx_import_source_pragma.tsx", + output: "run/jsx_import_source.out", + http_server: true, +}); + + itest!(jsx_import_source_pragma_with_dev_config { + args: + "run --reload --config jsx/deno-jsxdev.jsonc --no-lock run/jsx_import_source_pragma.tsx", + output: "run/jsx_import_source_dev.out", + http_server: true, +}); + + itest!(jsx_import_source_no_pragma { + args: + "run --reload --config jsx/deno-jsx.jsonc --no-lock run/jsx_import_source_no_pragma.tsx", + output: "run/jsx_import_source.out", + http_server: true, +}); + + itest!(jsx_import_source_no_pragma_dev { + args: "run --reload --config jsx/deno-jsxdev.jsonc --no-lock run/jsx_import_source_no_pragma.tsx", + output: "run/jsx_import_source_dev.out", + http_server: true, +}); + + itest!(jsx_import_source_pragma_import_map { + args: "run --reload --import-map jsx/import-map.json run/jsx_import_source_pragma_import_map.tsx", + output: "run/jsx_import_source_import_map.out", + http_server: true, +}); + + itest!(jsx_import_source_pragma_import_map_dev { + args: "run --reload --import-map jsx/import-map.json --config jsx/deno-jsxdev-import-map.jsonc run/jsx_import_source_pragma_import_map.tsx", + output: "run/jsx_import_source_import_map_dev.out", + http_server: true, +}); + + itest!(jsx_import_source_import_map { + args: "run --reload --import-map jsx/import-map.json --no-lock --config jsx/deno-jsx-import-map.jsonc run/jsx_import_source_no_pragma.tsx", + output: "run/jsx_import_source_import_map.out", + http_server: true, +}); + + itest!(jsx_import_source_import_map_dev { + args: "run --reload --import-map jsx/import-map.json --no-lock --config jsx/deno-jsxdev-import-map.jsonc run/jsx_import_source_no_pragma.tsx", + output: "run/jsx_import_source_import_map_dev.out", + http_server: true, +}); + + itest!(jsx_import_source_import_map_scoped { + args: "run --reload --import-map jsx/import-map-scoped.json --no-lock --config jsx/deno-jsx-import-map.jsonc subdir/jsx_import_source_no_pragma.tsx", + output: "run/jsx_import_source_import_map.out", + http_server: true, +}); + + itest!(jsx_import_source_import_map_scoped_dev { + args: "run --reload --import-map jsx/import-map-scoped.json --no-lock --config jsx/deno-jsxdev-import-map.jsonc subdir/jsx_import_source_no_pragma.tsx", + output: "run/jsx_import_source_import_map_dev.out", + http_server: true, +}); + + itest!(jsx_import_source_pragma_no_check { + args: "run --reload --no-check run/jsx_import_source_pragma.tsx", + output: "run/jsx_import_source.out", + http_server: true, + }); + + itest!(jsx_import_source_pragma_with_config_no_check { + args: "run --reload --config jsx/deno-jsx.jsonc --no-lock --no-check run/jsx_import_source_pragma.tsx", + output: "run/jsx_import_source.out", + http_server: true, +}); + + itest!(jsx_import_source_no_pragma_no_check { + args: + "run --reload --config jsx/deno-jsx.jsonc --no-lock --no-check run/jsx_import_source_no_pragma.tsx", + output: "run/jsx_import_source.out", + http_server: true, +}); + + itest!(jsx_import_source_pragma_import_map_no_check { + args: "run --reload --import-map jsx/import-map.json --no-check run/jsx_import_source_pragma_import_map.tsx", + output: "run/jsx_import_source_import_map.out", + http_server: true, +}); + + itest!(jsx_import_source_import_map_no_check { + args: "run --reload --import-map jsx/import-map.json --no-lock --config jsx/deno-jsx-import-map.jsonc --no-check run/jsx_import_source_no_pragma.tsx", + output: "run/jsx_import_source_import_map.out", + http_server: true, +}); + + itest!(jsx_import_source_error { + args: "run --config jsx/deno-jsx-error.jsonc --check run/jsx_import_source_no_pragma.tsx", + output: "run/jsx_import_source_error.out", + exit_code: 1, +}); + + // TODO(#11128): Flaky. Re-enable later. + // itest!(single_compile_with_reload { + // args: "run --relcert/oad --allow-read run/single_compile_with_reload.ts", + // output: "run/single_compile_with_reload.ts.out", + // }); + + itest!(proto_exploit { + args: "run run/proto_exploit.js", + output: "run/proto_exploit.js.out", + }); + + itest!(reference_types { + args: "run --reload --quiet run/reference_types.ts", + output: "run/reference_types.ts.out", + }); + + itest!(references_types_remote { + http_server: true, + args: "run --reload --quiet run/reference_types_remote.ts", + output: "run/reference_types_remote.ts.out", + }); + + itest!(reference_types_error { + args: + "run --config run/checkjs.tsconfig.json --check run/reference_types_error.js", + output: "run/reference_types_error.js.out", + exit_code: 1, +}); + + itest!(reference_types_error_no_check { + args: "run --no-check run/reference_types_error.js", + output_str: Some(""), + }); + + itest!(import_data_url_error_stack { + args: "run --quiet --reload run/import_data_url_error_stack.ts", + output: "run/import_data_url_error_stack.ts.out", + exit_code: 1, + }); + + itest!(import_data_url_import_relative { + args: "run --quiet --reload run/import_data_url_import_relative.ts", + output: "run/import_data_url_import_relative.ts.out", + exit_code: 1, + }); + + itest!(import_data_url_import_map { + args: "run --quiet --reload --import-map import_maps/import_map.json run/import_data_url.ts", + output: "run/import_data_url.ts.out", + }); + + itest!(import_data_url_imports { + args: "run --quiet --reload run/import_data_url_imports.ts", + output: "run/import_data_url_imports.ts.out", + http_server: true, + }); + + itest!(import_data_url_jsx { + args: "run --quiet --reload run/import_data_url_jsx.ts", + output: "run/import_data_url_jsx.ts.out", + }); + + itest!(import_data_url { + args: "run --quiet --reload run/import_data_url.ts", + output: "run/import_data_url.ts.out", + }); + + itest!(import_dynamic_data_url { + args: "run --quiet --reload run/import_dynamic_data_url.ts", + output: "run/import_dynamic_data_url.ts.out", + }); + + itest!(import_blob_url_error_stack { + args: "run --quiet --reload run/import_blob_url_error_stack.ts", + output: "run/import_blob_url_error_stack.ts.out", + exit_code: 1, + }); + + itest!(import_blob_url_import_relative { + args: "run --quiet --reload run/import_blob_url_import_relative.ts", + output: "run/import_blob_url_import_relative.ts.out", + exit_code: 1, + }); + + itest!(import_blob_url_imports { + args: + "run --quiet --reload --allow-net=localhost:4545 run/import_blob_url_imports.ts", + output: "run/import_blob_url_imports.ts.out", + http_server: true, +}); + + itest!(import_blob_url_jsx { + args: "run --quiet --reload run/import_blob_url_jsx.ts", + output: "run/import_blob_url_jsx.ts.out", + }); + + itest!(import_blob_url { + args: "run --quiet --reload run/import_blob_url.ts", + output: "run/import_blob_url.ts.out", + }); + + itest!(import_file_with_colon { + args: "run --quiet --reload run/import_file_with_colon.ts", + output: "run/import_file_with_colon.ts.out", + http_server: true, + }); + + itest!(import_extensionless { + args: "run --quiet --reload run/import_extensionless.ts", + output: "run/import_extensionless.ts.out", + http_server: true, + }); + + itest!(classic_workers_event_loop { + args: + "run --enable-testing-features-do-not-use run/classic_workers_event_loop.js", + output: "run/classic_workers_event_loop.js.out", +}); + + // FIXME(bartlomieju): disabled, because this test is very flaky on CI + // itest!(local_sources_not_cached_in_memory { + // args: "run --allow-read --allow-write run/no_mem_cache.js", + // output: "run/no_mem_cache.js.out", + // }); + + // This test checks that inline source map data is used. It uses a hand crafted + // source map that maps to a file that exists, but is not loaded into the module + // graph (inline_js_source_map_2.ts) (because there are no direct dependencies). + // Source line is not remapped because no inline source contents are included in + // the sourcemap and the file is not present in the dependency graph. + itest!(inline_js_source_map_2 { + args: "run --quiet run/inline_js_source_map_2.js", + output: "run/inline_js_source_map_2.js.out", + exit_code: 1, + }); + + // This test checks that inline source map data is used. It uses a hand crafted + // source map that maps to a file that exists, but is not loaded into the module + // graph (inline_js_source_map_2.ts) (because there are no direct dependencies). + // Source line remapped using th inline source contents that are included in the + // inline source map. + itest!(inline_js_source_map_2_with_inline_contents { + args: "run --quiet run/inline_js_source_map_2_with_inline_contents.js", + output: "run/inline_js_source_map_2_with_inline_contents.js.out", + exit_code: 1, + }); + + // This test checks that inline source map data is used. It uses a hand crafted + // source map that maps to a file that exists, and is loaded into the module + // graph because of a direct import statement (inline_js_source_map.ts). The + // source map was generated from an earlier version of this file, where the throw + // was not commented out. The source line is remapped using source contents that + // from the module graph. + itest!(inline_js_source_map_with_contents_from_graph { + args: "run --quiet run/inline_js_source_map_with_contents_from_graph.js", + output: "run/inline_js_source_map_with_contents_from_graph.js.out", + exit_code: 1, + http_server: true, + }); + + // This test ensures that a descriptive error is shown when we're unable to load + // the import map. Even though this tests only the `run` subcommand, we can be sure + // that the error message is similar for other subcommands as they all use + // `program_state.maybe_import_map` to access the import map underneath. + itest!(error_import_map_unable_to_load { + args: + "run --import-map=import_maps/does_not_exist.json import_maps/test.ts", + output: "run/error_import_map_unable_to_load.out", + exit_code: 1, + }); + + // Test that setting `self` in the main thread to some other value doesn't break + // the world. + itest!(replace_self { + args: "run run/replace_self.js", + output: "run/replace_self.js.out", + }); + + itest!(worker_event_handler_test { + args: "run --quiet --reload --allow-read run/worker_event_handler_test.js", + output: "run/worker_event_handler_test.js.out", + }); + + itest!(worker_close_race { + args: "run --quiet --reload --allow-read run/worker_close_race.js", + output: "run/worker_close_race.js.out", + }); + + itest!(worker_drop_handle_race { + args: "run --quiet --reload --allow-read run/worker_drop_handle_race.js", + output: "run/worker_drop_handle_race.js.out", + exit_code: 1, + }); + + itest!(worker_drop_handle_race_terminate { + args: "run --unstable run/worker_drop_handle_race_terminate.js", + output: "run/worker_drop_handle_race_terminate.js.out", + }); + + itest!(worker_close_nested { + args: "run --quiet --reload --allow-read run/worker_close_nested.js", + output: "run/worker_close_nested.js.out", + }); + + itest!(worker_message_before_close { + args: + "run --quiet --reload --allow-read run/worker_message_before_close.js", + output: "run/worker_message_before_close.js.out", + }); + + itest!(worker_close_in_wasm_reactions { + args: + "run --quiet --reload --allow-read run/worker_close_in_wasm_reactions.js", + output: "run/worker_close_in_wasm_reactions.js.out", + }); + + itest!(shebang_tsc { + args: "run --quiet --check run/shebang.ts", + output: "run/shebang.ts.out", + }); + + itest!(shebang_swc { + args: "run --quiet run/shebang.ts", + output: "run/shebang.ts.out", + }); + + itest!(shebang_with_json_imports_tsc { + args: "run --quiet import_assertions/json_with_shebang.ts", + output: "import_assertions/json_with_shebang.ts.out", + exit_code: 1, + }); + + itest!(shebang_with_json_imports_swc { + args: "run --quiet --no-check import_assertions/json_with_shebang.ts", + output: "import_assertions/json_with_shebang.ts.out", + exit_code: 1, + }); + + #[test] + fn no_validate_asm() { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("run/no_validate_asm.js") + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert!(output.stderr.is_empty()); + assert!(output.stdout.is_empty()); + } + + #[test] + fn exec_path() { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--allow-read") + .arg("run/exec_path.ts") + .stdout(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let stdout_str = std::str::from_utf8(&output.stdout).unwrap().trim(); + let actual = + std::fs::canonicalize(std::path::Path::new(stdout_str)).unwrap(); + let expected = std::fs::canonicalize(util::deno_exe_path()).unwrap(); + assert_eq!(expected, actual); + } + + #[cfg(windows)] + // Clippy suggests to remove the `NoStd` prefix from all variants. I disagree. + #[allow(clippy::enum_variant_names)] + enum WinProcConstraints { + NoStdIn, + NoStdOut, + NoStdErr, + } + + #[cfg(windows)] + fn run_deno_script_constrained( + script_path: std::path::PathBuf, + constraints: WinProcConstraints, + ) -> Result<(), i64> { + let file_path = "assets/DenoWinRunner.ps1"; + let constraints = match constraints { + WinProcConstraints::NoStdIn => "1", + WinProcConstraints::NoStdOut => "2", + WinProcConstraints::NoStdErr => "4", + }; + let deno_exe_path = util::deno_exe_path() + .into_os_string() + .into_string() + .unwrap(); + + let deno_script_path = script_path.into_os_string().into_string().unwrap(); + + let args = vec![&deno_exe_path[..], &deno_script_path[..], constraints]; + util::run_powershell_script_file(file_path, args) + } + + #[cfg(windows)] + #[test] + fn should_not_panic_on_no_stdin() { + let output = run_deno_script_constrained( + util::testdata_path().join("echo.ts"), + WinProcConstraints::NoStdIn, + ); + output.unwrap(); + } + + #[cfg(windows)] + #[test] + fn should_not_panic_on_no_stdout() { + let output = run_deno_script_constrained( + util::testdata_path().join("echo.ts"), + WinProcConstraints::NoStdOut, + ); + output.unwrap(); + } + + #[cfg(windows)] + #[test] + fn should_not_panic_on_no_stderr() { + let output = run_deno_script_constrained( + util::testdata_path().join("echo.ts"), + WinProcConstraints::NoStdErr, + ); + output.unwrap(); + } + + #[cfg(not(windows))] + #[test] + fn should_not_panic_on_undefined_home_environment_variable() { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("echo.ts") + .env_remove("HOME") + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + } + + #[test] + fn should_not_panic_on_undefined_deno_dir_environment_variable() { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("echo.ts") + .env_remove("DENO_DIR") + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + } + + #[cfg(not(windows))] + #[test] + fn should_not_panic_on_undefined_deno_dir_and_home_environment_variables() { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("echo.ts") + .env_remove("DENO_DIR") + .env_remove("HOME") + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + } + + #[test] + fn rust_log() { + // Without RUST_LOG the stderr is empty. + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("run/001_hello.js") + .stderr(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert!(output.stderr.is_empty()); + + // With RUST_LOG the stderr is not empty. + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("run/001_hello.js") + .env("RUST_LOG", "debug") + .stderr(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + assert!(!output.stderr.is_empty()); + } + + #[test] + fn dont_cache_on_check_fail() { + let deno_dir = util::new_deno_dir(); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--check=all") + .arg("--reload") + .arg("run/error_003_typescript.ts") + .stderr(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + assert!(!output.stderr.is_empty()); + + let mut deno_cmd = util::deno_cmd_with_deno_dir(&deno_dir); + let output = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--check=all") + .arg("run/error_003_typescript.ts") + .stderr(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + assert!(!output.stderr.is_empty()); + } + + mod permissions { + use super::itest; + use test_util as util; + + // TODO(bartlomieju): remove --unstable once Deno.spawn is stabilized + #[test] + fn with_allow() { + for permission in &util::PERMISSION_VARIANTS { + let status = util::deno_cmd() + .current_dir(&util::testdata_path()) + .arg("run") + .arg("--unstable") + .arg(format!("--allow-{0}", permission)) + .arg("run/permission_test.ts") + .arg(format!("{0}Required", permission)) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + } + + // TODO(bartlomieju): remove --unstable once Deno.spawn is stabilized + #[test] + fn without_allow() { + for permission in &util::PERMISSION_VARIANTS { + let (_, err) = util::run_and_collect_output( + false, + &format!( + "run --unstable run/permission_test.ts {0}Required", + permission + ), + None, + None, + false, + ); + assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); + } + } + + #[test] + fn rw_inside_project_dir() { + const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; + for permission in &PERMISSION_VARIANTS { + let status = util::deno_cmd() + .current_dir(&util::testdata_path()) + .arg("run") + .arg(format!( + "--allow-{0}={1}", + permission, + util::testdata_path() + .into_os_string() + .into_string() + .unwrap() + )) + .arg("run/complex_permissions_test.ts") + .arg(permission) + .arg("run/complex_permissions_test.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + } + + #[test] + fn rw_outside_test_dir() { + const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; + for permission in &PERMISSION_VARIANTS { + let (_, err) = util::run_and_collect_output( + false, + &format!( + "run --allow-{0}={1} run/complex_permissions_test.ts {0} {2}", + permission, + util::testdata_path() + .into_os_string() + .into_string() + .unwrap(), + util::root_path() + .join("Cargo.toml") + .into_os_string() + .into_string() + .unwrap(), + ), + None, + None, + false, + ); + assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); + } + } + + #[test] + fn rw_inside_test_dir() { + const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; + for permission in &PERMISSION_VARIANTS { + let status = util::deno_cmd() + .current_dir(&util::testdata_path()) + .arg("run") + .arg(format!( + "--allow-{0}={1}", + permission, + util::testdata_path() + .into_os_string() + .into_string() + .unwrap() + )) + .arg("run/complex_permissions_test.ts") + .arg(permission) + .arg("run/complex_permissions_test.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + } + + #[test] + fn rw_outside_test_and_js_dir() { + const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; + let test_dir = util::testdata_path() + .into_os_string() + .into_string() + .unwrap(); + let js_dir = util::root_path() + .join("js") + .into_os_string() + .into_string() + .unwrap(); + for permission in &PERMISSION_VARIANTS { + let (_, err) = util::run_and_collect_output( + false, + &format!( + "run --allow-{0}={1},{2} run/complex_permissions_test.ts {0} {3}", + permission, + test_dir, + js_dir, + util::root_path() + .join("Cargo.toml") + .into_os_string() + .into_string() + .unwrap(), + ), + None, + None, + false, + ); + assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); + } + } + + #[test] + fn rw_inside_test_and_js_dir() { + const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; + let test_dir = util::testdata_path() + .into_os_string() + .into_string() + .unwrap(); + let js_dir = util::root_path() + .join("js") + .into_os_string() + .into_string() + .unwrap(); + for permission in &PERMISSION_VARIANTS { + let status = util::deno_cmd() + .current_dir(&util::testdata_path()) + .arg("run") + .arg(format!("--allow-{0}={1},{2}", permission, test_dir, js_dir)) + .arg("run/complex_permissions_test.ts") + .arg(permission) + .arg("run/complex_permissions_test.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + } + + #[test] + fn rw_relative() { + const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; + for permission in &PERMISSION_VARIANTS { + let status = util::deno_cmd() + .current_dir(&util::testdata_path()) + .arg("run") + .arg(format!("--allow-{0}=.", permission)) + .arg("run/complex_permissions_test.ts") + .arg(permission) + .arg("run/complex_permissions_test.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + } + + #[test] + fn rw_no_prefix() { + const PERMISSION_VARIANTS: [&str; 2] = ["read", "write"]; + for permission in &PERMISSION_VARIANTS { + let status = util::deno_cmd() + .current_dir(&util::testdata_path()) + .arg("run") + .arg(format!("--allow-{0}=tls/../", permission)) + .arg("run/complex_permissions_test.ts") + .arg(permission) + .arg("run/complex_permissions_test.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + } + + #[test] + fn net_fetch_allow_localhost_4545() { + let (_, err) = util::run_and_collect_output( + true, + "run --allow-net=localhost:4545 run/complex_permissions_test.ts netFetch http://localhost:4545/", + None, + None, + true, + ); + assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn net_fetch_allow_deno_land() { + let (_, err) = util::run_and_collect_output( + false, + "run --allow-net=deno.land run/complex_permissions_test.ts netFetch http://localhost:4545/", + None, + None, + true, + ); + assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn net_fetch_localhost_4545_fail() { + let (_, err) = util::run_and_collect_output( + false, + "run --allow-net=localhost:4545 run/complex_permissions_test.ts netFetch http://localhost:4546/", + None, + None, + true, + ); + assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn net_fetch_localhost() { + let (_, err) = util::run_and_collect_output( + true, + "run --allow-net=localhost run/complex_permissions_test.ts netFetch http://localhost:4545/ http://localhost:4546/ http://localhost:4547/", + None, + None, + true, + ); + assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn net_connect_allow_localhost_ip_4555() { + let (_, err) = util::run_and_collect_output( + true, + "run --allow-net=127.0.0.1:4545 run/complex_permissions_test.ts netConnect 127.0.0.1:4545", + None, + None, + true, + ); + assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn net_connect_allow_deno_land() { + let (_, err) = util::run_and_collect_output( + false, + "run --allow-net=deno.land run/complex_permissions_test.ts netConnect 127.0.0.1:4546", + None, + None, + true, + ); + assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn net_connect_allow_localhost_ip_4545_fail() { + let (_, err) = util::run_and_collect_output( + false, + "run --allow-net=127.0.0.1:4545 run/complex_permissions_test.ts netConnect 127.0.0.1:4546", + None, + None, + true, + ); + assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn net_connect_allow_localhost_ip() { + let (_, err) = util::run_and_collect_output( + true, + "run --allow-net=127.0.0.1 run/complex_permissions_test.ts netConnect 127.0.0.1:4545 127.0.0.1:4546 127.0.0.1:4547", + None, + None, + true, + ); + assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn net_listen_allow_localhost_4555() { + let (_, err) = util::run_and_collect_output( + true, + "run --allow-net=localhost:4558 run/complex_permissions_test.ts netListen localhost:4558", + None, + None, + false, + ); + assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn net_listen_allow_deno_land() { + let (_, err) = util::run_and_collect_output( + false, + "run --allow-net=deno.land run/complex_permissions_test.ts netListen localhost:4545", + None, + None, + false, + ); + assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn net_listen_allow_localhost_4555_fail() { + let (_, err) = util::run_and_collect_output( + false, + "run --allow-net=localhost:4555 run/complex_permissions_test.ts netListen localhost:4556", + None, + None, + false, + ); + assert!(err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn net_listen_allow_localhost() { + // Port 4600 is chosen to not colide with those used by + // target/debug/test_server + let (_, err) = util::run_and_collect_output( + true, + "run --allow-net=localhost run/complex_permissions_test.ts netListen localhost:4600", + None, + None, + false, + ); + assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); + } + + #[test] + fn _061_permissions_request() { + let args = "run --quiet run/061_permissions_request.ts"; + use util::PtyData::*; + util::test_pty2(args, vec![ + Output("⚠️ ️Deno requests read access to \"foo\". Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)] "), + Input("y\n"), + Output("⚠️ ️Deno requests read access to \"bar\". Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), + Input("n\n"), + Output("granted\r\n"), + Output("prompt\r\n"), + Output("denied\r\n"), + ]); + } + + #[test] + fn _062_permissions_request_global() { + let args = "run --quiet run/062_permissions_request_global.ts"; + use util::PtyData::*; + util::test_pty2(args, vec![ + Output("⚠️ ️Deno requests read access. Run again with --allow-read to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)] "), + Input("y\n"), + Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"), + Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"), + Output("PermissionStatus { state: \"granted\", onchange: null }\r\n"), + ]); + } + + itest!(_063_permissions_revoke { + args: "run --allow-read=foo,bar run/063_permissions_revoke.ts", + output: "run/063_permissions_revoke.ts.out", + }); + + itest!(_064_permissions_revoke_global { + args: "run --allow-read=foo,bar run/064_permissions_revoke_global.ts", + output: "run/064_permissions_revoke_global.ts.out", + }); + + #[test] + fn _066_prompt() { + let args = "run --quiet --unstable run/066_prompt.ts"; + use util::PtyData::*; + util::test_pty2( + args, + vec![ + Output("What is your name? [Jane Doe] "), + Input("John Doe\n"), + Output("Your name is John Doe.\r\n"), + Output("What is your name? [Jane Doe] "), + Input("\n"), + Output("Your name is Jane Doe.\r\n"), + Output("Prompt "), + Input("foo\n"), + Output("Your input is foo.\r\n"), + Output("Question 0 [y/N] "), + Input("Y\n"), + Output("Your answer is true\r\n"), + Output("Question 1 [y/N] "), + Input("N\n"), + Output("Your answer is false\r\n"), + Output("Question 2 [y/N] "), + Input("yes\n"), + Output("Your answer is false\r\n"), + Output("Confirm [y/N] "), + Input("\n"), + Output("Your answer is false\r\n"), + Output("What is Windows EOL? "), + Input("windows\n"), + Output("Your answer is \"windows\"\r\n"), + Output("Hi [Enter] "), + Input("\n"), + Output("Alert [Enter] "), + Input("\n"), + Output("The end of test\r\n"), + Output("What is EOF? "), + Input("\n"), + Output("Your answer is null\r\n"), + ], + ); + } + + itest!(dynamic_import_permissions_remote_remote { + args: "run --quiet --reload --allow-net=localhost:4545 dynamic_import/permissions_remote_remote.ts", + output: "dynamic_import/permissions_remote_remote.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(dynamic_import_permissions_data_remote { + args: "run --quiet --reload --allow-net=localhost:4545 dynamic_import/permissions_data_remote.ts", + output: "dynamic_import/permissions_data_remote.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(dynamic_import_permissions_blob_remote { + args: "run --quiet --reload --allow-net=localhost:4545 dynamic_import/permissions_blob_remote.ts", + output: "dynamic_import/permissions_blob_remote.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(dynamic_import_permissions_data_local { + args: "run --quiet --reload --allow-net=localhost:4545 dynamic_import/permissions_data_local.ts", + output: "dynamic_import/permissions_data_local.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(dynamic_import_permissions_blob_local { + args: "run --quiet --reload --allow-net=localhost:4545 dynamic_import/permissions_blob_local.ts", + output: "dynamic_import/permissions_blob_local.ts.out", + http_server: true, + exit_code: 1, + }); + } + + itest!(tls_starttls { + args: "run --quiet --reload --allow-net --allow-read --unstable --cert tls/RootCA.pem run/tls_starttls.js", + output: "run/tls.out", +}); + + itest!(tls_connecttls { + args: "run --quiet --reload --allow-net --allow-read --cert tls/RootCA.pem run/tls_connecttls.js", + output: "run/tls.out", +}); + + itest!(byte_order_mark { + args: "run --no-check run/byte_order_mark.ts", + output: "run/byte_order_mark.out", + }); + + #[test] + fn issue9750() { + use util::PtyData::*; + util::test_pty2( + "run --prompt run/issue9750.js", + vec![ + Output("Enter 'yy':\r\n"), + Input("yy\n"), + Output("⚠️ ️Deno requests env access. Run again with --allow-env to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), + Input("n\n"), + Output("⚠️ ️Deno requests env access to \"SECRET\". Run again with --allow-env to bypass this prompt.\r\n Allow? [y/n (y = yes allow, n = no deny)]"), + Input("n\n"), + Output("error: Uncaught (in promise) PermissionDenied: Requires env access to \"SECRET\", run again with the --allow-env flag\r\n"), + ], + ); + } + + // Regression test for https://github.com/denoland/deno/issues/11451. + itest!(dom_exception_formatting { + args: "run run/dom_exception_formatting.ts", + output: "run/dom_exception_formatting.ts.out", + exit_code: 1, + }); + + itest!(long_data_url_formatting { + args: "run run/long_data_url_formatting.ts", + output: "run/long_data_url_formatting.ts.out", + exit_code: 1, + }); + + itest!(eval_context_throw_dom_exception { + args: "run run/eval_context_throw_dom_exception.js", + output: "run/eval_context_throw_dom_exception.js.out", + }); + + #[test] + #[cfg(unix)] + fn navigator_language_unix() { + let (res, _) = util::run_and_collect_output( + true, + "run navigator_language.ts", + None, + Some(vec![("LC_ALL".to_owned(), "pl_PL".to_owned())]), + false, + ); + assert_eq!(res, "pl-PL\n") + } + + #[test] + fn navigator_language() { + let (res, _) = util::run_and_collect_output( + true, + "run navigator_language.ts", + None, + None, + false, + ); + assert!(!res.is_empty()) + } + + #[test] + #[cfg(unix)] + fn navigator_languages_unix() { + let (res, _) = util::run_and_collect_output( + true, + "run navigator_languages.ts", + None, + Some(vec![ + ("LC_ALL".to_owned(), "pl_PL".to_owned()), + ("NO_COLOR".to_owned(), "1".to_owned()), + ]), + false, + ); + assert_eq!(res, "[ \"pl-PL\" ]\n") + } + + #[test] + fn navigator_languages() { + let (res, _) = util::run_and_collect_output( + true, + "run navigator_languages.ts", + None, + None, + false, + ); + assert!(!res.is_empty()) + } + + /// Regression test for https://github.com/denoland/deno/issues/12740. + #[test] + fn issue12740() { + let mod_dir = TempDir::new(); + let mod1_path = mod_dir.path().join("mod1.ts"); + let mod2_path = mod_dir.path().join("mod2.ts"); + let mut deno_cmd = util::deno_cmd(); + std::fs::write(&mod1_path, "").unwrap(); + let status = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg(&mod1_path) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + std::fs::write(&mod1_path, "export { foo } from \"./mod2.ts\";").unwrap(); + std::fs::write(&mod2_path, "(").unwrap(); + let status = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg(&mod1_path) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(!status.success()); + } + + /// Regression test for https://github.com/denoland/deno/issues/12807. + #[test] + fn issue12807() { + let mod_dir = TempDir::new(); + let mod1_path = mod_dir.path().join("mod1.ts"); + let mod2_path = mod_dir.path().join("mod2.ts"); + let mut deno_cmd = util::deno_cmd(); + // With a fresh `DENO_DIR`, run a module with a dependency and a type error. + std::fs::write(&mod1_path, "import './mod2.ts'; Deno.exit('0');").unwrap(); + std::fs::write(&mod2_path, "console.log('Hello, world!');").unwrap(); + let status = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--check") + .arg(&mod1_path) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(!status.success()); + // Fix the type error and run again. + std::fs::write(&mod1_path, "import './mod2.ts'; Deno.exit(0);").unwrap(); + let status = deno_cmd + .current_dir(util::testdata_path()) + .arg("run") + .arg("--check") + .arg(&mod1_path) + .stderr(Stdio::null()) + .stdout(Stdio::null()) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + + itest!(issue_13562 { + args: "run run/issue13562.ts", + output: "run/issue13562.ts.out", + }); + + itest!(import_assertions_static_import { + args: "run --allow-read import_assertions/static_import.ts", + output: "import_assertions/static_import.out", + }); + + itest!(import_assertions_static_export { + args: "run --allow-read import_assertions/static_export.ts", + output: "import_assertions/static_export.out", + }); + + itest!(import_assertions_static_error { + args: "run --allow-read import_assertions/static_error.ts", + output: "import_assertions/static_error.out", + exit_code: 1, + }); + + itest!(import_assertions_dynamic_import { + args: "run --allow-read import_assertions/dynamic_import.ts", + output: "import_assertions/dynamic_import.out", + }); + + itest!(import_assertions_dynamic_error { + args: "run --allow-read import_assertions/dynamic_error.ts", + output: "import_assertions/dynamic_error.out", + exit_code: 1, + }); + + itest!(import_assertions_type_check { + args: "run --allow-read --check import_assertions/type_check.ts", + output: "import_assertions/type_check.out", + exit_code: 1, + }); + + itest!(delete_window { + args: "run run/delete_window.js", + output_str: Some("true\n"), + }); + + itest!(colors_without_global_this { + args: "run run/colors_without_globalThis.js", + output_str: Some("true\n"), + }); + + itest!(config_auto_discovered_for_local_script { + args: "run --quiet run/with_config/frontend_work.ts", + output_str: Some("ok\n"), + }); + + itest!(no_config_auto_discovery_for_local_script { + args: "run --quiet --no-config --check run/with_config/frontend_work.ts", + output: "run/with_config/no_auto_discovery.out", + exit_code: 1, + }); + + itest!(config_not_auto_discovered_for_remote_script { + args: + "run --quiet http://127.0.0.1:4545/run/with_config/server_side_work.ts", + output_str: Some("ok\n"), + http_server: true, + }); + + itest!(wasm_streaming_panic_test { + args: "run run/wasm_streaming_panic_test.js", + output: "run/wasm_streaming_panic_test.js.out", + exit_code: 1, + }); + + // Regression test for https://github.com/denoland/deno/issues/13897. + itest!(fetch_async_error_stack { + args: "run --quiet -A run/fetch_async_error_stack.ts", + output: "run/fetch_async_error_stack.ts.out", + exit_code: 1, + }); + + itest!(unstable_ffi_1 { + args: "run run/ffi/unstable_ffi_1.js", + output: "run/ffi/unstable_ffi_1.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_2 { + args: "run run/ffi/unstable_ffi_2.js", + output: "run/ffi/unstable_ffi_2.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_3 { + args: "run run/ffi/unstable_ffi_3.js", + output: "run/ffi/unstable_ffi_3.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_4 { + args: "run run/ffi/unstable_ffi_4.js", + output: "run/ffi/unstable_ffi_4.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_5 { + args: "run run/ffi/unstable_ffi_5.js", + output: "run/ffi/unstable_ffi_5.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_6 { + args: "run run/ffi/unstable_ffi_6.js", + output: "run/ffi/unstable_ffi_6.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_7 { + args: "run run/ffi/unstable_ffi_7.js", + output: "run/ffi/unstable_ffi_7.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_8 { + args: "run run/ffi/unstable_ffi_8.js", + output: "run/ffi/unstable_ffi_8.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_9 { + args: "run run/ffi/unstable_ffi_9.js", + output: "run/ffi/unstable_ffi_9.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_10 { + args: "run run/ffi/unstable_ffi_10.js", + output: "run/ffi/unstable_ffi_10.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_11 { + args: "run run/ffi/unstable_ffi_11.js", + output: "run/ffi/unstable_ffi_11.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_12 { + args: "run run/ffi/unstable_ffi_12.js", + output: "run/ffi/unstable_ffi_12.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_13 { + args: "run run/ffi/unstable_ffi_13.js", + output: "run/ffi/unstable_ffi_13.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_14 { + args: "run run/ffi/unstable_ffi_14.js", + output: "run/ffi/unstable_ffi_14.js.out", + exit_code: 70, + }); + + itest!(unstable_ffi_15 { + args: "run run/ffi/unstable_ffi_15.js", + output: "run/ffi/unstable_ffi_15.js.out", + exit_code: 70, + }); + + itest!(future_check2 { + args: "run --check run/future_check.ts", + output: "run/future_check2.out", + }); + + itest!(event_listener_error { + args: "run --quiet run/event_listener_error.ts", + output: "run/event_listener_error.ts.out", + exit_code: 1, + }); + + itest!(event_listener_error_handled { + args: "run --quiet run/event_listener_error_handled.ts", + output: "run/event_listener_error_handled.ts.out", + }); + + // https://github.com/denoland/deno/pull/14159#issuecomment-1092285446 + itest!(event_listener_error_immediate_exit { + args: "run --quiet run/event_listener_error_immediate_exit.ts", + output: "run/event_listener_error_immediate_exit.ts.out", + exit_code: 1, + }); + + // https://github.com/denoland/deno/pull/14159#issuecomment-1092285446 + itest!(event_listener_error_immediate_exit_worker { + args: + "run --quiet --unstable -A run/event_listener_error_immediate_exit_worker.ts", + output: "run/event_listener_error_immediate_exit_worker.ts.out", + exit_code: 1, +}); + + itest!(set_timeout_error { + args: "run --quiet run/set_timeout_error.ts", + output: "run/set_timeout_error.ts.out", + exit_code: 1, + }); + + itest!(set_timeout_error_handled { + args: "run --quiet run/set_timeout_error_handled.ts", + output: "run/set_timeout_error_handled.ts.out", + }); + + itest!(aggregate_error { + args: "run --quiet run/aggregate_error.ts", + output: "run/aggregate_error.out", + exit_code: 1, + }); + + itest!(complex_error { + args: "run --quiet run/complex_error.ts", + output: "run/complex_error.ts.out", + exit_code: 1, + }); + + // Regression test for https://github.com/denoland/deno/issues/12143. + itest!(js_root_with_ts_check { + args: "run --quiet --check run/js_root_with_ts_check.js", + output: "run/js_root_with_ts_check.js.out", + exit_code: 1, + }); + + #[test] + fn check_local_then_remote() { + let _http_guard = util::http_server(); + let deno_dir = util::new_deno_dir(); + let output = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--check") + .arg("run/remote_type_error/main.ts") + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + let output = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--check=all") + .arg("run/remote_type_error/main.ts") + .env("NO_COLOR", "1") + .stderr(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + let stderr = std::str::from_utf8(&output.stderr).unwrap(); + assert_contains!( + stderr, + "Type 'string' is not assignable to type 'number'." + ); + } + + // Regression test for https://github.com/denoland/deno/issues/15163 + itest!(check_js_points_to_ts { + args: "run --quiet --check --config run/checkjs.tsconfig.json run/check_js_points_to_ts/test.js", + output: "run/check_js_points_to_ts/test.js.out", + exit_code: 1, +}); + + itest!(no_prompt_flag { + args: "run --quiet --unstable --no-prompt run/no_prompt.ts", + output_str: Some(""), + }); + + #[test] + fn deno_no_prompt_environment_variable() { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--unstable") + .arg("run/no_prompt.ts") + .env("DENO_NO_PROMPT", "1") + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + } + + itest!(report_error { + args: "run --quiet run/report_error.ts", + output: "run/report_error.ts.out", + exit_code: 1, + }); + + itest!(report_error_handled { + args: "run --quiet run/report_error_handled.ts", + output: "run/report_error_handled.ts.out", + }); + + // Regression test for https://github.com/denoland/deno/issues/15513. + itest!(report_error_end_of_program { + args: "run --quiet run/report_error_end_of_program.ts", + output: "run/report_error_end_of_program.ts.out", + exit_code: 1, + }); + + itest!(queue_microtask_error { + args: "run --quiet run/queue_microtask_error.ts", + output: "run/queue_microtask_error.ts.out", + exit_code: 1, + }); + + itest!(queue_microtask_error_handled { + args: "run --quiet run/queue_microtask_error_handled.ts", + output: "run/queue_microtask_error_handled.ts.out", + }); + + itest!(spawn_stdout_inherit { + args: "run --quiet --unstable -A run/spawn_stdout_inherit.ts", + output: "run/spawn_stdout_inherit.ts.out", + }); + + itest!(error_name_non_string { + args: "run --quiet run/error_name_non_string.js", + output: "run/error_name_non_string.js.out", + exit_code: 1, + }); + + itest!(custom_inspect_url { + args: "run run/custom_inspect_url.js", + output: "run/custom_inspect_url.js.out", + }); + + itest!(config_json_import { + args: "run --quiet -c jsx/deno-jsx.json run/config_json_import.ts", + output: "run/config_json_import.ts.out", + http_server: true, + }); + + #[test] + fn running_declaration_files() { + let temp_dir = TempDir::new(); + let files = vec!["file.d.ts", "file.d.cts", "file.d.mts"]; + + for file in files { + temp_dir.write(file, ""); + let mut deno_cmd = util::deno_cmd_with_deno_dir(&temp_dir); + let output = deno_cmd + .current_dir(temp_dir.path()) + .args(["run", file]) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(output.status.success()); + } + } + + itest!(test_and_bench_are_noops_in_run { + args: "run run/test_and_bench_in_run.js", + output_str: Some(""), + }); + + itest!(followup_dyn_import_resolved { + args: + "run --unstable --allow-read run/followup_dyn_import_resolves/main.ts", + output: "run/followup_dyn_import_resolves/main.ts.out", + }); + + itest!(unhandled_rejection { + args: "run --check run/unhandled_rejection.ts", + output: "run/unhandled_rejection.ts.out", + }); + + itest!(unhandled_rejection_sync_error { + args: "run --check run/unhandled_rejection_sync_error.ts", + output: "run/unhandled_rejection_sync_error.ts.out", + }); + + itest!(nested_error { + args: "run run/nested_error.ts", + output: "run/nested_error.ts.out", + exit_code: 1, + }); + + itest!(node_env_var_allowlist { + args: "run --unstable --no-prompt run/node_env_var_allowlist.ts", + output: "run/node_env_var_allowlist.ts.out", + exit_code: 1, + }); + + #[test] + fn cache_test() { + let _g = util::http_server(); + let deno_dir = TempDir::new(); + let module_url = + url::Url::parse("http://localhost:4545/run/006_url_imports.ts").unwrap(); + let output = Command::new(util::deno_exe_path()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("cache") + .arg("--check=all") + .arg("-L") + .arg("debug") + .arg(module_url.to_string()) + .output() + .expect("Failed to spawn script"); + assert!(output.status.success()); + + let prg = util::deno_exe_path(); + let output = Command::new(&prg) + .env("DENO_DIR", deno_dir.path()) + .env("HTTP_PROXY", "http://nil") + .env("NO_COLOR", "1") + .current_dir(util::testdata_path()) + .arg("run") + .arg(module_url.to_string()) + .output() + .expect("Failed to spawn script"); + + let str_output = std::str::from_utf8(&output.stdout).unwrap(); + + let module_output_path = + util::testdata_path().join("run/006_url_imports.ts.out"); + let mut module_output = String::new(); + let mut module_output_file = + std::fs::File::open(module_output_path).unwrap(); + module_output_file + .read_to_string(&mut module_output) + .unwrap(); + + assert_eq!(module_output, str_output); + } + + #[test] + fn cache_invalidation_test() { + let deno_dir = TempDir::new(); + let fixture_path = deno_dir.path().join("fixture.ts"); + { + let mut file = std::fs::File::create(fixture_path.clone()) + .expect("could not create fixture"); + file + .write_all(b"console.log(\"42\");") + .expect("could not write fixture"); + } + let output = Command::new(util::deno_exe_path()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("run") + .arg(fixture_path.to_str().unwrap()) + .output() + .expect("Failed to spawn script"); + assert!(output.status.success()); + let actual = std::str::from_utf8(&output.stdout).unwrap(); + assert_eq!(actual, "42\n"); + { + let mut file = std::fs::File::create(fixture_path.clone()) + .expect("could not create fixture"); + file + .write_all(b"console.log(\"43\");") + .expect("could not write fixture"); + } + let output = Command::new(util::deno_exe_path()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("run") + .arg(fixture_path.to_str().unwrap()) + .output() + .expect("Failed to spawn script"); + assert!(output.status.success()); + let actual = std::str::from_utf8(&output.stdout).unwrap(); + assert_eq!(actual, "43\n"); + } + + #[test] + fn cache_invalidation_test_no_check() { + let deno_dir = TempDir::new(); + let fixture_path = deno_dir.path().join("fixture.ts"); + { + let mut file = std::fs::File::create(fixture_path.clone()) + .expect("could not create fixture"); + file + .write_all(b"console.log(\"42\");") + .expect("could not write fixture"); + } + let output = Command::new(util::deno_exe_path()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--no-check") + .arg(fixture_path.to_str().unwrap()) + .output() + .expect("Failed to spawn script"); + assert!(output.status.success()); + let actual = std::str::from_utf8(&output.stdout).unwrap(); + assert_eq!(actual, "42\n"); + { + let mut file = std::fs::File::create(fixture_path.clone()) + .expect("could not create fixture"); + file + .write_all(b"console.log(\"43\");") + .expect("could not write fixture"); + } + let output = Command::new(util::deno_exe_path()) + .env("DENO_DIR", deno_dir.path()) + .current_dir(util::testdata_path()) + .arg("run") + .arg("--no-check") + .arg(fixture_path.to_str().unwrap()) + .output() + .expect("Failed to spawn script"); + assert!(output.status.success()); + let actual = std::str::from_utf8(&output.stdout).unwrap(); + assert_eq!(actual, "43\n"); + } + + #[test] + fn ts_dependency_recompilation() { + let t = TempDir::new(); + let ats = t.path().join("a.ts"); + + std::fs::write( + &ats, + " + import { foo } from \"./b.ts\"; + + function print(str: string): void { + console.log(str); + } + + print(foo);", + ) + .unwrap(); + + let bts = t.path().join("b.ts"); + std::fs::write( + &bts, + " + export const foo = \"foo\";", + ) + .unwrap(); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .env("NO_COLOR", "1") + .arg("run") + .arg("--check") + .arg(&ats) + .output() + .expect("failed to spawn script"); + + let stdout_output = std::str::from_utf8(&output.stdout).unwrap().trim(); + let stderr_output = std::str::from_utf8(&output.stderr).unwrap().trim(); + + assert!(stdout_output.ends_with("foo")); + assert!(stderr_output.starts_with("Check")); + + // Overwrite contents of b.ts and run again + std::fs::write( + &bts, + " + export const foo = 5;", + ) + .expect("error writing file"); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .env("NO_COLOR", "1") + .arg("run") + .arg("--check") + .arg(&ats) + .output() + .expect("failed to spawn script"); + + let stdout_output = std::str::from_utf8(&output.stdout).unwrap().trim(); + let stderr_output = std::str::from_utf8(&output.stderr).unwrap().trim(); + + // error: TS2345 [ERROR]: Argument of type '5' is not assignable to parameter of type 'string'. + assert!(stderr_output.contains("TS2345")); + assert!(!output.status.success()); + assert!(stdout_output.is_empty()); + } + + #[test] + fn basic_auth_tokens() { + let _g = util::http_server(); + + let output = util::deno_cmd() + .current_dir(util::root_path()) + .arg("run") + .arg("http://127.0.0.1:4554/run/001_hello.js") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + + assert!(!output.status.success()); + + let stdout_str = std::str::from_utf8(&output.stdout).unwrap().trim(); + assert!(stdout_str.is_empty()); + + let stderr_str = std::str::from_utf8(&output.stderr).unwrap().trim(); + eprintln!("{}", stderr_str); + + assert!(stderr_str.contains( + "Module not found \"http://127.0.0.1:4554/run/001_hello.js\"." + )); + + let output = util::deno_cmd() + .current_dir(util::root_path()) + .arg("run") + .arg("http://127.0.0.1:4554/run/001_hello.js") + .env("DENO_AUTH_TOKENS", "testuser123:testpassabc@127.0.0.1:4554") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + + let stderr_str = std::str::from_utf8(&output.stderr).unwrap().trim(); + eprintln!("{}", stderr_str); + + assert!(output.status.success()); + + let stdout_str = std::str::from_utf8(&output.stdout).unwrap().trim(); + assert_eq!(util::strip_ansi_codes(stdout_str), "Hello World"); + } + + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] + async fn test_resolve_dns() { + use std::net::SocketAddr; + use std::str::FromStr; + use std::sync::Arc; + use std::time::Duration; + use tokio::net::TcpListener; + use tokio::net::UdpSocket; + use tokio::sync::oneshot; + use trust_dns_server::authority::Catalog; + use trust_dns_server::authority::ZoneType; + use trust_dns_server::proto::rr::Name; + use trust_dns_server::store::in_memory::InMemoryAuthority; + use trust_dns_server::ServerFuture; + + const DNS_PORT: u16 = 4553; + + // Setup DNS server for testing + async fn run_dns_server(tx: oneshot::Sender<()>) { + let zone_file = std::fs::read_to_string( + util::testdata_path().join("run/resolve_dns.zone.in"), + ) + .unwrap(); + let lexer = Lexer::new(&zone_file); + let records = Parser::new().parse( + lexer, + Some(Name::from_str("example.com").unwrap()), + None, + ); + if records.is_err() { + panic!("failed to parse: {:?}", records.err()) + } + let (origin, records) = records.unwrap(); + let authority = Box::new(Arc::new( + InMemoryAuthority::new(origin, records, ZoneType::Primary, false) + .unwrap(), + )); + let mut catalog: Catalog = Catalog::new(); + catalog.upsert(Name::root().into(), authority); + + let mut server_fut = ServerFuture::new(catalog); + let socket_addr = SocketAddr::from(([127, 0, 0, 1], DNS_PORT)); + let tcp_listener = TcpListener::bind(socket_addr).await.unwrap(); + let udp_socket = UdpSocket::bind(socket_addr).await.unwrap(); + server_fut.register_socket(udp_socket); + server_fut.register_listener(tcp_listener, Duration::from_secs(2)); + + // Notifies that the DNS server is ready + tx.send(()).unwrap(); + + server_fut.block_until_done().await.unwrap(); + } + + let (ready_tx, ready_rx) = oneshot::channel(); + let dns_server_fut = run_dns_server(ready_tx); + let handle = tokio::spawn(dns_server_fut); + + // Waits for the DNS server to be ready + ready_rx.await.unwrap(); + + // Pass: `--allow-net` + { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .env("NO_COLOR", "1") + .arg("run") + .arg("--check") + .arg("--allow-net") + .arg("run/resolve_dns.ts") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + let err = String::from_utf8_lossy(&output.stderr); + let out = String::from_utf8_lossy(&output.stdout); + println!("{}", err); + assert!(output.status.success()); + assert!(err.starts_with("Check file")); + + let expected = std::fs::read_to_string( + util::testdata_path().join("run/resolve_dns.ts.out"), + ) + .unwrap(); + assert_eq!(expected, out); + } + + // Pass: `--allow-net=127.0.0.1:4553` + { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .env("NO_COLOR", "1") + .arg("run") + .arg("--check") + .arg("--allow-net=127.0.0.1:4553") + .arg("run/resolve_dns.ts") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + let err = String::from_utf8_lossy(&output.stderr); + let out = String::from_utf8_lossy(&output.stdout); + assert!(output.status.success()); + assert!(err.starts_with("Check file")); + + let expected = std::fs::read_to_string( + util::testdata_path().join("run/resolve_dns.ts.out"), + ) + .unwrap(); + assert_eq!(expected, out); + } + + // Permission error: `--allow-net=deno.land` + { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .env("NO_COLOR", "1") + .arg("run") + .arg("--check") + .arg("--allow-net=deno.land") + .arg("run/resolve_dns.ts") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + let err = String::from_utf8_lossy(&output.stderr); + let out = String::from_utf8_lossy(&output.stdout); + assert!(!output.status.success()); + assert!(err.starts_with("Check file")); + assert!(err.contains(r#"error: Uncaught PermissionDenied: Requires net access to "127.0.0.1:4553""#)); + assert!(out.is_empty()); + } + + // Permission error: no permission specified + { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .env("NO_COLOR", "1") + .arg("run") + .arg("--check") + .arg("run/resolve_dns.ts") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + let err = String::from_utf8_lossy(&output.stderr); + let out = String::from_utf8_lossy(&output.stdout); + assert!(!output.status.success()); + assert!(err.starts_with("Check file")); + assert!(err.contains(r#"error: Uncaught PermissionDenied: Requires net access to "127.0.0.1:4553""#)); + assert!(out.is_empty()); + } + + handle.abort(); + } + + #[tokio::test] + async fn http2_request_url() { + // TLS streams require the presence of an ambient local task set to gracefully + // close dropped connections in the background. + LocalSet::new() + .run_until(async { + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--unstable") + .arg("--quiet") + .arg("--allow-net") + .arg("--allow-read") + .arg("./run/http2_request_url.ts") + .arg("4506") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let stdout = child.stdout.as_mut().unwrap(); + let mut buffer = [0; 5]; + let read = stdout.read(&mut buffer).unwrap(); + assert_eq!(read, 5); + let msg = std::str::from_utf8(&buffer).unwrap(); + assert_eq!(msg, "READY"); + + let cert = reqwest::Certificate::from_pem(include_bytes!( + "./testdata/tls/RootCA.crt" + )) + .unwrap(); + + let client = reqwest::Client::builder() + .add_root_certificate(cert) + .http2_prior_knowledge() + .build() + .unwrap(); + + let res = client.get("http://127.0.0.1:4506").send().await.unwrap(); + assert_eq!(200, res.status()); + + let body = res.text().await.unwrap(); + assert_eq!(body, "http://127.0.0.1:4506/"); + + child.kill().unwrap(); + child.wait().unwrap(); + }) + .await; + } + + #[cfg(not(windows))] + #[test] + fn set_raw_should_not_panic_on_no_tty() { + let output = util::deno_cmd() + .arg("eval") + .arg("Deno.stdin.setRaw(true)") + // stdin set to piped so it certainly does not refer to TTY + .stdin(std::process::Stdio::piped()) + // stderr is piped so we can capture output. + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + let stderr = std::str::from_utf8(&output.stderr).unwrap().trim(); + assert!(stderr.contains("BadResource")); + } + + #[test] + fn timeout_clear() { + // https://github.com/denoland/deno/issues/7599 + + use std::time::Duration; + use std::time::Instant; + + let source_code = r#" +const handle = setTimeout(() => { + console.log("timeout finish"); +}, 10000); +clearTimeout(handle); +console.log("finish"); +"#; + + let mut p = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("-") + .stdin(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let stdin = p.stdin.as_mut().unwrap(); + stdin.write_all(source_code.as_bytes()).unwrap(); + let start = Instant::now(); + let status = p.wait().unwrap(); + let end = Instant::now(); + assert!(status.success()); + // check that program did not run for 10 seconds + // for timeout to clear + assert!(end - start < Duration::new(10, 0)); + } + + #[test] + fn broken_stdout() { + let (reader, writer) = os_pipe::pipe().unwrap(); + // drop the reader to create a broken pipe + drop(reader); + + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("eval") + .arg("console.log(3.14)") + .stdout(writer) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + + assert!(!output.status.success()); + let stderr = std::str::from_utf8(output.stderr.as_ref()).unwrap().trim(); + assert!(stderr.contains("Uncaught BrokenPipe")); + assert!(!stderr.contains("panic")); + } + + itest!(error_cause { + args: "run run/error_cause.ts", + output: "run/error_cause.ts.out", + exit_code: 1, + }); + + itest!(error_cause_recursive_aggregate { + args: "run error_cause_recursive_aggregate.ts", + output: "error_cause_recursive_aggregate.ts.out", + exit_code: 1, + }); + + itest!(error_cause_recursive_tail { + args: "run error_cause_recursive_tail.ts", + output: "error_cause_recursive_tail.ts.out", + exit_code: 1, + }); + + itest!(error_cause_recursive { + args: "run run/error_cause_recursive.ts", + output: "run/error_cause_recursive.ts.out", + exit_code: 1, + }); + + #[test] + fn websocket() { + let _g = util::http_server(); + + let script = util::testdata_path().join("run/websocket_test.ts"); + let root_ca = util::testdata_path().join("tls/RootCA.pem"); + let status = util::deno_cmd() + .arg("test") + .arg("--unstable") + .arg("--allow-net") + .arg("--cert") + .arg(root_ca) + .arg(script) + .spawn() + .unwrap() + .wait() + .unwrap(); + + assert!(status.success()); + } + + #[ignore] + #[test] + fn websocketstream() { + let _g = util::http_server(); + + let script = util::testdata_path().join("run/websocketstream_test.ts"); + let root_ca = util::testdata_path().join("tls/RootCA.pem"); + let status = util::deno_cmd() + .arg("test") + .arg("--unstable") + .arg("--allow-net") + .arg("--cert") + .arg(root_ca) + .arg(script) + .spawn() + .unwrap() + .wait() + .unwrap(); + + assert!(status.success()); + } + + #[test] + fn websocketstream_ping() { + use deno_runtime::deno_websocket::tokio_tungstenite::tungstenite; + let _g = util::http_server(); + + let script = util::testdata_path().join("run/websocketstream_ping_test.ts"); + let root_ca = util::testdata_path().join("tls/RootCA.pem"); + let mut child = util::deno_cmd() + .arg("test") + .arg("--unstable") + .arg("--allow-net") + .arg("--cert") + .arg(root_ca) + .arg(script) + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let server = std::net::TcpListener::bind("127.0.0.1:4513").unwrap(); + let (stream, _) = server.accept().unwrap(); + let mut socket = tungstenite::accept(stream).unwrap(); + socket + .write_message(tungstenite::Message::Text(String::from("A"))) + .unwrap(); + socket + .write_message(tungstenite::Message::Ping(vec![])) + .unwrap(); + socket + .write_message(tungstenite::Message::Text(String::from("B"))) + .unwrap(); + let message = socket.read_message().unwrap(); + assert_eq!(message, tungstenite::Message::Pong(vec![])); + socket + .write_message(tungstenite::Message::Text(String::from("C"))) + .unwrap(); + socket.close(None).unwrap(); + + assert!(child.wait().unwrap().success()); + } + + #[test] + fn websocket_server_multi_field_connection_header() { + let script = util::testdata_path() + .join("run/websocket_server_multi_field_connection_header_test.ts"); + let root_ca = util::testdata_path().join("tls/RootCA.pem"); + let mut child = util::deno_cmd() + .arg("run") + .arg("--unstable") + .arg("--allow-net") + .arg("--cert") + .arg(root_ca) + .arg(script) + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut buffer = [0; 5]; + let read = stdout.read(&mut buffer).unwrap(); + assert_eq!(read, 5); + let msg = std::str::from_utf8(&buffer).unwrap(); + assert_eq!(msg, "READY"); + + let req = http::request::Builder::new() + .header(http::header::CONNECTION, "keep-alive, Upgrade") + .uri("ws://localhost:4319") + .body(()) + .unwrap(); + let (mut socket, _) = + deno_runtime::deno_websocket::tokio_tungstenite::tungstenite::connect( + req, + ) + .unwrap(); + let message = socket.read_message().unwrap(); + assert_eq!(message, deno_runtime::deno_websocket::tokio_tungstenite::tungstenite::Message::Close(None)); + socket.close(None).unwrap(); + assert!(child.wait().unwrap().success()); + } + + // TODO(bartlomieju): this should use `deno run`, not `deno test`; but the + // test hangs then. https://github.com/denoland/deno/issues/14283 + #[test] + #[ignore] + fn websocket_server_idletimeout() { + let script = + util::testdata_path().join("run/websocket_server_idletimeout.ts"); + let root_ca = util::testdata_path().join("tls/RootCA.pem"); + let mut child = util::deno_cmd() + .arg("test") + .arg("--unstable") + .arg("--allow-net") + .arg("--cert") + .arg(root_ca) + .arg(script) + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap(); + + let stdout = child.stdout.as_mut().unwrap(); + let mut buffer = [0; 5]; + let read = stdout.read(&mut buffer).unwrap(); + assert_eq!(read, 5); + let msg = std::str::from_utf8(&buffer).unwrap(); + assert_eq!(msg, "READY"); + + let req = http::request::Builder::new() + .uri("ws://localhost:4509") + .body(()) + .unwrap(); + let (_ws, _request) = + deno_runtime::deno_websocket::tokio_tungstenite::tungstenite::connect( + req, + ) + .unwrap(); + + assert!(child.wait().unwrap().success()); + } + + itest!(auto_discover_lockfile { + args: "run run/auto_discover_lockfile/main.ts", + output: "run/auto_discover_lockfile/main.out", + http_server: true, + exit_code: 10, + }); + + itest!(no_lock_flag { + args: "run --no-lock run/no_lock_flag/main.ts", + output: "run/no_lock_flag/main.out", + http_server: true, + exit_code: 0, + }); + + // Check https://github.com/denoland/deno_std/issues/2882 + itest!(flash_shutdown { + args: "run --unstable --allow-net run/flash_shutdown/main.ts", + exit_code: 0, + }); + + itest!(permission_args { + args: "run run/001_hello.js --allow-net", + output: "run/permission_args.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + }); + + itest!(permission_args_quiet { + args: "run --quiet run/001_hello.js --allow-net", + output: "run/001_hello.js.out", + }); +} diff --git a/cli/tests/task_tests.rs b/cli/tests/task_tests.rs new file mode 100644 index 0000000000..0280391da4 --- /dev/null +++ b/cli/tests/task_tests.rs @@ -0,0 +1,136 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +// Most of the tests for this are in deno_task_shell. +// These tests are intended to only test integration. + +mod task { + use super::*; + + itest!(task_no_args { + args: "task -q --config task/deno.json", + output: "task/task_no_args.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + exit_code: 1, + }); + + itest!(task_cwd { + args: "task -q --config task/deno.json --cwd .. echo_cwd", + output: "task/task_cwd.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + exit_code: 0, + }); + + itest!(task_init_cwd { + args: "task -q --config task/deno.json --cwd .. echo_init_cwd", + output: "task/task_init_cwd.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + exit_code: 0, + }); + + itest!(task_init_cwd_already_set { + args: "task -q --config task/deno.json echo_init_cwd", + output: "task/task_init_cwd_already_set.out", + envs: vec![ + ("NO_COLOR".to_string(), "1".to_string()), + ("INIT_CWD".to_string(), "HELLO".to_string()) + ], + exit_code: 0, + }); + + itest!(task_cwd_resolves_config_from_specified_dir { + args: "task -q --cwd task", + output: "task/task_no_args.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + exit_code: 1, + }); + + itest!(task_non_existent { + args: "task --config task/deno.json non_existent", + output: "task/task_non_existent.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + exit_code: 1, + }); + + #[test] + fn task_emoji() { + // this bug only appears when using a pty/tty + let args = "task --config task/deno.json echo_emoji"; + use test_util::PtyData::*; + test_util::test_pty2(args, vec![Output("Task echo_emoji echo 🔥\r\n🔥")]); + } + + itest!(task_boolean_logic { + args: "task -q --config task/deno.json boolean_logic", + output: "task/task_boolean_logic.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + }); + + itest!(task_exit_code_5 { + args: "task --config task/deno.json exit_code_5", + output: "task/task_exit_code_5.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + exit_code: 5, + }); + + itest!(task_additional_args { + args: "task -q --config task/deno.json echo 2", + output: "task/task_additional_args.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + }); + + itest!(task_additional_args_no_shell_expansion { + args_vec: vec![ + "task", + "-q", + "--config", + "task/deno.json", + "echo", + "$(echo 5)" + ], + output: "task/task_additional_args_no_shell_expansion.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + }); + + itest!(task_additional_args_nested_strings { + args_vec: vec![ + "task", + "-q", + "--config", + "task/deno.json", + "echo", + "string \"quoted string\"" + ], + output: "task/task_additional_args_nested_strings.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + }); + + itest!(task_additional_args_no_logic { + args_vec: vec![ + "task", + "-q", + "--config", + "task/deno.json", + "echo", + "||", + "echo", + "5" + ], + output: "task/task_additional_args_no_logic.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + }); + + itest!(task_deno_exe_no_env { + args_vec: vec!["task", "-q", "--config", "task/deno.json", "deno_echo"], + output: "task/task_deno_exe_no_env.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + env_clear: true, + }); + + itest!(task_piped_stdin { + args_vec: vec!["task", "-q", "--config", "task/deno.json", "piped"], + output: "task/task_piped_stdin.out", + envs: vec![("NO_COLOR".to_string(), "1".to_string())], + }); +} diff --git a/cli/tests/test_tests.rs b/cli/tests/test_tests.rs new file mode 100644 index 0000000000..92b1c33c8e --- /dev/null +++ b/cli/tests/test_tests.rs @@ -0,0 +1,453 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; +use deno_core::url::Url; +use test_util as util; + +mod test { + use super::*; + + #[test] + fn no_color() { + let (out, _) = util::run_and_collect_output( + false, + "test test/no_color.ts", + None, + Some(vec![("NO_COLOR".to_owned(), "true".to_owned())]), + false, + ); + // ANSI escape codes should be stripped. + assert!(out.contains("success ... ok")); + assert!(out.contains("fail ... FAILED")); + assert!(out.contains("ignored ... ignored")); + assert!(out.contains("FAILED | 1 passed | 1 failed | 1 ignored")); + } + + itest!(overloads { + args: "test test/overloads.ts", + exit_code: 0, + output: "test/overloads.out", + }); + + itest!(meta { + args: "test test/meta.ts", + exit_code: 0, + output: "test/meta.out", + }); + + itest!(pass { + args: "test test/pass.ts", + exit_code: 0, + output: "test/pass.out", + }); + + itest!(ignore { + args: "test test/ignore.ts", + exit_code: 0, + output: "test/ignore.out", + }); + + itest!(ignore_permissions { + args: "test --unstable test/ignore_permissions.ts", + exit_code: 0, + output: "test/ignore_permissions.out", + }); + + itest!(fail { + args: "test test/fail.ts", + exit_code: 1, + output: "test/fail.out", + }); + + itest!(collect { + args: "test --ignore=test/collect/ignore test/collect", + exit_code: 0, + output: "test/collect.out", + }); + + itest!(test_with_config { + args: "test --config test/collect/deno.jsonc test/collect", + exit_code: 0, + output: "test/collect.out", + }); + + itest!(test_with_config2 { + args: "test --config test/collect/deno2.jsonc test/collect", + exit_code: 0, + output: "test/collect2.out", + }); + + itest!(test_with_malformed_config { + args: "test --config test/collect/deno.malformed.jsonc", + exit_code: 1, + output: "test/collect_with_malformed_config.out", + }); + + itest!(parallel_flag { + args: "test test/short-pass.ts --parallel", + exit_code: 0, + output: "test/short-pass.out", + }); + + itest!(parallel_flag_with_env_variable { + args: "test test/short-pass.ts --parallel", + envs: vec![("DENO_JOBS".to_owned(), "2".to_owned())], + exit_code: 0, + output: "test/short-pass.out", + }); + + itest!(jobs_flag { + args: "test test/short-pass.ts --jobs", + exit_code: 0, + output: "test/short-pass-jobs-flag-warning.out", + }); + + itest!(jobs_flag_with_numeric_value { + args: "test test/short-pass.ts --jobs=2", + exit_code: 0, + output: "test/short-pass-jobs-flag-warning.out", + }); + + itest!(load_unload { + args: "test test/load_unload.ts", + exit_code: 0, + output: "test/load_unload.out", + }); + + itest!(interval { + args: "test test/interval.ts", + exit_code: 0, + output: "test/interval.out", + }); + + itest!(doc { + args: "test --doc --allow-all test/doc.ts", + exit_code: 1, + output: "test/doc.out", + }); + + itest!(doc_only { + args: "test --doc --allow-all test/doc_only", + exit_code: 0, + output: "test/doc_only.out", + }); + + itest!(markdown { + args: "test --doc --allow-all test/markdown.md", + exit_code: 1, + output: "test/markdown.out", + }); + + itest!(markdown_windows { + args: "test --doc --allow-all test/markdown_windows.md", + exit_code: 1, + output: "test/markdown_windows.out", + }); + + itest!(markdown_full_block_names { + args: "test --doc --allow-all test/markdown_full_block_names.md", + exit_code: 1, + output: "test/markdown_full_block_names.out", + }); + + itest!(markdown_ignore_html_comment { + args: "test --doc --allow-all test/markdown_with_comment.md", + exit_code: 1, + output: "test/markdown_with_comment.out", + }); + + itest!(text { + args: "test --doc --allow-all test/text.md", + exit_code: 0, + output: "test/text.out", + }); + + itest!(quiet { + args: "test --quiet test/quiet.ts", + exit_code: 0, + output: "test/quiet.out", + }); + + itest!(fail_fast { + args: "test --fail-fast test/fail_fast.ts", + exit_code: 1, + output: "test/fail_fast.out", + }); + + itest!(only { + args: "test test/only.ts", + exit_code: 1, + output: "test/only.out", + }); + + itest!(no_check { + args: "test --no-check test/no_check.ts", + exit_code: 1, + output: "test/no_check.out", + }); + + itest!(no_run { + args: "test --unstable --no-run test/no_run.ts", + output: "test/no_run.out", + exit_code: 1, + }); + + itest!(allow_all { + args: "test --unstable --allow-all test/allow_all.ts", + exit_code: 0, + output: "test/allow_all.out", + }); + + itest!(allow_none { + args: "test --unstable test/allow_none.ts", + exit_code: 1, + output: "test/allow_none.out", + }); + + itest!(ops_sanitizer_unstable { + args: "test --unstable --trace-ops test/ops_sanitizer_unstable.ts", + exit_code: 1, + output: "test/ops_sanitizer_unstable.out", + }); + + itest!(ops_sanitizer_timeout_failure { + args: "test test/ops_sanitizer_timeout_failure.ts", + output: "test/ops_sanitizer_timeout_failure.out", + }); + + itest!(ops_sanitizer_multiple_timeout_tests { + args: "test --trace-ops test/ops_sanitizer_multiple_timeout_tests.ts", + exit_code: 1, + output: "test/ops_sanitizer_multiple_timeout_tests.out", + }); + + itest!(ops_sanitizer_multiple_timeout_tests_no_trace { + args: "test test/ops_sanitizer_multiple_timeout_tests.ts", + exit_code: 1, + output: "test/ops_sanitizer_multiple_timeout_tests_no_trace.out", + }); + + // TODO(@littledivy): re-enable this test, recent optimizations made output non deterministic. + // https://github.com/denoland/deno/issues/14268 + // + // itest!(ops_sanitizer_missing_details { + // args: "test --allow-write --allow-read test/ops_sanitizer_missing_details.ts", + // exit_code: 1, + // output: "test/ops_sanitizer_missing_details.out", + // }); + + itest!(ops_sanitizer_nexttick { + args: "test test/ops_sanitizer_nexttick.ts", + output: "test/ops_sanitizer_nexttick.out", + }); + + itest!(resource_sanitizer { + args: "test --allow-read test/resource_sanitizer.ts", + exit_code: 1, + output: "test/resource_sanitizer.out", + }); + + itest!(exit_sanitizer { + args: "test test/exit_sanitizer.ts", + output: "test/exit_sanitizer.out", + exit_code: 1, + }); + + itest!(clear_timeout { + args: "test test/clear_timeout.ts", + exit_code: 0, + output: "test/clear_timeout.out", + }); + + itest!(finally_timeout { + args: "test test/finally_timeout.ts", + exit_code: 1, + output: "test/finally_timeout.out", + }); + + itest!(unresolved_promise { + args: "test test/unresolved_promise.ts", + exit_code: 1, + output: "test/unresolved_promise.out", + }); + + itest!(unhandled_rejection { + args: "test test/unhandled_rejection.ts", + exit_code: 1, + output: "test/unhandled_rejection.out", + }); + + itest!(filter { + args: "test --filter=foo test/filter", + exit_code: 0, + output: "test/filter.out", + }); + + itest!(shuffle { + args: "test --shuffle test/shuffle", + exit_code: 0, + output_str: Some("[WILDCARD]"), + }); + + itest!(shuffle_with_seed { + args: "test --shuffle=42 test/shuffle", + exit_code: 0, + output: "test/shuffle.out", + }); + + itest!(aggregate_error { + args: "test --quiet test/aggregate_error.ts", + exit_code: 1, + output: "test/aggregate_error.out", + }); + + itest!(steps_passing_steps { + args: "test test/steps/passing_steps.ts", + exit_code: 0, + output: "test/steps/passing_steps.out", + }); + + itest!(steps_failing_steps { + args: "test test/steps/failing_steps.ts", + exit_code: 1, + output: "test/steps/failing_steps.out", + }); + + itest!(steps_ignored_steps { + args: "test test/steps/ignored_steps.ts", + exit_code: 0, + output: "test/steps/ignored_steps.out", + }); + + itest!(steps_invalid_usage { + args: "test test/steps/invalid_usage.ts", + exit_code: 1, + output: "test/steps/invalid_usage.out", + }); + + itest!(steps_output_within { + args: "test test/steps/output_within.ts", + exit_code: 0, + output: "test/steps/output_within.out", + }); + + itest!(no_prompt_by_default { + args: "test --quiet test/no_prompt_by_default.ts", + exit_code: 1, + output: "test/no_prompt_by_default.out", + }); + + itest!(no_prompt_with_denied_perms { + args: "test --quiet --allow-read test/no_prompt_with_denied_perms.ts", + exit_code: 1, + output: "test/no_prompt_with_denied_perms.out", + }); + + itest!(test_with_custom_jsx { + args: "test --quiet --allow-read test/hello_world.ts --config=test/deno_custom_jsx.json", + exit_code: 0, + output: "test/hello_world.out", +}); + + #[test] + fn captured_output() { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("test") + .arg("--allow-run") + .arg("--allow-read") + .arg("--unstable") + .arg("test/captured_output.ts") + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + + let output_start = "------- output -------"; + let output_end = "----- output end -----"; + assert!(output.status.success()); + let output_text = String::from_utf8(output.stdout).unwrap(); + let start = output_text.find(output_start).unwrap() + output_start.len(); + let end = output_text.find(output_end).unwrap(); + // replace zero width space that may appear in test output due + // to test runner output flusher + let output_text = output_text[start..end] + .replace('\u{200B}', "") + .trim() + .to_string(); + let mut lines = output_text.lines().collect::>(); + // the output is racy on either stdout or stderr being flushed + // from the runtime into the rust code, so sort it... the main + // thing here to ensure is that we're capturing the output in + // this block on stdout + lines.sort_unstable(); + assert_eq!(lines.join(" "), "0 1 2 3 4 5 6 7 8 9"); + } + + #[test] + fn recursive_permissions_pledge() { + let output = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("test") + .arg("test/recursive_permissions_pledge.js") + .stderr(std::process::Stdio::piped()) + .stdout(std::process::Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + assert!(String::from_utf8(output.stderr).unwrap().contains( + "pledge test permissions called before restoring previous pledge" + )); + } + + #[test] + fn file_protocol() { + let file_url = + Url::from_file_path(util::testdata_path().join("test/file_protocol.ts")) + .unwrap() + .to_string(); + + (util::CheckOutputIntegrationTest { + args_vec: vec!["test", &file_url], + exit_code: 0, + output: "test/file_protocol.out", + ..Default::default() + }) + .run(); + } + + itest!(uncaught_errors { + args: "test --quiet test/uncaught_errors_1.ts test/uncaught_errors_2.ts test/uncaught_errors_3.ts", + output: "test/uncaught_errors.out", + exit_code: 1, +}); + + itest!(check_local_by_default { + args: "test --quiet test/check_local_by_default.ts", + output: "test/check_local_by_default.out", + http_server: true, + }); + + itest!(check_local_by_default2 { + args: "test --quiet test/check_local_by_default2.ts", + output: "test/check_local_by_default2.out", + http_server: true, + exit_code: 1, + }); + + itest!(non_error_thrown { + args: "test --quiet test/non_error_thrown.ts", + output: "test/non_error_thrown.out", + exit_code: 1, + }); + + itest!(parallel_output { + args: "test --parallel --reload test/parallel_output.ts", + output: "test/parallel_output.out", + exit_code: 1, + }); +} diff --git a/cli/tests/upgrade_tests.rs b/cli/tests/upgrade_tests.rs new file mode 100644 index 0000000000..f67e302ff8 --- /dev/null +++ b/cli/tests/upgrade_tests.rs @@ -0,0 +1,197 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +use std::process::{Command, Stdio}; +use test_util as util; +use test_util::TempDir; + +mod upgrade { + use super::*; + + // Warning: this test requires internet access. + // TODO(#7412): reenable. test is flaky + #[test] + #[ignore] + fn upgrade_in_tmpdir() { + let temp_dir = TempDir::new(); + let exe_path = temp_dir.path().join("deno"); + let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); + assert!(exe_path.exists()); + let _mtime1 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); + let status = Command::new(&exe_path) + .arg("upgrade") + .arg("--force") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + let _mtime2 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); + // TODO(ry) assert!(mtime1 < mtime2); + } + + // Warning: this test requires internet access. + // TODO(#7412): reenable. test is flaky + #[test] + #[ignore] + fn upgrade_with_space_in_path() { + let temp_dir = TempDir::new_with_prefix("directory with spaces"); + let exe_path = temp_dir.path().join("deno"); + let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); + assert!(exe_path.exists()); + let status = Command::new(&exe_path) + .arg("upgrade") + .arg("--force") + .env("TMP", temp_dir.path()) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + + // Warning: this test requires internet access. + // TODO(#7412): reenable. test is flaky + #[test] + #[ignore] + fn upgrade_with_version_in_tmpdir() { + let temp_dir = TempDir::new(); + let exe_path = temp_dir.path().join("deno"); + let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); + assert!(exe_path.exists()); + let _mtime1 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); + let status = Command::new(&exe_path) + .arg("upgrade") + .arg("--force") + .arg("--version") + .arg("1.11.5") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + let upgraded_deno_version = String::from_utf8( + Command::new(&exe_path).arg("-V").output().unwrap().stdout, + ) + .unwrap(); + assert!(upgraded_deno_version.contains("1.11.5")); + let _mtime2 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); + // TODO(ry) assert!(mtime1 < mtime2); + } + + // Warning: this test requires internet access. + // TODO(#7412): reenable. test is flaky + #[test] + #[ignore] + fn upgrade_with_canary_in_tmpdir() { + let temp_dir = TempDir::new(); + let exe_path = temp_dir.path().join("deno"); + let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); + assert!(exe_path.exists()); + let _mtime1 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); + let status = Command::new(&exe_path) + .arg("upgrade") + .arg("--canary") + .arg("--version") + .arg("e6685f0f01b8a11a5eaff020f5babcfde76b3038") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + let upgraded_deno_version = String::from_utf8( + Command::new(&exe_path).arg("-V").output().unwrap().stdout, + ) + .unwrap(); + assert!(upgraded_deno_version.contains("e6685f0")); + let _mtime2 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); + // TODO(ry) assert!(mtime1 < mtime2); + } + + // Warning: this test requires internet access. + // TODO(#7412): reenable. test is flaky + #[test] + #[ignore] + fn upgrade_with_out_in_tmpdir() { + let temp_dir = TempDir::new(); + let exe_path = temp_dir.path().join("deno"); + let new_exe_path = temp_dir.path().join("foo"); + let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); + assert!(exe_path.exists()); + let mtime1 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); + let status = Command::new(&exe_path) + .arg("upgrade") + .arg("--version") + .arg("1.11.5") + .arg("--output") + .arg(new_exe_path.to_str().unwrap()) + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + assert!(new_exe_path.exists()); + let mtime2 = std::fs::metadata(&exe_path).unwrap().modified().unwrap(); + assert_eq!(mtime1, mtime2); // Original exe_path was not changed. + + let v = String::from_utf8( + Command::new(&new_exe_path) + .arg("-V") + .output() + .unwrap() + .stdout, + ) + .unwrap(); + assert!(v.contains("1.11.5")); + } + + // Warning: this test requires internet access. + // TODO(#7412): reenable. test is flaky + #[test] + #[ignore] + fn upgrade_invalid_stable_version() { + let temp_dir = TempDir::new(); + let exe_path = temp_dir.path().join("deno"); + let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); + assert!(exe_path.exists()); + let output = Command::new(&exe_path) + .arg("upgrade") + .arg("--version") + .arg("foobar") + .stderr(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + assert_eq!( + "error: Invalid semver passed\n", + util::strip_ansi_codes(&String::from_utf8(output.stderr).unwrap()) + ); + } + + // Warning: this test requires internet access. + // TODO(#7412): reenable. test is flaky + #[test] + #[ignore] + fn upgrade_invalid_canary_version() { + let temp_dir = TempDir::new(); + let exe_path = temp_dir.path().join("deno"); + let _ = std::fs::copy(util::deno_exe_path(), &exe_path).unwrap(); + assert!(exe_path.exists()); + let output = Command::new(&exe_path) + .arg("upgrade") + .arg("--canary") + .arg("--version") + .arg("foobar") + .stderr(Stdio::piped()) + .spawn() + .unwrap() + .wait_with_output() + .unwrap(); + assert!(!output.status.success()); + assert_eq!( + "error: Invalid commit hash passed\n", + util::strip_ansi_codes(&String::from_utf8(output.stderr).unwrap()) + ); + } +} diff --git a/cli/tests/vendor_tests.rs b/cli/tests/vendor_tests.rs new file mode 100644 index 0000000000..6f9628a406 --- /dev/null +++ b/cli/tests/vendor_tests.rs @@ -0,0 +1,583 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod vendor { + use deno_core::serde_json; + use deno_core::serde_json::json; + use pretty_assertions::assert_eq; + use std::fmt::Write as _; + use std::path::PathBuf; + use std::process::Stdio; + use test_util as util; + use test_util::TempDir; + use util::http_server; + use util::new_deno_dir; + + #[test] + fn output_dir_exists() { + let t = TempDir::new(); + t.write("mod.ts", ""); + t.create_dir_all("vendor"); + t.write("vendor/mod.ts", ""); + + let deno = util::deno_cmd() + .current_dir(t.path()) + .env("NO_COLOR", "1") + .arg("vendor") + .arg("mod.ts") + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!( + String::from_utf8_lossy(&output.stderr).trim(), + concat!( + "error: Output directory was not empty. Please specify an empty ", + "directory or use --force to ignore this error and potentially ", + "overwrite its contents.", + ), + ); + assert!(!output.status.success()); + + // ensure it errors when using the `--output` arg too + let deno = util::deno_cmd() + .current_dir(t.path()) + .env("NO_COLOR", "1") + .arg("vendor") + .arg("--output") + .arg("vendor") + .arg("mod.ts") + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!( + String::from_utf8_lossy(&output.stderr).trim(), + concat!( + "error: Output directory was not empty. Please specify an empty ", + "directory or use --force to ignore this error and potentially ", + "overwrite its contents.", + ), + ); + assert!(!output.status.success()); + + // now use `--force` + let status = util::deno_cmd() + .current_dir(t.path()) + .env("NO_COLOR", "1") + .arg("vendor") + .arg("mod.ts") + .arg("--force") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + + #[test] + fn standard_test() { + let _server = http_server(); + let t = TempDir::new(); + let vendor_dir = t.path().join("vendor2"); + t.write( + "my_app.ts", + "import {Logger} from 'http://localhost:4545/vendor/query_reexport.ts?testing'; new Logger().log('outputted');", + ); + + let deno = util::deno_cmd() + .current_dir(t.path()) + .arg("vendor") + .arg("my_app.ts") + .arg("--output") + .arg("vendor2") + .env("NO_COLOR", "1") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!( + String::from_utf8_lossy(&output.stderr).trim(), + format!( + concat!( + "Download http://localhost:4545/vendor/query_reexport.ts?testing\n", + "Download http://localhost:4545/vendor/logger.ts?test\n", + "{}", + ), + success_text("2 modules", "vendor2", true), + ) + ); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); + assert!(output.status.success()); + + assert!(vendor_dir.exists()); + assert!(!t.path().join("vendor").exists()); + let import_map: serde_json::Value = + serde_json::from_str(&t.read_to_string("vendor2/import_map.json")) + .unwrap(); + assert_eq!( + import_map, + json!({ + "imports": { + "http://localhost:4545/vendor/query_reexport.ts?testing": "./localhost_4545/vendor/query_reexport.ts", + "http://localhost:4545/": "./localhost_4545/", + }, + "scopes": { + "./localhost_4545/": { + "./localhost_4545/vendor/logger.ts?test": "./localhost_4545/vendor/logger.ts" + } + } + }), + ); + + // try running the output with `--no-remote` + let deno = util::deno_cmd() + .current_dir(t.path()) + .env("NO_COLOR", "1") + .arg("run") + .arg("--no-remote") + .arg("--check") + .arg("--quiet") + .arg("--import-map") + .arg("vendor2/import_map.json") + .arg("my_app.ts") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!(String::from_utf8_lossy(&output.stderr).trim(), ""); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "outputted"); + assert!(output.status.success()); + } + + #[test] + fn import_map_output_dir() { + let _server = http_server(); + let t = TempDir::new(); + t.write("mod.ts", ""); + t.create_dir_all("vendor"); + t.write( + "vendor/import_map.json", + // will be ignored + "{ \"imports\": { \"https://localhost:4545/\": \"./localhost/\" }}", + ); + t.write( + "deno.json", + "{ \"import_map\": \"./vendor/import_map.json\" }", + ); + t.write( + "my_app.ts", + "import {Logger} from 'http://localhost:4545/vendor/logger.ts'; new Logger().log('outputted');", + ); + + let deno = util::deno_cmd() + .current_dir(t.path()) + .env("NO_COLOR", "1") + .arg("vendor") + .arg("--force") + .arg("--import-map") + .arg("vendor/import_map.json") + .arg("my_app.ts") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!( + String::from_utf8_lossy(&output.stderr).trim(), + format!( + concat!( + "Ignoring import map. Specifying an import map file ({}) in the deno ", + "vendor output directory is not supported. If you wish to use an ", + "import map while vendoring, please specify one located outside this ", + "directory.\n", + "Download http://localhost:4545/vendor/logger.ts\n", + "{}", + ), + PathBuf::from("vendor").join("import_map.json").display(), + success_text_updated_deno_json("1 module", "vendor/"), + ) + ); + assert!(output.status.success()); + } + + #[test] + fn remote_module_test() { + let _server = http_server(); + let t = TempDir::new(); + let vendor_dir = t.path().join("vendor"); + + let deno = util::deno_cmd() + .current_dir(t.path()) + .env("NO_COLOR", "1") + .arg("vendor") + .arg("http://localhost:4545/vendor/query_reexport.ts") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!( + String::from_utf8_lossy(&output.stderr).trim(), + format!( + concat!( + "Download http://localhost:4545/vendor/query_reexport.ts\n", + "Download http://localhost:4545/vendor/logger.ts?test\n", + "{}", + ), + success_text("2 modules", "vendor/", true), + ) + ); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); + assert!(output.status.success()); + assert!(vendor_dir.exists()); + assert!(vendor_dir + .join("localhost_4545/vendor/query_reexport.ts") + .exists()); + assert!(vendor_dir.join("localhost_4545/vendor/logger.ts").exists()); + let import_map: serde_json::Value = + serde_json::from_str(&t.read_to_string("vendor/import_map.json")) + .unwrap(); + assert_eq!( + import_map, + json!({ + "imports": { + "http://localhost:4545/": "./localhost_4545/", + }, + "scopes": { + "./localhost_4545/": { + "./localhost_4545/vendor/logger.ts?test": "./localhost_4545/vendor/logger.ts", + } + } + }), + ); + } + + #[test] + fn existing_import_map_no_remote() { + let _server = http_server(); + let t = TempDir::new(); + t.write( + "mod.ts", + "import {Logger} from 'http://localhost:4545/vendor/logger.ts';", + ); + let import_map_filename = "imports2.json"; + let import_map_text = + r#"{ "imports": { "http://localhost:4545/vendor/": "./logger/" } }"#; + t.write(import_map_filename, import_map_text); + t.create_dir_all("logger"); + t.write("logger/logger.ts", "export class Logger {}"); + + let deno = util::deno_cmd() + .current_dir(t.path()) + .env("NO_COLOR", "1") + .arg("vendor") + .arg("mod.ts") + .arg("--import-map") + .arg(import_map_filename) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!( + String::from_utf8_lossy(&output.stderr).trim(), + success_text("0 modules", "vendor/", false) + ); + assert!(output.status.success()); + // it should not have found any remote dependencies because + // the provided import map mapped it to a local directory + assert_eq!(t.read_to_string(import_map_filename), import_map_text); + } + + #[test] + fn existing_import_map_mixed_with_remote() { + let _server = http_server(); + let deno_dir = new_deno_dir(); + let t = TempDir::new(); + t.write( + "mod.ts", + "import {Logger} from 'http://localhost:4545/vendor/logger.ts';", + ); + + let status = util::deno_cmd_with_deno_dir(&deno_dir) + .current_dir(t.path()) + .arg("vendor") + .arg("mod.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + + assert_eq!( + t.read_to_string("vendor/import_map.json"), + r#"{ + "imports": { + "http://localhost:4545/": "./localhost_4545/" + } +} +"#, + ); + + // make the import map specific to support vendoring mod.ts in the next step + t.write( + "vendor/import_map.json", + r#"{ + "imports": { + "http://localhost:4545/vendor/logger.ts": "./localhost_4545/vendor/logger.ts" + } +} +"#, + ); + + t.write( + "mod.ts", + concat!( + "import {Logger} from 'http://localhost:4545/vendor/logger.ts';\n", + "import {Logger as OtherLogger} from 'http://localhost:4545/vendor/mod.ts';\n", + ), + ); + + // now vendor with the existing import map in a separate vendor directory + let deno = util::deno_cmd_with_deno_dir(&deno_dir) + .env("NO_COLOR", "1") + .current_dir(t.path()) + .arg("vendor") + .arg("mod.ts") + .arg("--import-map") + .arg("vendor/import_map.json") + .arg("--output") + .arg("vendor2") + .stderr(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!( + String::from_utf8_lossy(&output.stderr).trim(), + format!( + concat!("Download http://localhost:4545/vendor/mod.ts\n", "{}",), + success_text("1 module", "vendor2", true), + ) + ); + assert!(output.status.success()); + + // tricky scenario here where the output directory now contains a mapping + // back to the previous vendor location + assert_eq!( + t.read_to_string("vendor2/import_map.json"), + r#"{ + "imports": { + "http://localhost:4545/vendor/logger.ts": "../vendor/localhost_4545/vendor/logger.ts", + "http://localhost:4545/": "./localhost_4545/" + }, + "scopes": { + "./localhost_4545/": { + "./localhost_4545/vendor/logger.ts": "../vendor/localhost_4545/vendor/logger.ts" + } + } +} +"#, + ); + + // ensure it runs + let status = util::deno_cmd() + .current_dir(t.path()) + .arg("run") + .arg("--check") + .arg("--no-remote") + .arg("--import-map") + .arg("vendor2/import_map.json") + .arg("mod.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + } + + #[test] + fn dynamic_import() { + let _server = http_server(); + let t = TempDir::new(); + t.write( + "mod.ts", + "import {Logger} from 'http://localhost:4545/vendor/dynamic.ts'; new Logger().log('outputted');", + ); + + let status = util::deno_cmd() + .current_dir(t.path()) + .arg("vendor") + .arg("mod.ts") + .spawn() + .unwrap() + .wait() + .unwrap(); + assert!(status.success()); + let import_map: serde_json::Value = + serde_json::from_str(&t.read_to_string("vendor/import_map.json")) + .unwrap(); + assert_eq!( + import_map, + json!({ + "imports": { + "http://localhost:4545/": "./localhost_4545/", + } + }), + ); + + // try running the output with `--no-remote` + let deno = util::deno_cmd() + .current_dir(t.path()) + .env("NO_COLOR", "1") + .arg("run") + .arg("--allow-read=.") + .arg("--no-remote") + .arg("--check") + .arg("--quiet") + .arg("--import-map") + .arg("vendor/import_map.json") + .arg("mod.ts") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!(String::from_utf8_lossy(&output.stderr).trim(), ""); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "outputted"); + assert!(output.status.success()); + } + + #[test] + fn dynamic_non_analyzable_import() { + let _server = http_server(); + let t = TempDir::new(); + t.write( + "mod.ts", + "import {Logger} from 'http://localhost:4545/vendor/dynamic_non_analyzable.ts'; new Logger().log('outputted');", + ); + + let deno = util::deno_cmd() + .current_dir(t.path()) + .env("NO_COLOR", "1") + .arg("vendor") + .arg("--reload") + .arg("mod.ts") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + // todo(https://github.com/denoland/deno_graph/issues/138): it should warn about + // how it couldn't analyze the dynamic import + assert_eq!( + String::from_utf8_lossy(&output.stderr).trim(), + format!( + "Download http://localhost:4545/vendor/dynamic_non_analyzable.ts\n{}", + success_text("1 module", "vendor/", true), + ) + ); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); + assert!(output.status.success()); + } + + #[test] + fn update_existing_config_test() { + let _server = http_server(); + let t = TempDir::new(); + t.write( + "my_app.ts", + "import {Logger} from 'http://localhost:4545/vendor/logger.ts'; new Logger().log('outputted');", + ); + t.write("deno.json", "{\n}"); + + let deno = util::deno_cmd() + .current_dir(t.path()) + .arg("vendor") + .arg("my_app.ts") + .arg("--output") + .arg("vendor2") + .env("NO_COLOR", "1") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!( + String::from_utf8_lossy(&output.stderr).trim(), + format!( + "Download http://localhost:4545/vendor/logger.ts\n{}", + success_text_updated_deno_json("1 module", "vendor2",) + ) + ); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), ""); + assert!(output.status.success()); + + // try running the output with `--no-remote` and not specifying a `--vendor` + let deno = util::deno_cmd() + .current_dir(t.path()) + .env("NO_COLOR", "1") + .arg("run") + .arg("--no-remote") + .arg("--check") + .arg("--quiet") + .arg("my_app.ts") + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn() + .unwrap(); + let output = deno.wait_with_output().unwrap(); + assert_eq!(String::from_utf8_lossy(&output.stderr).trim(), ""); + assert_eq!(String::from_utf8_lossy(&output.stdout).trim(), "outputted"); + assert!(output.status.success()); + } + + fn success_text( + module_count: &str, + dir: &str, + has_import_map: bool, + ) -> String { + let mut text = format!("Vendored {} into {} directory.", module_count, dir); + if has_import_map { + let f = format!( + concat!( + "\n\nTo use vendored modules, specify the `--import-map {}import_map.json` flag when ", + r#"invoking Deno subcommands or add an `"importMap": ""` "#, + "entry to a deno.json file.", + ), + if dir != "vendor/" { + format!("{}{}", dir.trim_end_matches('/'), if cfg!(windows) { '\\' } else {'/'}) + } else { + dir.to_string() + } + ); + write!(text, "{}", f).unwrap(); + } + text + } + + fn success_text_updated_deno_json(module_count: &str, dir: &str) -> String { + format!( + concat!( + "Vendored {} into {} directory.\n\n", + "Updated your local Deno configuration file with a reference to the ", + "new vendored import map at {}import_map.json. Invoking Deno subcommands will ", + "now automatically resolve using the vendored modules. You may override ", + "this by providing the `--import-map ` flag or by ", + "manually editing your Deno configuration file.", + ), + module_count, + dir, + if dir != "vendor/" { + format!( + "{}{}", + dir.trim_end_matches('/'), + if cfg!(windows) { '\\' } else { '/' } + ) + } else { + dir.to_string() + } + ) + } +} diff --git a/cli/tests/watcher_tests.rs b/cli/tests/watcher_tests.rs new file mode 100644 index 0000000000..0ef9d72b54 --- /dev/null +++ b/cli/tests/watcher_tests.rs @@ -0,0 +1,1247 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +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; + +mod watcher { + use super::*; + + const CLEAR_SCREEN: &str = r#"[2J"#; + + // Helper function to skip watcher output that contains "Restarting" + // phrase. + fn skip_restarting_line( + stderr_lines: &mut impl Iterator, + ) -> String { + loop { + let msg = stderr_lines.next().unwrap(); + if !msg.contains("Restarting") { + return msg; + } + } + } + + fn read_all_lints(stderr_lines: &mut impl Iterator) -> String { + let mut str = String::new(); + for t in stderr_lines { + let t = util::strip_ansi_codes(&t); + if t.starts_with("Watcher File change detected") { + continue; + } + if t.starts_with("Watcher") { + break; + } + if t.starts_with('(') { + str.push_str(&t); + str.push('\n'); + } + } + str + } + + fn wait_for( + condition: impl Fn(&str) -> bool, + lines: &mut impl Iterator, + ) { + loop { + let msg = lines.next().unwrap(); + if condition(&msg) { + break; + } + } + } + + fn wait_contains(s: &str, lines: &mut impl Iterator) { + wait_for(|msg| msg.contains(s), lines) + } + + fn read_line(s: &str, lines: &mut impl Iterator) -> String { + lines.find(|m| m.contains(s)).unwrap() + } + + fn check_alive_then_kill(mut child: std::process::Child) { + assert!(child.try_wait().unwrap().is_none()); + child.kill().unwrap(); + } + + 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!("STERR: {}", line); + line + }); + (stdout_lines, stderr_lines) + } + + #[test] + fn lint_watch_test() { + let t = TempDir::new(); + let badly_linted_original = + util::testdata_path().join("lint/watch/badly_linted.js"); + let badly_linted_output = + util::testdata_path().join("lint/watch/badly_linted.js.out"); + let badly_linted_fixed1 = + util::testdata_path().join("lint/watch/badly_linted_fixed1.js"); + let badly_linted_fixed1_output = + util::testdata_path().join("lint/watch/badly_linted_fixed1.js.out"); + let badly_linted_fixed2 = + util::testdata_path().join("lint/watch/badly_linted_fixed2.js"); + let badly_linted_fixed2_output = + util::testdata_path().join("lint/watch/badly_linted_fixed2.js.out"); + let badly_linted = t.path().join("badly_linted.js"); + + std::fs::copy(&badly_linted_original, &badly_linted).unwrap(); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("lint") + .arg(&badly_linted) + .arg("--watch") + .arg("--unstable") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); + let next_line = stderr_lines.next().unwrap(); + assert_contains!(&next_line, "Lint started"); + let mut output = read_all_lints(&mut stderr_lines); + let expected = std::fs::read_to_string(badly_linted_output).unwrap(); + assert_eq!(output, expected); + + // Change content of the file again to be badly-linted1 + 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); + 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-linted1 + std::fs::copy(&badly_linted_fixed2, &badly_linted).unwrap(); + + output = read_all_lints(&mut stderr_lines); + let expected = std::fs::read_to_string(badly_linted_fixed2_output).unwrap(); + assert_eq!(output, expected); + + // the watcher process is still alive + assert!(child.try_wait().unwrap().is_none()); + + child.kill().unwrap(); + drop(t); + } + + #[test] + fn lint_watch_without_args_test() { + let t = TempDir::new(); + let badly_linted_original = + util::testdata_path().join("lint/watch/badly_linted.js"); + let badly_linted_output = + util::testdata_path().join("lint/watch/badly_linted.js.out"); + let badly_linted_fixed1 = + util::testdata_path().join("lint/watch/badly_linted_fixed1.js"); + let badly_linted_fixed1_output = + util::testdata_path().join("lint/watch/badly_linted_fixed1.js.out"); + let badly_linted_fixed2 = + util::testdata_path().join("lint/watch/badly_linted_fixed2.js"); + let badly_linted_fixed2_output = + util::testdata_path().join("lint/watch/badly_linted_fixed2.js.out"); + let badly_linted = t.path().join("badly_linted.js"); + + std::fs::copy(&badly_linted_original, &badly_linted).unwrap(); + + let mut child = util::deno_cmd() + .current_dir(t.path()) + .arg("lint") + .arg("--watch") + .arg("--unstable") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); + + let next_line = stderr_lines.next().unwrap(); + assert_contains!(&next_line, "Lint started"); + let mut output = read_all_lints(&mut stderr_lines); + let expected = std::fs::read_to_string(badly_linted_output).unwrap(); + assert_eq!(output, expected); + + // Change content of the file again to be badly-linted1 + std::fs::copy(&badly_linted_fixed1, &badly_linted).unwrap(); + + output = read_all_lints(&mut stderr_lines); + 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-linted1 + 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); + let expected = std::fs::read_to_string(badly_linted_fixed2_output).unwrap(); + assert_eq!(output, expected); + + // the watcher process is still alive + assert!(child.try_wait().unwrap().is_none()); + + child.kill().unwrap(); + drop(t); + } + + #[test] + 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"); + let badly_linted_fixed1 = + util::testdata_path().join("lint/watch/badly_linted_fixed1.js"); + let badly_linted_fixed2 = + util::testdata_path().join("lint/watch/badly_linted_fixed2.js"); + + let badly_linted_1 = t.path().join("badly_linted_1.js"); + let badly_linted_2 = t.path().join("badly_linted_2.js"); + std::fs::copy(&badly_linted_fixed0, &badly_linted_1).unwrap(); + std::fs::copy(&badly_linted_fixed1, &badly_linted_2).unwrap(); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("lint") + .arg(t.path()) + .arg("--watch") + .arg("--unstable") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); + + assert_contains!( + read_line("Checked", &mut stderr_lines), + "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!(child.try_wait().unwrap().is_none()); + + child.kill().unwrap(); + drop(t); + } + + #[test] + 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"); + let badly_formatted_original = + fmt_testdata_path.join("badly_formatted.mjs"); + let badly_formatted = t.path().join("badly_formatted.js"); + std::fs::copy(&badly_formatted_original, &badly_formatted).unwrap(); + + let mut child = util::deno_cmd() + .current_dir(&fmt_testdata_path) + .arg("fmt") + .arg(&badly_formatted) + .arg("--watch") + .arg("--unstable") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); + + let next_line = stderr_lines.next().unwrap(); + assert_contains!(&next_line, "Fmt started"); + assert_contains!( + skip_restarting_line(&mut stderr_lines), + "badly_formatted.js" + ); + assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); + + let expected = std::fs::read_to_string(fixed.clone()).unwrap(); + let actual = std::fs::read_to_string(badly_formatted.clone()).unwrap(); + assert_eq!(actual, expected); + + // 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), + "badly_formatted.js" + ); + assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); + + // 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!(actual, expected); + check_alive_then_kill(child); + } + + #[test] + 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"); + let badly_formatted_original = + fmt_testdata_path.join("badly_formatted.mjs"); + let badly_formatted = t.path().join("badly_formatted.js"); + std::fs::copy(&badly_formatted_original, &badly_formatted).unwrap(); + + let mut child = util::deno_cmd() + .current_dir(t.path()) + .arg("fmt") + .arg("--watch") + .arg("--unstable") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); + + let next_line = stderr_lines.next().unwrap(); + assert_contains!(&next_line, "Fmt started"); + assert_contains!( + skip_restarting_line(&mut stderr_lines), + "badly_formatted.js" + ); + assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); + + let expected = std::fs::read_to_string(fixed.clone()).unwrap(); + let actual = std::fs::read_to_string(badly_formatted.clone()).unwrap(); + assert_eq!(actual, expected); + + // 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), + "badly_formatted.js" + ); + assert_contains!(read_line("Checked", &mut stderr_lines), "Checked 1 file"); + + // 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!(actual, expected); + check_alive_then_kill(child); + } + + #[test] + 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"); + let badly_formatted_1 = t.path().join("badly_formatted_1.js"); + let badly_formatted_2 = t.path().join("badly_formatted_2.js"); + std::fs::copy(&badly_formatted_original, &badly_formatted_1).unwrap(); + std::fs::copy(&badly_formatted_original, &badly_formatted_2).unwrap(); + + let mut child = util::deno_cmd() + .current_dir(&fmt_testdata_path) + .arg("fmt") + .arg(t.path()) + .arg("--watch") + .arg("--check") + .arg("--unstable") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (_stdout_lines, mut stderr_lines) = child_lines(&mut child); + + assert_contains!( + read_line("error", &mut stderr_lines), + "Found 2 not formatted files in 2 files" + ); + + // Change content of the file again to be badly formatted + std::fs::copy(&badly_formatted_original, &badly_formatted_1).unwrap(); + + assert_contains!( + read_line("error", &mut stderr_lines), + "Found 2 not formatted files in 2 files" + ); + + check_alive_then_kill(child); + } + + #[test] + fn bundle_js_watch() { + use std::path::PathBuf; + // Test strategy extends this of test bundle_js by adding watcher + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.ts"); + write(&file_to_watch, "console.log('Hello world');").unwrap(); + assert!(file_to_watch.is_file()); + let t = TempDir::new(); + let bundle = t.path().join("mod6.bundle.js"); + let mut deno = util::deno_cmd() + .current_dir(util::testdata_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() + .unwrap(); + + let (_stdout_lines, mut stderr_lines) = child_lines(&mut deno); + + 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"); + let file = PathBuf::from(&bundle); + assert!(file.is_file()); + wait_contains("Bundle finished", &mut stderr_lines); + + 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, 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"); + let file = PathBuf::from(&bundle); + assert!(file.is_file()); + wait_contains("Bundle finished", &mut stderr_lines); + + // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax + write(&file_to_watch, "syntax error ^^").unwrap(); + + assert_contains!(stderr_lines.next().unwrap(), "File change detected!"); + assert_contains!(stderr_lines.next().unwrap(), "error: "); + wait_contains("Bundle failed", &mut stderr_lines); + 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() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.ts"); + write(&file_to_watch, "syntax error ^^").unwrap(); + let target_file = t.path().join("target.js"); + + let mut deno = util::deno_cmd() + .current_dir(util::testdata_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() + .unwrap(); + let (_stdout_lines, mut stderr_lines) = child_lines(&mut deno); + + let next_line = stderr_lines.next().unwrap(); + assert_contains!(&next_line, "Bundle started"); + assert_contains!(stderr_lines.next().unwrap(), "error:"); + assert_contains!(stderr_lines.next().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, 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"); + + wait_contains("Bundle finished", &mut stderr_lines); + + // bundled file is created + assert!(target_file.is_file()); + check_alive_then_kill(deno); + } + + #[test] + fn run_watch_no_dynamic() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.js"); + write(&file_to_watch, "console.log('Hello world');").unwrap(); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--watch") + .arg("--unstable") + .arg("-L") + .arg("debug") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); + + wait_contains("Hello world", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); + + // Change content of the file + write(&file_to_watch, "console.log('Hello world2');").unwrap(); + + wait_contains("Restarting", &mut stderr_lines); + wait_contains("Hello world2", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); + + // Add dependency + let another_file = t.path().join("another_file.js"); + write(&another_file, "export const foo = 0;").unwrap(); + write( + &file_to_watch, + "import { foo } from './another_file.js'; console.log(foo);", + ) + .unwrap(); + + wait_contains("Restarting", &mut stderr_lines); + wait_contains("0", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("another_file.js"), + &mut stderr_lines, + ); + + // Confirm that restarting occurs when a new file is updated + write(&another_file, "export const foo = 42;").unwrap(); + + wait_contains("Restarting", &mut stderr_lines); + wait_contains("42", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); + + // Confirm that the watcher keeps on working even if the file is updated and has invalid syntax + write(&file_to_watch, "syntax error ^^").unwrap(); + + wait_contains("Restarting", &mut stderr_lines); + wait_contains("error:", &mut stderr_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); + + // Then restore the file + write( + &file_to_watch, + "import { foo } from './another_file.js'; console.log(foo);", + ) + .unwrap(); + + wait_contains("Restarting", &mut stderr_lines); + wait_contains("42", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("another_file.js"), + &mut stderr_lines, + ); + + // Update the content of the imported file with invalid syntax + write(&another_file, "syntax error ^^").unwrap(); + + wait_contains("Restarting", &mut stderr_lines); + wait_contains("error:", &mut stderr_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("another_file.js"), + &mut stderr_lines, + ); + + // Modify the imported file and make sure that restarting occurs + write(&another_file, "export const foo = 'modified!';").unwrap(); + + wait_contains("Restarting", &mut stderr_lines); + wait_contains("modified!", &mut stdout_lines); + wait_contains("Watching paths", &mut stderr_lines); + check_alive_then_kill(child); + } + + // TODO(bartlomieju): this test became flaky on macOS runner; it is unclear + // 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() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.js"); + write(&file_to_watch, "console.log('Hello world');").unwrap(); + + let external_file_to_watch = t.path().join("external_file_to_watch.txt"); + write(&external_file_to_watch, "Hello world").unwrap(); + + let mut watch_arg = "--watch=".to_owned(); + let external_file_to_watch_str = external_file_to_watch + .clone() + .into_os_string() + .into_string() + .unwrap(); + watch_arg.push_str(&external_file_to_watch_str); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg(watch_arg) + .arg("-L") + .arg("debug") + .arg("--unstable") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .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( + |m| { + m.contains("Watching paths") && m.contains("external_file_to_watch.txt") + }, + &mut stderr_lines, + ); + + // Change content of the external file + write(&external_file_to_watch, "Hello world2").unwrap(); + + wait_contains("Restarting", &mut stderr_lines); + wait_contains("Process finished", &mut stderr_lines); + check_alive_then_kill(child); + } + + #[test] + fn run_watch_load_unload_events() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.js"); + write( + &file_to_watch, + r#" + setInterval(() => {}, 0); + window.addEventListener("load", () => { + console.log("load"); + }); + + window.addEventListener("unload", () => { + console.log("unload"); + }); + "#, + ) + .unwrap(); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--watch") + .arg("--unstable") + .arg("-L") + .arg("debug") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + 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( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); + + // Change content of the file, this time without an interval to keep it alive. + write( + &file_to_watch, + r#" + window.addEventListener("load", () => { + console.log("load"); + }); + + window.addEventListener("unload", () => { + console.log("unload"); + }); + "#, + ) + .unwrap(); + + // Wait for the restart + wait_contains("Restarting", &mut stderr_lines); + + // Confirm that the unload event was dispatched from the first run + wait_contains("unload", &mut stdout_lines); + + // Followed by the load event of the second run + wait_contains("load", &mut stdout_lines); + + // Which is then unloaded as there is nothing keeping it alive. + wait_contains("unload", &mut stdout_lines); + 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() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.js"); + write(&file_to_watch, "syntax error ^^").unwrap(); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--watch") + .arg("--unstable") + .arg("-L") + .arg("debug") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .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( + |m| m.contains("Watching paths") && m.contains("file_to_watch.js"), + &mut stderr_lines, + ); + + // Make sure the watcher actually restarts and works fine with the proper syntax + write(&file_to_watch, "console.log(42);").unwrap(); + + wait_contains("Restarting", &mut stderr_lines); + wait_contains("42", &mut stdout_lines); + wait_contains("Process finished", &mut stderr_lines); + check_alive_then_kill(child); + } + + #[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); + write(&absolute_path, filecontent).unwrap(); + let relative_path = absolute_path + .strip_prefix(util::testdata_path()) + .unwrap() + .to_owned(); + assert!(relative_path.is_relative()); + relative_path + } + let temp_directory = TempDir::new_in(&util::testdata_path()); + 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::testdata_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() + .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"); + + check_alive_then_kill(child); + } + + #[test] + fn run_watch_error_messages() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.js"); + write( + &file_to_watch, + "throw SyntaxError(`outer`, {cause: TypeError(`inner`)})", + ) + .unwrap(); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--watch") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .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); + + check_alive_then_kill(child); + } + + #[test] + fn test_watch() { + let t = TempDir::new(); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_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() + .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); + + 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"); + write(&foo_file, "export default function foo() { 1 + 1 }").unwrap(); + write(&bar_file, "export default function bar() { 2 + 2 }").unwrap(); + write( + &foo_test, + "import foo from './foo.js'; Deno.test('foo', foo);", + ) + .unwrap(); + write( + &bar_test, + "import bar from './bar.js'; Deno.test('bar', bar);", + ) + .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); + + // Change content of the file + write( + &foo_test, + "import foo from './foo.js'; Deno.test('foobar', foo);", + ) + .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); + + // 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); + + // 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); + + // 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_contains!(stderr_lines.next().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); + + // 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 + write( + &foo_file, + "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); + + // 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); + + // Test that circular dependencies work fine + write( + &foo_file, + "import './bar.js'; export default function foo() { 1 + 1 }", + ) + .unwrap(); + write( + &bar_file, + "import './foo.js'; export default function bar() { 2 + 2 }", + ) + .unwrap(); + check_alive_then_kill(child); + } + + #[flaky_test] + fn test_watch_doc() { + let t = TempDir::new(); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_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() + .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); + + let foo_file = t.path().join("foo.ts"); + write( + &foo_file, + r#" + export default function foo() {} + "#, + ) + .unwrap(); + + write( + &foo_file, + r#" + /** + * ```ts + * import foo from "./foo.ts"; + * ``` + */ + export default function foo() {} + "#, + ) + .unwrap(); + + // 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"); + check_alive_then_kill(child); + } + + #[test] + 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(); + let mut child = util::deno_cmd() + .current_dir(util::testdata_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() + .unwrap(); + let (_, mut stderr_lines) = child_lines(&mut child); + let line1 = stderr_lines.next().unwrap(); + assert_contains!(&line1, "Process started"); + let line2 = stderr_lines.next().unwrap(); + assert_contains!(&line2, "error: Module not found"); + assert_contains!(&line2, "nonexistent.js"); + let line3 = stderr_lines.next().unwrap(); + assert_contains!(&line3, " at "); + assert_contains!(&line3, "file_to_watch.js"); + wait_contains("Process finished", &mut stderr_lines); + check_alive_then_kill(child); + } + + // Regression test for https://github.com/denoland/deno/issues/15428. + #[test] + fn test_watch_unload_handler_error_on_drop() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.js"); + write( + &file_to_watch, + r#" + addEventListener("unload", () => { + throw new Error("foo"); + }); + setTimeout(() => { + throw new Error("bar"); + }); + "#, + ) + .unwrap(); + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--watch") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .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); + check_alive_then_kill(child); + } + + #[test] + fn run_watch_dynamic_imports() { + let t = TempDir::new(); + let file_to_watch = t.path().join("file_to_watch.js"); + write( + &file_to_watch, + r#" + console.log("Hopefully dynamic import will be watched..."); + await import("./imported.js"); + "#, + ) + .unwrap(); + let file_to_watch2 = t.path().join("imported.js"); + write( + &file_to_watch2, + r#" + import "./imported2.js"; + console.log("I'm dynamically imported and I cause restarts!"); + "#, + ) + .unwrap(); + let file_to_watch3 = t.path().join("imported2.js"); + write( + &file_to_watch3, + r#" + console.log("I'm statically imported from the dynamic import"); + "#, + ) + .unwrap(); + + let mut child = util::deno_cmd() + .current_dir(util::testdata_path()) + .arg("run") + .arg("--watch") + .arg("--unstable") + .arg("--allow-read") + .arg("-L") + .arg("debug") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); + + assert_contains!(stderr_lines.next().unwrap(), "Process started"); + + wait_contains( + "Hopefully dynamic import will be watched...", + &mut stdout_lines, + ); + wait_contains( + "I'm statically imported from the dynamic import", + &mut stdout_lines, + ); + wait_contains( + "I'm dynamically imported and I cause restarts!", + &mut stdout_lines, + ); + + wait_contains("finished", &mut stderr_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains("imported2.js"), + &mut stderr_lines, + ); + + write( + &file_to_watch3, + r#" + console.log("I'm statically imported from the dynamic import and I've changed"); + "#, + ) + .unwrap(); + + wait_contains("Restarting", &mut stderr_lines); + wait_contains( + "Hopefully dynamic import will be watched...", + &mut stdout_lines, + ); + wait_contains( + "I'm statically imported from the dynamic import and I've changed", + &mut stdout_lines, + ); + wait_contains( + "I'm dynamically imported and I cause restarts!", + &mut stdout_lines, + ); + + check_alive_then_kill(child); + } + + // https://github.com/denoland/deno/issues/16267 + #[test] + fn run_watch_flash() { + let filename = "watch_flash.js"; + let t = TempDir::new(); + let file_to_watch = t.path().join(filename); + write( + &file_to_watch, + r#" + console.log("Starting flash server..."); + Deno.serve({ + onListen() { + console.error("First server is listening"); + }, + handler: () => {}, + port: 4601, + }); + "#, + ) + .unwrap(); + + let mut child = util::deno_cmd() + .current_dir(t.path()) + .arg("run") + .arg("--watch") + .arg("--unstable") + .arg("--allow-net") + .arg("-L") + .arg("debug") + .arg(&file_to_watch) + .env("NO_COLOR", "1") + .stdout(std::process::Stdio::piped()) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let (mut stdout_lines, mut stderr_lines) = child_lines(&mut child); + + wait_contains("Starting flash server...", &mut stdout_lines); + wait_for( + |m| m.contains("Watching paths") && m.contains(filename), + &mut stderr_lines, + ); + + write( + &file_to_watch, + r#" + console.log("Restarting flash server..."); + Deno.serve({ + onListen() { + console.error("Second server is listening"); + }, + handler: () => {}, + port: 4601, + }); + "#, + ) + .unwrap(); + + wait_contains("File change detected! Restarting!", &mut stderr_lines); + wait_contains("Restarting flash server...", &mut stdout_lines); + wait_contains("Second server is listening", &mut stderr_lines); + + check_alive_then_kill(child); + } +} diff --git a/cli/tests/worker_tests.rs b/cli/tests/worker_tests.rs new file mode 100644 index 0000000000..1c74225e31 --- /dev/null +++ b/cli/tests/worker_tests.rs @@ -0,0 +1,116 @@ +// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. + +mod integration; + +mod worker { + use super::*; + + itest!(workers { + args: "test --reload --location http://127.0.0.1:4545/ -A --unstable workers/test.ts", + output: "workers/test.ts.out", + http_server: true, + }); + + itest!(worker_error { + args: "run -A workers/worker_error.ts", + output: "workers/worker_error.ts.out", + exit_code: 1, + }); + + itest!(worker_nested_error { + args: "run -A workers/worker_nested_error.ts", + output: "workers/worker_nested_error.ts.out", + exit_code: 1, + }); + + itest!(worker_async_error { + args: "run -A --quiet --reload workers/worker_async_error.ts", + output: "workers/worker_async_error.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(worker_message_handler_error { + args: "run -A --quiet --reload workers/worker_message_handler_error.ts", + output: "workers/worker_message_handler_error.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(nonexistent_worker { + args: "run --allow-read workers/nonexistent_worker.ts", + output: "workers/nonexistent_worker.out", + exit_code: 1, + }); + + itest!(_084_worker_custom_inspect { + args: "run --allow-read workers/custom_inspect/main.ts", + output: "workers/custom_inspect/main.out", + }); + + itest!(error_worker_permissions_local { + args: "run --reload workers/error_worker_permissions_local.ts", + output: "workers/error_worker_permissions_local.ts.out", + exit_code: 1, + }); + + itest!(error_worker_permissions_remote { + args: "run --reload workers/error_worker_permissions_remote.ts", + http_server: true, + output: "workers/error_worker_permissions_remote.ts.out", + exit_code: 1, + }); + + itest!(worker_permissions_remote_remote { + args: "run --quiet --reload --allow-net=localhost:4545 workers/permissions_remote_remote.ts", + output: "workers/permissions_remote_remote.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(worker_permissions_dynamic_remote { + args: "run --quiet --reload --allow-net --unstable workers/permissions_dynamic_remote.ts", + output: "workers/permissions_dynamic_remote.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(worker_permissions_data_remote { + args: "run --quiet --reload --allow-net=localhost:4545 workers/permissions_data_remote.ts", + output: "workers/permissions_data_remote.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(worker_permissions_blob_remote { + args: "run --quiet --reload --allow-net=localhost:4545 workers/permissions_blob_remote.ts", + output: "workers/permissions_blob_remote.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(worker_permissions_data_local { + args: "run --quiet --reload --allow-net=localhost:4545 workers/permissions_data_local.ts", + output: "workers/permissions_data_local.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(worker_permissions_blob_local { + args: "run --quiet --reload --allow-net=localhost:4545 workers/permissions_blob_local.ts", + output: "workers/permissions_blob_local.ts.out", + http_server: true, + exit_code: 1, + }); + + itest!(worker_terminate_tla_crash { + args: "run --quiet --reload workers/terminate_tla_crash.js", + output: "workers/terminate_tla_crash.js.out", + }); + + itest!(worker_error_event { + args: "run --quiet -A workers/error_event.ts", + output: "workers/error_event.ts.out", + exit_code: 1, + }); +}