From b3ca3b2f25931afb350027bde87dc3d4f9a741b0 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 13 Mar 2024 12:21:13 -0400 Subject: [PATCH] chore: rough first pass on spec tests (#22877) --- Cargo.lock | 2 + Cargo.toml | 1 + cli/Cargo.toml | 2 +- tests/Cargo.toml | 7 + tests/integration/cache_tests.rs | 6 - tests/integration/info_tests.rs | 11 - tests/integration/lsp_tests.rs | 6 +- tests/integration/npm_tests.rs | 48 --- tests/integration/run_tests.rs | 34 --- tests/specs/README.md | 92 ++++++ .../import_map_config/__test__.json | 28 ++ .../import_map/import_map_config/cache.out} | 0 .../import_map/import_map_config}/config.json | 0 .../import_map/import_map_config/config.out} | 0 .../flag_has_precedence.out} | 0 .../import_map_config/import_data_url.out | 3 + .../import_map_config/import_data_url.ts | 12 + .../import_map_config}/import_map.json | 1 - .../import_map_invalid.json | 0 .../import_map/import_map_config/info.out} | 0 .../import_map_config/lodash/lodash.ts | 1 + .../import_map_config/lodash/other_file.ts | 1 + .../import_map_config/moment/moment.ts | 1 + .../import_map_config/moment/other_file.ts | 1 + .../import_map/import_map_config/run.out} | 0 .../import_map_config}/scope/scoped.ts | 0 .../import_map_config}/scoped_moment.ts | 0 .../import_map/import_map_config/test.ts | 6 + .../unmapped_bare_specifier.out | 6 + .../unmapped_bare_specifier.ts} | 0 .../specs/import_map/import_map_config/vue.ts | 1 + tests/specs/info/ts_error/__test__.json | 4 + tests/specs/info/ts_error/info_ts_error.out | 6 + .../info/ts_error/info_ts_error.ts} | 0 tests/specs/mod.rs | 279 ++++++++++++++++++ .../npm/conditional_exports/__test__.json | 5 + .../npm/conditional_exports/main.js | 0 .../npm/conditional_exports/main.out | 0 .../__test__.json | 6 + .../main.js | 15 + .../main.out} | 0 tests/specs/npm/es_module/__test__.json | 22 ++ tests/specs/npm/es_module/bundle.out | 1 + .../npm/esm => specs/npm/es_module}/main.js | 0 .../npm/esm => specs/npm/es_module}/main.out | 0 .../npm/esm => specs/npm/es_module}/test.js | 0 .../npm/esm => specs/npm/es_module}/test.out | 2 +- .../run/redirect_javascript/__test__.json | 4 + .../run/redirect_javascript/main.js} | 0 .../run/redirect_javascript/main.out} | 0 .../import_maps/import_map_remote.json | 3 +- tests/testdata/info/031_info_ts_error.out | 6 - ..._import_map_unmapped_bare_specifier.ts.out | 6 - tests/util/server/Cargo.toml | 1 + tests/util/server/src/fs.rs | 34 ++- tools/copyright_checker.js | 1 + 56 files changed, 544 insertions(+), 121 deletions(-) create mode 100644 tests/specs/README.md create mode 100644 tests/specs/import_map/import_map_config/__test__.json rename tests/{testdata/cache/036_import_map_fetch.out => specs/import_map/import_map_config/cache.out} (100%) rename tests/{testdata/import_maps => specs/import_map/import_map_config}/config.json (100%) rename tests/{testdata/run/033_import_map_in_config_file.out => specs/import_map/import_map_config/config.out} (100%) rename tests/{testdata/run/033_import_map_in_flag_has_precedence.out => specs/import_map/import_map_config/flag_has_precedence.out} (100%) create mode 100644 tests/specs/import_map/import_map_config/import_data_url.out create mode 100644 tests/specs/import_map/import_map_config/import_data_url.ts rename tests/{testdata/import_maps => specs/import_map/import_map_config}/import_map.json (88%) rename tests/{testdata/import_maps => specs/import_map/import_map_config}/import_map_invalid.json (100%) rename tests/{testdata/info/065_import_map_info.out => specs/import_map/import_map_config/info.out} (100%) create mode 100644 tests/specs/import_map/import_map_config/lodash/lodash.ts create mode 100644 tests/specs/import_map/import_map_config/lodash/other_file.ts create mode 100644 tests/specs/import_map/import_map_config/moment/moment.ts create mode 100644 tests/specs/import_map/import_map_config/moment/other_file.ts rename tests/{testdata/run/033_import_map.out => specs/import_map/import_map_config/run.out} (100%) rename tests/{testdata/import_maps => specs/import_map/import_map_config}/scope/scoped.ts (100%) rename tests/{testdata/import_maps => specs/import_map/import_map_config}/scoped_moment.ts (100%) create mode 100644 tests/specs/import_map/import_map_config/test.ts create mode 100644 tests/specs/import_map/import_map_config/unmapped_bare_specifier.out rename tests/{testdata/run/092_import_map_unmapped_bare_specifier.ts => specs/import_map/import_map_config/unmapped_bare_specifier.ts} (100%) create mode 100644 tests/specs/import_map/import_map_config/vue.ts create mode 100644 tests/specs/info/ts_error/__test__.json create mode 100644 tests/specs/info/ts_error/info_ts_error.out rename tests/{testdata/info/031_info_ts_error.ts => specs/info/ts_error/info_ts_error.ts} (100%) create mode 100644 tests/specs/mod.rs create mode 100644 tests/specs/npm/conditional_exports/__test__.json rename tests/{testdata => specs}/npm/conditional_exports/main.js (100%) rename tests/{testdata => specs}/npm/conditional_exports/main.out (100%) create mode 100644 tests/specs/npm/conditional_exports_node_modules_dir/__test__.json create mode 100644 tests/specs/npm/conditional_exports_node_modules_dir/main.js rename tests/{testdata/npm/conditional_exports/main_node_modules.out => specs/npm/conditional_exports_node_modules_dir/main.out} (100%) create mode 100644 tests/specs/npm/es_module/__test__.json create mode 100644 tests/specs/npm/es_module/bundle.out rename tests/{testdata/npm/esm => specs/npm/es_module}/main.js (100%) rename tests/{testdata/npm/esm => specs/npm/es_module}/main.out (100%) rename tests/{testdata/npm/esm => specs/npm/es_module}/test.js (100%) rename tests/{testdata/npm/esm => specs/npm/es_module}/test.out (86%) create mode 100644 tests/specs/run/redirect_javascript/__test__.json rename tests/{testdata/run/026_redirect_javascript.js => specs/run/redirect_javascript/main.js} (100%) rename tests/{testdata/run/026_redirect_javascript.js.out => specs/run/redirect_javascript/main.out} (100%) delete mode 100644 tests/testdata/info/031_info_ts_error.out delete mode 100644 tests/testdata/run/092_import_map_unmapped_bare_specifier.ts.out diff --git a/Cargo.lock b/Cargo.lock index 4fa6abacee..9eb1c5d2e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -644,6 +644,7 @@ dependencies = [ "deno_core", "deno_fetch", "deno_lockfile", + "deno_terminal", "deno_tls", "fastwebsockets", "flaky_test", @@ -6517,6 +6518,7 @@ dependencies = [ "http-body-util", "hyper 1.1.0", "hyper-util", + "jsonc-parser", "lazy-regex", "libc", "lsp-types", diff --git a/Cargo.toml b/Cargo.toml index afd1e7800f..261a6b84f3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -121,6 +121,7 @@ hyper = { version = "=1.1.0", features = ["full"] } hyper-util = { version = "=0.1.2", features = ["tokio", "server", "server-auto"] } hyper_v014 = { package = "hyper", version = "0.14.26", features = ["runtime", "http1"] } indexmap = { version = "2", features = ["serde"] } +jsonc-parser = { version = "=0.23.0", features = ["serde"] } lazy-regex = "3" libc = "0.2.126" libz-sys = { version = "1.1", default-features = false } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 77e67d1186..86511465c8 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -109,7 +109,7 @@ glob = "0.3.1" ignore = "0.4" import_map = { version = "=0.19.0", features = ["ext"] } indexmap.workspace = true -jsonc-parser = { version = "=0.23.0", features = ["serde"] } +jsonc-parser.workspace = true lazy-regex.workspace = true libc.workspace = true libz-sys.workspace = true diff --git a/tests/Cargo.toml b/tests/Cargo.toml index ffef6eb2d8..872920bd9a 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -20,6 +20,12 @@ name = "integration_tests" path = "integration/mod.rs" required-features = ["run"] +[[test]] +name = "specs" +path = "specs/mod.rs" +required-features = ["run"] +harness = false + [[test]] name = "node_compat_tests" path = "node_compat/test_runner.rs" @@ -33,6 +39,7 @@ deno_cache_dir = { workspace = true } deno_core = { workspace = true, features = ["include_js_files_for_snapshotting", "unsafe_use_unprotected_platform"] } deno_fetch.workspace = true deno_lockfile.workspace = true +deno_terminal.workspace = true deno_tls.workspace = true fastwebsockets = { workspace = true, features = ["upgrade", "unstable-split"] } flaky_test = "=0.1.0" diff --git a/tests/integration/cache_tests.rs b/tests/integration/cache_tests.rs index d5b4e88441..988cbc9962 100644 --- a/tests/integration/cache_tests.rs +++ b/tests/integration/cache_tests.rs @@ -5,12 +5,6 @@ use test_util::itest; use test_util::TestContext; use test_util::TestContextBuilder; -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, diff --git a/tests/integration/info_tests.rs b/tests/integration/info_tests.rs index 62e169c9e6..4697cc2b8c 100644 --- a/tests/integration/info_tests.rs +++ b/tests/integration/info_tests.rs @@ -58,11 +58,6 @@ itest!(multiple_imports { 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", @@ -95,12 +90,6 @@ itest!(json_file { 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 --json info/076_info_json_deps_order.ts", output: "info/076_info_json_deps_order.out", diff --git a/tests/integration/lsp_tests.rs b/tests/integration/lsp_tests.rs index a7193ff59e..f1a368413f 100644 --- a/tests/integration/lsp_tests.rs +++ b/tests/integration/lsp_tests.rs @@ -215,7 +215,7 @@ fn lsp_import_map_remote() { temp_dir.write( "deno.json", json!({ - "importMap": "http://localhost:4545/import_maps/import_map.json", + "importMap": "http://localhost:4545/import_maps/import_map_remote.json", }) .to_string(), ); @@ -228,7 +228,9 @@ fn lsp_import_map_remote() { ); let mut client = context.new_lsp_command().build(); client.initialize(|builder| { - builder.set_import_map("http://localhost:4545/import_maps/import_map.json"); + builder.set_import_map( + "http://localhost:4545/import_maps/import_map_remote.json", + ); }); client.write_request( "workspace/executeCommand", diff --git a/tests/integration/npm_tests.rs b/tests/integration/npm_tests.rs index 7df9e4f8ab..d46c0544c9 100644 --- a/tests/integration/npm_tests.rs +++ b/tests/integration/npm_tests.rs @@ -13,30 +13,6 @@ use util::TestContextBuilder; // NOTE: See how to make test npm packages at ./testdata/npm/README.md -itest!(es_module { - args: "run --allow-read --allow-env npm/esm/main.js", - output: "npm/esm/main.out", - envs: env_vars_for_npm_tests(), - http_server: true, -}); - -itest!(es_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_for_npm_tests(), - http_server: true, -}); - -itest!(es_module_deno_test { - args: "test --allow-read --allow-env npm/esm/test.js", - output: "npm/esm/test.out", - envs: env_vars_for_npm_tests(), - http_server: true, -}); - itest!(esm_import_cjs_default { args: "run --allow-read --allow-env --quiet --check=all npm/esm_import_cjs_default/main.ts", output: "npm/esm_import_cjs_default/main.out", @@ -130,22 +106,6 @@ itest!(compare_globals { http_server: true, }); -itest!(conditional_exports { - args: "run --allow-read npm/conditional_exports/main.js", - output: "npm/conditional_exports/main.out", - envs: env_vars_for_npm_tests(), - http_server: true, -}); - -itest!(conditional_exports_node_modules_dir { - args: - "run --allow-read --node-modules-dir $TESTDATA/npm/conditional_exports/main.js", - output: "npm/conditional_exports/main_node_modules.out", - envs: env_vars_for_npm_tests(), - http_server: true, - temp_cwd: true, -}); - itest!(dual_cjs_esm { args: "run -A --quiet npm/dual_cjs_esm/main.ts", output: "npm/dual_cjs_esm/main.out", @@ -1011,14 +971,6 @@ fn ensure_registry_files_local() { } } -itest!(bundle_errors { - args: "bundle --quiet npm/esm/main.js", - output_str: Some("error: npm specifiers have not yet been implemented for this subcommand (https://github.com/denoland/deno/issues/15960). Found: npm:/chalk@5.0.1\n"), - exit_code: 1, - envs: env_vars_for_npm_tests(), - http_server: true, -}); - itest!(info_chalk_display { args: "info --quiet npm/cjs_with_deps/main.js", output: "npm/cjs_with_deps/main_info.out", diff --git a/tests/integration/run_tests.rs b/tests/integration/run_tests.rs index 637dc578bb..1d46cd249d 100644 --- a/tests/integration/run_tests.rs +++ b/tests/integration/run_tests.rs @@ -154,12 +154,6 @@ itest!(_025_reload_js_type_error { 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", @@ -180,23 +174,6 @@ itest!(_028_args { 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_in_config_file { - args: "run --reload --config=import_maps/config.json import_maps/test.ts", - output: "run/033_import_map_in_config_file.out", -}); - -itest!(_033_import_map_in_flag_has_precedence { - args: "run --quiet --reload --import-map=import_maps/import_map_invalid.json --config=import_maps/config.json import_maps/test.ts", - output: "run/033_import_map_in_flag_has_precedence.out", - exit_code: 1, -}); - itest!(_033_import_map_remote { args: "run --quiet --reload --import-map=http://127.0.0.1:4545/import_maps/import_map_remote.json import_maps/test_remote.ts", @@ -766,12 +743,6 @@ itest!(_091_use_define_for_class_fields { 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", @@ -2233,11 +2204,6 @@ itest!(import_data_url_import_relative { 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", diff --git a/tests/specs/README.md b/tests/specs/README.md new file mode 100644 index 0000000000..bbd9b880c4 --- /dev/null +++ b/tests/specs/README.md @@ -0,0 +1,92 @@ +# specs + +These are integration tests that execute the `deno` binary. They supersede the +`itest` macro found in the `tests/integration` folder and are the preferred way +of writing tests that use the `deno` binary. + +## Structure + +Tests must have the following directory structure: + +``` +tests/specs///__test__.json +``` + +## Test filtering + +To run a specific test, run: + +``` +cargo test specs::category_name::test_name +``` + +Or just the following, though it might run other tests: + +``` +cargo test test_name +``` + +## `__test__.json` file + +This file describes the test to execute and the steps to execute. A basic +example looks like: + +```json +{ + "args": "run main.js", + "output": "main.out" +} +``` + +This will run `deno run main.js` then assert that the output matches the text in +`main.out`. + +Or another example that runs multiple steps: + +```json +{ + "tempDir": true, + "steps": [{ + "args": "cache main.ts", + "output": "cache.out" + }, { + "args": "run main.ts", + "output": "error.out", + "exitCode": 1 + }] +} +``` + +### Top level properties + +- `base` - The base config to use for the test. Options: + - `jsr` - Uses env vars for jsr. + - `npm` - Uses env vars for npm. +- `tempDir` (boolean) - Copy all the non-test files to a temporary directory and + execute the command in that temporary directory. + - By default, tests are executed with a current working directory of the test, + but this may not be desirable for tests such as ones that create a + node_modules directory. + +### Step properties + +When writing a single step, these may be at the top level rather than nested in +a "steps" array. + +- `args` - A string (that will be spilt on whitespace into an args array) or an + array of arguments. +- `output` - Path to use to assert the output. +- `clean` (boolean) - Whether to empty the deno_dir before running the step. +- `exitCode` (number) - Expected exit code. + +## `.out` files + +`.out` files are used to assert the output when running a test or test step. + +Within the file, you can use the following for matching: + +- `[WILDCARD]` - match any text at the wildcard +- `[WILDLINE]` - match any text on the current line +- `[WILDCHARS(5)]` - match any of the next 5 characters +- `[UNORDERED_START]` followed by many lines then `[UNORDERED_END]` will match + the lines in any order (useful for non-deterministic output) diff --git a/tests/specs/import_map/import_map_config/__test__.json b/tests/specs/import_map/import_map_config/__test__.json new file mode 100644 index 0000000000..b0463a2314 --- /dev/null +++ b/tests/specs/import_map/import_map_config/__test__.json @@ -0,0 +1,28 @@ +{ + "steps": [{ + "args": "run --quiet --reload --import-map=import_map.json test.ts", + "output": "run.out" + }, { + "args": "run --quiet --reload --import-map=import_map_invalid.json --config=config.json test.ts", + "output": "flag_has_precedence.out", + "exitCode": 1 + }, { + "args": "run --reload --config=config.json test.ts", + "output": "config.out" + }, { + "cleanDenoDir": true, + "args": "cache --quiet --reload --import-map=import_map.json test.ts", + "output": "cache.out" + }, { + "cleanDenoDir": true, + "args": "info --quiet --import-map=import_map.json test.ts", + "output": "info.out" + }, { + "args": "run --quiet --reload --import-map=import_map.json unmapped_bare_specifier.ts", + "output": "unmapped_bare_specifier.out", + "exitCode": 1 + }, { + "args": "run --quiet --reload --import-map import_map.json import_data_url.ts", + "output": "import_data_url.out" + }] +} diff --git a/tests/testdata/cache/036_import_map_fetch.out b/tests/specs/import_map/import_map_config/cache.out similarity index 100% rename from tests/testdata/cache/036_import_map_fetch.out rename to tests/specs/import_map/import_map_config/cache.out diff --git a/tests/testdata/import_maps/config.json b/tests/specs/import_map/import_map_config/config.json similarity index 100% rename from tests/testdata/import_maps/config.json rename to tests/specs/import_map/import_map_config/config.json diff --git a/tests/testdata/run/033_import_map_in_config_file.out b/tests/specs/import_map/import_map_config/config.out similarity index 100% rename from tests/testdata/run/033_import_map_in_config_file.out rename to tests/specs/import_map/import_map_config/config.out diff --git a/tests/testdata/run/033_import_map_in_flag_has_precedence.out b/tests/specs/import_map/import_map_config/flag_has_precedence.out similarity index 100% rename from tests/testdata/run/033_import_map_in_flag_has_precedence.out rename to tests/specs/import_map/import_map_config/flag_has_precedence.out diff --git a/tests/specs/import_map/import_map_config/import_data_url.out b/tests/specs/import_map/import_map_config/import_data_url.out new file mode 100644 index 0000000000..bfa0b9d948 --- /dev/null +++ b/tests/specs/import_map/import_map_config/import_data_url.out @@ -0,0 +1,3 @@ +a +{ "0": "A", "1": "B", "2": "C", A: 0, B: 1, C: 2 } +0 diff --git a/tests/specs/import_map/import_map_config/import_data_url.ts b/tests/specs/import_map/import_map_config/import_data_url.ts new file mode 100644 index 0000000000..258514a5e3 --- /dev/null +++ b/tests/specs/import_map/import_map_config/import_data_url.ts @@ -0,0 +1,12 @@ +// export const a = "a"; + +// export enum A { +// A, +// B, +// C, +// } +import * as a from "data:application/typescript;base64,ZXhwb3J0IGNvbnN0IGEgPSAiYSI7CgpleHBvcnQgZW51bSBBIHsKICBBLAogIEIsCiAgQywKfQo="; + +console.log(a.a); +console.log(a.A); +console.log(a.A.A); diff --git a/tests/testdata/import_maps/import_map.json b/tests/specs/import_map/import_map_config/import_map.json similarity index 88% rename from tests/testdata/import_maps/import_map.json rename to tests/specs/import_map/import_map_config/import_map.json index 40d1d4ec2f..601874aab9 100644 --- a/tests/testdata/import_maps/import_map.json +++ b/tests/specs/import_map/import_map_config/import_map.json @@ -1,6 +1,5 @@ { "imports": { - "print_hello": "./print_hello.ts", "moment": "./moment/moment.ts", "moment/": "./moment/", "lodash": "./lodash/lodash.ts", diff --git a/tests/testdata/import_maps/import_map_invalid.json b/tests/specs/import_map/import_map_config/import_map_invalid.json similarity index 100% rename from tests/testdata/import_maps/import_map_invalid.json rename to tests/specs/import_map/import_map_config/import_map_invalid.json diff --git a/tests/testdata/info/065_import_map_info.out b/tests/specs/import_map/import_map_config/info.out similarity index 100% rename from tests/testdata/info/065_import_map_info.out rename to tests/specs/import_map/import_map_config/info.out diff --git a/tests/specs/import_map/import_map_config/lodash/lodash.ts b/tests/specs/import_map/import_map_config/lodash/lodash.ts new file mode 100644 index 0000000000..2ec04ed3cf --- /dev/null +++ b/tests/specs/import_map/import_map_config/lodash/lodash.ts @@ -0,0 +1 @@ +console.log("Hello from remapped lodash!"); diff --git a/tests/specs/import_map/import_map_config/lodash/other_file.ts b/tests/specs/import_map/import_map_config/lodash/other_file.ts new file mode 100644 index 0000000000..714adae3fb --- /dev/null +++ b/tests/specs/import_map/import_map_config/lodash/other_file.ts @@ -0,0 +1 @@ +console.log("Hello from remapped lodash dir!"); diff --git a/tests/specs/import_map/import_map_config/moment/moment.ts b/tests/specs/import_map/import_map_config/moment/moment.ts new file mode 100644 index 0000000000..2b54a431e7 --- /dev/null +++ b/tests/specs/import_map/import_map_config/moment/moment.ts @@ -0,0 +1 @@ +console.log("Hello from remapped moment!"); diff --git a/tests/specs/import_map/import_map_config/moment/other_file.ts b/tests/specs/import_map/import_map_config/moment/other_file.ts new file mode 100644 index 0000000000..24f3a0226d --- /dev/null +++ b/tests/specs/import_map/import_map_config/moment/other_file.ts @@ -0,0 +1 @@ +console.log("Hello from remapped moment dir!"); diff --git a/tests/testdata/run/033_import_map.out b/tests/specs/import_map/import_map_config/run.out similarity index 100% rename from tests/testdata/run/033_import_map.out rename to tests/specs/import_map/import_map_config/run.out diff --git a/tests/testdata/import_maps/scope/scoped.ts b/tests/specs/import_map/import_map_config/scope/scoped.ts similarity index 100% rename from tests/testdata/import_maps/scope/scoped.ts rename to tests/specs/import_map/import_map_config/scope/scoped.ts diff --git a/tests/testdata/import_maps/scoped_moment.ts b/tests/specs/import_map/import_map_config/scoped_moment.ts similarity index 100% rename from tests/testdata/import_maps/scoped_moment.ts rename to tests/specs/import_map/import_map_config/scoped_moment.ts diff --git a/tests/specs/import_map/import_map_config/test.ts b/tests/specs/import_map/import_map_config/test.ts new file mode 100644 index 0000000000..9b09e9953d --- /dev/null +++ b/tests/specs/import_map/import_map_config/test.ts @@ -0,0 +1,6 @@ +import "moment"; +import "moment/other_file.ts"; +import "lodash"; +import "lodash/other_file.ts"; +import "https://www.unpkg.com/vue/dist/vue.runtime.esm.js"; +import "./scope/scoped.ts"; diff --git a/tests/specs/import_map/import_map_config/unmapped_bare_specifier.out b/tests/specs/import_map/import_map_config/unmapped_bare_specifier.out new file mode 100644 index 0000000000..6980fc16bb --- /dev/null +++ b/tests/specs/import_map/import_map_config/unmapped_bare_specifier.out @@ -0,0 +1,6 @@ +error: Uncaught (in promise) TypeError: Relative import path "unmapped" not prefixed with / or ./ or ../ and not in import map from "file://[WILDCARD]/unmapped_bare_specifier.ts" + at file://[WILDCARD]/unmapped_bare_specifier.ts:1:14 + +await import("unmapped"); +^ + at async file://[WILDCARD]/unmapped_bare_specifier.ts:1:1 diff --git a/tests/testdata/run/092_import_map_unmapped_bare_specifier.ts b/tests/specs/import_map/import_map_config/unmapped_bare_specifier.ts similarity index 100% rename from tests/testdata/run/092_import_map_unmapped_bare_specifier.ts rename to tests/specs/import_map/import_map_config/unmapped_bare_specifier.ts diff --git a/tests/specs/import_map/import_map_config/vue.ts b/tests/specs/import_map/import_map_config/vue.ts new file mode 100644 index 0000000000..76dbe19179 --- /dev/null +++ b/tests/specs/import_map/import_map_config/vue.ts @@ -0,0 +1 @@ +console.log("Hello from remapped Vue!"); diff --git a/tests/specs/info/ts_error/__test__.json b/tests/specs/info/ts_error/__test__.json new file mode 100644 index 0000000000..9ec9b60442 --- /dev/null +++ b/tests/specs/info/ts_error/__test__.json @@ -0,0 +1,4 @@ +{ + "args": "info info_ts_error.ts", + "output": "info_ts_error.out" +} diff --git a/tests/specs/info/ts_error/info_ts_error.out b/tests/specs/info/ts_error/info_ts_error.out new file mode 100644 index 0000000000..d2e7aeb146 --- /dev/null +++ b/tests/specs/info/ts_error/info_ts_error.out @@ -0,0 +1,6 @@ +local: [WILDCARD]info_ts_error.ts +type: TypeScript +dependencies: 0 unique +size: [WILDCARD] + +[WILDCARD]info_ts_error.ts ([WILDCARD]) diff --git a/tests/testdata/info/031_info_ts_error.ts b/tests/specs/info/ts_error/info_ts_error.ts similarity index 100% rename from tests/testdata/info/031_info_ts_error.ts rename to tests/specs/info/ts_error/info_ts_error.ts diff --git a/tests/specs/mod.rs b/tests/specs/mod.rs new file mode 100644 index 0000000000..831b944306 --- /dev/null +++ b/tests/specs/mod.rs @@ -0,0 +1,279 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use std::collections::HashSet; + +use deno_core::anyhow::Context; +use deno_core::serde_json; +use deno_terminal::colors; +use serde::Deserialize; +use test_util::tests_path; +use test_util::PathRef; +use test_util::TestContextBuilder; + +pub fn main() { + let maybe_filter = parse_cli_arg_filter(); + let categories = filter(collect_tests(), maybe_filter.as_deref()); + let total_tests = categories.iter().map(|c| c.tests.len()).sum::(); + let mut failures = Vec::new(); + let _http_guard = test_util::http_server(); + // todo(dsherret): the output should be changed to be terse + // when it passes, but verbose on failure + for category in &categories { + eprintln!(); + eprintln!(" {} {}", colors::green_bold("Running"), category.name); + for test in &category.tests { + eprintln!(); + eprintln!("==== Starting {} ====", test.name); + let result = std::panic::catch_unwind(|| run_test(test)); + let success = result.is_ok(); + if !success { + failures.push(&test.name); + } + eprintln!( + "==== {} {} ====", + if success { + "Finished".to_string() + } else { + colors::red("^^FAILED^^").to_string() + }, + test.name + ); + } + } + + eprintln!(); + if !failures.is_empty() { + eprintln!("spec failures:"); + for failure in &failures { + eprintln!(" {}", failure); + } + eprintln!(); + panic!("{} failed of {}", failures.len(), total_tests); + } + eprintln!("{} tests passed", total_tests); +} + +fn parse_cli_arg_filter() -> Option { + let args: Vec = std::env::args().collect(); + let maybe_filter = + args.get(1).filter(|s| !s.starts_with('-') && !s.is_empty()); + maybe_filter.cloned() +} + +fn run_test(test: &Test) { + let metadata = &test.metadata; + let mut builder = TestContextBuilder::new(); + let cwd = &test.cwd; + + if test.metadata.temp_dir { + builder = builder.use_temp_cwd(); + } else { + builder = builder.cwd(cwd.to_string_lossy()); + } + + if let Some(base) = &metadata.base { + match base.as_str() { + "npm" => { + builder = builder.add_npm_env_vars(); + } + "jsr" => { + builder = builder.add_jsr_env_vars(); + } + _ => panic!("Unknown test base: {}", base), + } + } + + let context = builder.build(); + + if test.metadata.temp_dir { + // copy all the files in the cwd to a temp directory + // excluding the metadata and assertion files + let temp_dir = context.temp_dir().path(); + let assertion_paths = test.resolve_test_and_assertion_files(); + cwd.copy_to_recursive_with_exclusions(temp_dir, &assertion_paths); + } + + for step in &metadata.steps { + if step.clean_deno_dir { + context.deno_dir().path().remove_dir_all(); + } + + let test_output_path = cwd.join(&step.output); + if !test_output_path.to_string_lossy().ends_with(".out") { + panic!( + "Use the .out extension for output files (invalid: {})", + test_output_path + ); + } + let expected_output = test_output_path.read_to_string(); + let command = context.new_command(); + let command = match &step.args { + VecOrString::Vec(args) => command.args_vec(args), + VecOrString::String(text) => command.args(text), + }; + let output = command.run(); + output.assert_matches_text(expected_output); + output.assert_exit_code(step.exit_code); + } +} + +#[derive(Clone, Deserialize)] +#[serde(untagged)] +enum VecOrString { + Vec(Vec), + String(String), +} + +#[derive(Clone, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +struct MultiTestMetaData { + /// Whether to copy all the non-assertion files in the current + /// test directory to a temporary directory before running the + /// steps. + #[serde(default)] + pub temp_dir: bool, + /// The base environment to use for the test. + #[serde(default)] + pub base: Option, + pub steps: Vec, +} + +#[derive(Clone, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +struct SingleTestMetaData { + #[serde(default)] + pub base: Option, + #[serde(default)] + pub temp_dir: bool, + #[serde(flatten)] + pub step: StepMetaData, +} + +impl SingleTestMetaData { + pub fn into_multi(self) -> MultiTestMetaData { + MultiTestMetaData { + base: self.base, + temp_dir: self.temp_dir, + steps: vec![self.step], + } + } +} + +#[derive(Clone, Deserialize)] +#[serde(deny_unknown_fields, rename_all = "camelCase")] +struct StepMetaData { + /// Whether to clean the deno_dir before running the step. + #[serde(default)] + pub clean_deno_dir: bool, + pub args: VecOrString, + pub output: String, + #[serde(default)] + pub exit_code: i32, +} + +#[derive(Clone)] +struct Test { + pub name: String, + pub cwd: PathRef, + pub metadata: MultiTestMetaData, +} + +impl Test { + pub fn resolve_test_and_assertion_files(&self) -> HashSet { + let mut result = HashSet::with_capacity(self.metadata.steps.len() + 1); + result.insert(self.cwd.join("__test__.json")); + result.extend( + self + .metadata + .steps + .iter() + .map(|step| self.cwd.join(&step.output)), + ); + result + } +} + +struct TestCategory { + pub name: String, + pub tests: Vec, +} + +fn filter( + categories: Vec, + maybe_filter: Option<&str>, +) -> Vec { + if categories.iter().all(|c| c.tests.is_empty()) { + panic!("no tests found"); + } + match maybe_filter { + Some(filter) => categories + .into_iter() + .map(|mut c| { + c.tests.retain(|t| t.name.contains(filter)); + c + }) + .collect(), + None => categories, + } +} + +fn collect_tests() -> Vec { + let specs_dir = tests_path().join("specs"); + let mut result = Vec::new(); + for entry in specs_dir.read_dir() { + let entry = entry.unwrap(); + let file_type = entry + .file_type() + .context(entry.path().to_string_lossy().to_string()) + .unwrap(); + if !file_type.is_dir() { + continue; + } + + let mut category = TestCategory { + name: format!("specs::{}", entry.file_name().to_string_lossy()), + tests: Vec::new(), + }; + + let category_path = PathRef::new(entry.path()); + for entry in category_path.read_dir() { + let entry = entry.unwrap(); + let file_type = entry + .file_type() + .context(entry.path().to_string_lossy().to_string()) + .unwrap(); + if !file_type.is_dir() { + continue; + } + + let test_dir = PathRef::new(entry.path()); + let metadata_path = test_dir.join("__test__.json"); + let metadata_value = metadata_path.read_jsonc_value(); + // checking for "steps" leads to a more targeted error message + // instead of when deserializing an untagged enum + let metadata = if metadata_value + .as_object() + .and_then(|o| o.get("steps")) + .is_some() + { + serde_json::from_value::(metadata_value) + } else { + serde_json::from_value::(metadata_value) + .map(|s| s.into_multi()) + } + .with_context(|| format!("Failed to parse {}", metadata_path)) + .unwrap(); + category.tests.push(Test { + name: format!( + "{}::{}", + category.name, + entry.file_name().to_string_lossy() + ), + cwd: test_dir, + metadata, + }); + } + result.push(category); + } + result +} diff --git a/tests/specs/npm/conditional_exports/__test__.json b/tests/specs/npm/conditional_exports/__test__.json new file mode 100644 index 0000000000..72b058cbfb --- /dev/null +++ b/tests/specs/npm/conditional_exports/__test__.json @@ -0,0 +1,5 @@ +{ + "base": "npm", + "args": "run --allow-read main.js", + "output": "main.out" +} diff --git a/tests/testdata/npm/conditional_exports/main.js b/tests/specs/npm/conditional_exports/main.js similarity index 100% rename from tests/testdata/npm/conditional_exports/main.js rename to tests/specs/npm/conditional_exports/main.js diff --git a/tests/testdata/npm/conditional_exports/main.out b/tests/specs/npm/conditional_exports/main.out similarity index 100% rename from tests/testdata/npm/conditional_exports/main.out rename to tests/specs/npm/conditional_exports/main.out diff --git a/tests/specs/npm/conditional_exports_node_modules_dir/__test__.json b/tests/specs/npm/conditional_exports_node_modules_dir/__test__.json new file mode 100644 index 0000000000..09ea8b23b2 --- /dev/null +++ b/tests/specs/npm/conditional_exports_node_modules_dir/__test__.json @@ -0,0 +1,6 @@ +{ + "base": "npm", + "tempDir": true, + "args": "run --allow-read --node-modules-dir main.js", + "output": "main.out" +} diff --git a/tests/specs/npm/conditional_exports_node_modules_dir/main.js b/tests/specs/npm/conditional_exports_node_modules_dir/main.js new file mode 100644 index 0000000000..52b78bc22d --- /dev/null +++ b/tests/specs/npm/conditional_exports_node_modules_dir/main.js @@ -0,0 +1,15 @@ +import mod from "npm:@denotest/conditional-exports"; +import foo from "npm:@denotest/conditional-exports/foo.js"; +import client from "npm:@denotest/conditional-exports/client"; +import clientFoo from "npm:@denotest/conditional-exports/client/foo"; +import clientBar from "npm:@denotest/conditional-exports/client/bar"; +import clientM from "npm:@denotest/conditional-exports/client/m"; +import supportsESM from "npm:supports-esm"; + +console.log(mod); +console.log(foo); +console.log(client); +console.log(clientFoo); +console.log(clientBar); +console.log(clientM); +console.log(supportsESM); diff --git a/tests/testdata/npm/conditional_exports/main_node_modules.out b/tests/specs/npm/conditional_exports_node_modules_dir/main.out similarity index 100% rename from tests/testdata/npm/conditional_exports/main_node_modules.out rename to tests/specs/npm/conditional_exports_node_modules_dir/main.out diff --git a/tests/specs/npm/es_module/__test__.json b/tests/specs/npm/es_module/__test__.json new file mode 100644 index 0000000000..f720a2bc08 --- /dev/null +++ b/tests/specs/npm/es_module/__test__.json @@ -0,0 +1,22 @@ +{ + "base": "npm", + "steps": [{ + "args": "run --allow-read --allow-env main.js", + "output": "main.out" + }, { + "cleanDenoDir": true, + "args": "test --allow-read --allow-env test.js", + "output": "test.out" + }, { + "cleanDenoDir": true, + "args": [ + "eval", + "import chalk from 'npm:chalk@5'; console.log(chalk.green('chalk esm loads'));" + ], + "output": "main.out" + }, { + "args": "bundle --quiet main.js", + "output": "bundle.out", + "exitCode": 1 + }] +} diff --git a/tests/specs/npm/es_module/bundle.out b/tests/specs/npm/es_module/bundle.out new file mode 100644 index 0000000000..c749a236a6 --- /dev/null +++ b/tests/specs/npm/es_module/bundle.out @@ -0,0 +1 @@ +error: npm specifiers have not yet been implemented for this subcommand (https://github.com/denoland/deno/issues/15960). Found: npm:/chalk@5.0.1 diff --git a/tests/testdata/npm/esm/main.js b/tests/specs/npm/es_module/main.js similarity index 100% rename from tests/testdata/npm/esm/main.js rename to tests/specs/npm/es_module/main.js diff --git a/tests/testdata/npm/esm/main.out b/tests/specs/npm/es_module/main.out similarity index 100% rename from tests/testdata/npm/esm/main.out rename to tests/specs/npm/es_module/main.out diff --git a/tests/testdata/npm/esm/test.js b/tests/specs/npm/es_module/test.js similarity index 100% rename from tests/testdata/npm/esm/test.js rename to tests/specs/npm/es_module/test.js diff --git a/tests/testdata/npm/esm/test.out b/tests/specs/npm/es_module/test.out similarity index 86% rename from tests/testdata/npm/esm/test.out rename to tests/specs/npm/es_module/test.out index a879244248..0ed0fbd3c6 100644 --- a/tests/testdata/npm/esm/test.out +++ b/tests/specs/npm/es_module/test.out @@ -1,6 +1,6 @@ Download http://localhost:4545/npm/registry/chalk Download http://localhost:4545/npm/registry/chalk/chalk-5.0.1.tgz -running 1 test from ./npm/esm/test.js +running 1 test from ./test.js test ... ------- output ------- test diff --git a/tests/specs/run/redirect_javascript/__test__.json b/tests/specs/run/redirect_javascript/__test__.json new file mode 100644 index 0000000000..1fd0b4cb12 --- /dev/null +++ b/tests/specs/run/redirect_javascript/__test__.json @@ -0,0 +1,4 @@ +{ + "args": "run --quiet --reload main.js", + "output": "main.out" +} diff --git a/tests/testdata/run/026_redirect_javascript.js b/tests/specs/run/redirect_javascript/main.js similarity index 100% rename from tests/testdata/run/026_redirect_javascript.js rename to tests/specs/run/redirect_javascript/main.js diff --git a/tests/testdata/run/026_redirect_javascript.js.out b/tests/specs/run/redirect_javascript/main.out similarity index 100% rename from tests/testdata/run/026_redirect_javascript.js.out rename to tests/specs/run/redirect_javascript/main.out diff --git a/tests/testdata/import_maps/import_map_remote.json b/tests/testdata/import_maps/import_map_remote.json index 51f90f69c6..190fc4f55a 100644 --- a/tests/testdata/import_maps/import_map_remote.json +++ b/tests/testdata/import_maps/import_map_remote.json @@ -4,6 +4,7 @@ "moment/": "./moment/", "lodash": "./lodash/lodash.ts", "lodash/": "./lodash/", - "https://www.unpkg.com/vue/dist/vue.runtime.esm.js": "./vue.ts" + "https://www.unpkg.com/vue/dist/vue.runtime.esm.js": "./vue.ts", + "print_hello": "./print_hello.ts" } } diff --git a/tests/testdata/info/031_info_ts_error.out b/tests/testdata/info/031_info_ts_error.out deleted file mode 100644 index 81edd00322..0000000000 --- a/tests/testdata/info/031_info_ts_error.out +++ /dev/null @@ -1,6 +0,0 @@ -local: [WILDCARD]031_info_ts_error.ts -type: TypeScript -dependencies: 0 unique -size: [WILDCARD] - -[WILDCARD]031_info_ts_error.ts ([WILDCARD]) diff --git a/tests/testdata/run/092_import_map_unmapped_bare_specifier.ts.out b/tests/testdata/run/092_import_map_unmapped_bare_specifier.ts.out deleted file mode 100644 index 7f35b8b4f1..0000000000 --- a/tests/testdata/run/092_import_map_unmapped_bare_specifier.ts.out +++ /dev/null @@ -1,6 +0,0 @@ -error: Uncaught (in promise) TypeError: Relative import path "unmapped" not prefixed with / or ./ or ../ and not in import map from "file://[WILDCARD]/092_import_map_unmapped_bare_specifier.ts" - at file://[WILDCARD]/092_import_map_unmapped_bare_specifier.ts:1:14 - -await import("unmapped"); -^ - at async file://[WILDCARD]/092_import_map_unmapped_bare_specifier.ts:1:1 diff --git a/tests/util/server/Cargo.toml b/tests/util/server/Cargo.toml index fbec0e1aad..15a182a783 100644 --- a/tests/util/server/Cargo.toml +++ b/tests/util/server/Cargo.toml @@ -30,6 +30,7 @@ http.workspace = true http-body-util.workspace = true hyper.workspace = true hyper-util.workspace = true +jsonc-parser.workspace = true lazy-regex.workspace = true libc.workspace = true lsp-types.workspace = true diff --git a/tests/util/server/src/fs.rs b/tests/util/server/src/fs.rs index d99572b06f..fb3e36ab60 100644 --- a/tests/util/server/src/fs.rs +++ b/tests/util/server/src/fs.rs @@ -2,6 +2,7 @@ use pretty_assertions::assert_eq; use std::borrow::Cow; +use std::collections::HashSet; use std::ffi::OsStr; use std::fs; use std::fs::OpenOptions; @@ -21,7 +22,7 @@ use crate::testdata_path; /// Represents a path on the file system, which can be used /// to perform specific actions. -#[derive(Clone, Debug, Default, PartialEq, Eq)] +#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)] pub struct PathRef(PathBuf); impl AsRef for PathRef { @@ -124,12 +125,29 @@ impl PathRef { fs::read(self).with_context(|| format!("Could not read file: {}", self)) } + #[track_caller] pub fn read_json(&self) -> TValue { - serde_json::from_str(&self.read_to_string()).unwrap() + serde_json::from_str(&self.read_to_string()) + .with_context(|| format!("Failed deserializing: {}", self)) + .unwrap() } + #[track_caller] pub fn read_json_value(&self) -> serde_json::Value { - serde_json::from_str(&self.read_to_string()).unwrap() + serde_json::from_str(&self.read_to_string()) + .with_context(|| format!("Failed deserializing: {}", self)) + .unwrap() + } + + #[track_caller] + pub fn read_jsonc_value(&self) -> serde_json::Value { + jsonc_parser::parse_to_serde_value( + &self.read_to_string(), + &Default::default(), + ) + .with_context(|| format!("Failed to parse {}", self)) + .unwrap() + .expect("Found no value.") } pub fn rename(&self, to: impl AsRef) { @@ -204,6 +222,14 @@ impl PathRef { /// /// Note: Does not handle symlinks. pub fn copy_to_recursive(&self, to: &PathRef) { + self.copy_to_recursive_with_exclusions(to, &HashSet::new()) + } + + pub fn copy_to_recursive_with_exclusions( + &self, + to: &PathRef, + file_exclusions: &HashSet, + ) { to.create_dir_all(); let read_dir = self.read_dir(); @@ -215,7 +241,7 @@ impl PathRef { if file_type.is_dir() { new_from.copy_to_recursive(&new_to); - } else if file_type.is_file() { + } else if file_type.is_file() && !file_exclusions.contains(&new_from) { new_from.copy(&new_to); } } diff --git a/tools/copyright_checker.js b/tools/copyright_checker.js index 7337c0d841..86289b9d45 100644 --- a/tools/copyright_checker.js +++ b/tools/copyright_checker.js @@ -24,6 +24,7 @@ export async function checkCopyright() { "*.js", "*.ts", ":!:.github/mtime_cache/action.js", + ":!:tests/specs/**", ":!:tests/testdata/**", ":!:cli/bench/testdata/**", ":!:cli/tsc/dts/**",