From 54890ee98b9068af41214b86fb693135f0998a0a Mon Sep 17 00:00:00 2001 From: David Sherret Date: Thu, 14 Sep 2023 12:21:57 -0400 Subject: [PATCH] chore(tests): ability to pattern match unordered lines (#20488) This adds the ability to pattern match unordered lines. For example, the downloading messages may appear in any order ``` [UNORDERED_START] Download https://localhost:4546/a.ts Download https://localhost:4546/b.ts [UNORDERED_END] Hello! ``` Additionally, I've made the pattern matching slightly more strict and the output better. --- Cargo.lock | 2 + Cargo.toml | 1 + cli/Cargo.toml | 2 +- cli/tests/integration/run_tests.rs | 4 +- .../error_cause_recursive_aggregate.ts.out | 1 - .../error_cause_recursive_tail.ts.out | 2 - cli/tests/testdata/eval/dyn_import_eval.out | 1 - .../import_attributes/dynamic_error.out | 1 - .../import_attributes/static_error.out | 1 - .../import_attributes/static_export.out | 1 - .../import_attributes/static_import.out | 1 - cli/tests/testdata/info/031_info_ts_error.out | 1 - .../testdata/info/065_import_map_info.out | 1 - .../testdata/run/003_relative_import.ts.out | 1 - .../testdata/run/020_json_modules.ts.out | 1 - cli/tests/testdata/run/070_location.ts.out | 3 +- .../testdata/run/071_location_unset.ts.out | 3 +- .../run/079_location_authentication.ts.out | 2 - ..._import_map_unmapped_bare_specifier.ts.out | 1 - .../run/error_026_remote_import_error.ts.out | 4 +- cli/tests/testdata/run/error_cause.ts.out | 2 - .../testdata/run/error_cause_recursive.ts.out | 1 - .../error_missing_module_named_import.ts.out | 1 - .../testdata/run/fix_emittable_skipped.ts.out | 1 - .../run/import_blob_url_error_stack.ts.out | 3 +- .../run/import_data_url_error_stack.ts.out | 3 +- .../testdata/run/import_extensionless.ts.out | 1 - cli/tests/testdata/run/import_type.ts.out | 1 - cli/tests/testdata/run/issue13562.ts.out | 1 - cli/tests/testdata/run/mts_dmts_mjs.out | 1 - .../run/private_field_presence.ts.out | 1 - .../testdata/run/unbuffered_stderr.ts.out | 1 - .../run/webstorage/serialization.ts.out | 1 - .../task/deno_json/task_deno_exe_no_env.out | 1 - .../task/deno_json/task_piped_stdin.out | 1 - .../testdata/test/steps/failing_steps.dot.out | 1 - .../testdata/test/steps/ignored_steps.dot.out | 1 - .../testdata/workers/custom_inspect/main.out | 3 +- .../error_worker_permissions_local.ts.out | 1 - .../error_worker_permissions_remote.ts.out | 1 - test_util/Cargo.toml | 2 + test_util/src/assertions.rs | 41 +- test_util/src/lib.rs | 409 +++++++++++++++--- 43 files changed, 405 insertions(+), 108 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 42b608bb0b..1b56cd78f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5455,6 +5455,7 @@ dependencies = [ "lazy-regex", "libc", "lsp-types", + "monch", "nix 0.26.2", "once_cell", "os_pipe", @@ -5471,6 +5472,7 @@ dependencies = [ "serde_json", "tar", "tempfile", + "termcolor", "tokio", "tokio-rustls", "url", diff --git a/Cargo.toml b/Cargo.toml index 707a974ba9..402b6e5073 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -105,6 +105,7 @@ libc = "0.2.126" log = "=0.4.20" lsp-types = "=0.93.2" # used by tower-lsp and "proposed" feature is unstable in patch releases memmem = "0.1.1" +monch = "=0.4.3" notify = "=5.0.0" num-bigint = { version = "0.4", features = ["rand"] } once_cell = "1.17.1" diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 42f868ef96..8906488e9f 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -93,7 +93,7 @@ lazy-regex.workspace = true libc.workspace = true log = { workspace = true, features = ["serde"] } lsp-types.workspace = true -monch = "=0.4.3" +monch.workspace = true notify.workspace = true once_cell.workspace = true os_pipe.workspace = true diff --git a/cli/tests/integration/run_tests.rs b/cli/tests/integration/run_tests.rs index 0edfe1c488..be9bc554ac 100644 --- a/cli/tests/integration/run_tests.rs +++ b/cli/tests/integration/run_tests.rs @@ -870,7 +870,7 @@ itest!(lock_write_fetch { itest!(lock_check_ok { args: - "run --lock=run/lock_check_ok.json http://127.0.0.1:4545/run/003_relative_import.ts", + "run --quiet --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, }); @@ -917,7 +917,7 @@ itest!(lock_flag_overrides_config_file_lock_path { 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", + "run --quiet --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, }); diff --git a/cli/tests/testdata/error_cause_recursive_aggregate.ts.out b/cli/tests/testdata/error_cause_recursive_aggregate.ts.out index bf1e45e51f..652403e4a4 100644 --- a/cli/tests/testdata/error_cause_recursive_aggregate.ts.out +++ b/cli/tests/testdata/error_cause_recursive_aggregate.ts.out @@ -1,4 +1,3 @@ -[WILDCARD] error: Uncaught AggregateError Error: bar at file:///[WILDCARD]/error_cause_recursive_aggregate.ts:2:13 diff --git a/cli/tests/testdata/error_cause_recursive_tail.ts.out b/cli/tests/testdata/error_cause_recursive_tail.ts.out index 48b65d3f40..e19fa39bc4 100644 --- a/cli/tests/testdata/error_cause_recursive_tail.ts.out +++ b/cli/tests/testdata/error_cause_recursive_tail.ts.out @@ -1,4 +1,3 @@ -[WILDCARD] error: Uncaught Error: baz const baz = new Error("baz", { cause: bar }); ^ @@ -8,4 +7,3 @@ Caused by: Error: bar Caused by: Error: foo at file:///[WILDCARD]/error_cause_recursive_tail.ts:1:13 Caused by: [Circular *1] -[WILDCARD] diff --git a/cli/tests/testdata/eval/dyn_import_eval.out b/cli/tests/testdata/eval/dyn_import_eval.out index bbc53b558f..89e16b4781 100644 --- a/cli/tests/testdata/eval/dyn_import_eval.out +++ b/cli/tests/testdata/eval/dyn_import_eval.out @@ -1,2 +1 @@ -[WILDCARD] [Module: null prototype] { isMod4: true } diff --git a/cli/tests/testdata/import_attributes/dynamic_error.out b/cli/tests/testdata/import_attributes/dynamic_error.out index 927eae0b87..d6e0c9115a 100644 --- a/cli/tests/testdata/import_attributes/dynamic_error.out +++ b/cli/tests/testdata/import_attributes/dynamic_error.out @@ -1,4 +1,3 @@ -[WILDCARD] error: Uncaught (in promise) TypeError: Expected a "JavaScriptOrWasm" module but loaded a "JSON" module. const data = await import("./data.json"); ^ diff --git a/cli/tests/testdata/import_attributes/static_error.out b/cli/tests/testdata/import_attributes/static_error.out index 171ae0c1a2..29b24b965a 100644 --- a/cli/tests/testdata/import_attributes/static_error.out +++ b/cli/tests/testdata/import_attributes/static_error.out @@ -1,4 +1,3 @@ -[WILDCARD] error: Expected a JavaScript or TypeScript module, but identified a Json module. Consider importing Json modules with an import attribute with the type of "json". Specifier: [WILDCARD]/data.json at [WILDCARD]static_error.ts:1:18 diff --git a/cli/tests/testdata/import_attributes/static_export.out b/cli/tests/testdata/import_attributes/static_export.out index 42fbc066c2..41af79d7c8 100644 --- a/cli/tests/testdata/import_attributes/static_export.out +++ b/cli/tests/testdata/import_attributes/static_export.out @@ -1,2 +1 @@ -[WILDCARD] { a: "b", c: { d: 10 } } diff --git a/cli/tests/testdata/import_attributes/static_import.out b/cli/tests/testdata/import_attributes/static_import.out index b3b71cd8f8..e57dffa992 100644 --- a/cli/tests/testdata/import_attributes/static_import.out +++ b/cli/tests/testdata/import_attributes/static_import.out @@ -1,3 +1,2 @@ -[WILDCARD] { a: "b", c: { d: 10 } } { a: "b", c: { d: 10 } } diff --git a/cli/tests/testdata/info/031_info_ts_error.out b/cli/tests/testdata/info/031_info_ts_error.out index 49df500123..81edd00322 100644 --- a/cli/tests/testdata/info/031_info_ts_error.out +++ b/cli/tests/testdata/info/031_info_ts_error.out @@ -1,4 +1,3 @@ -[WILDCARD] local: [WILDCARD]031_info_ts_error.ts type: TypeScript dependencies: 0 unique diff --git a/cli/tests/testdata/info/065_import_map_info.out b/cli/tests/testdata/info/065_import_map_info.out index 657510b897..68d98f14a0 100644 --- a/cli/tests/testdata/info/065_import_map_info.out +++ b/cli/tests/testdata/info/065_import_map_info.out @@ -1,4 +1,3 @@ -[WILDCARD] local: [WILDCARD]test.ts type: TypeScript dependencies: 7 unique diff --git a/cli/tests/testdata/run/003_relative_import.ts.out b/cli/tests/testdata/run/003_relative_import.ts.out index 699b756edc..e965047ad7 100644 --- a/cli/tests/testdata/run/003_relative_import.ts.out +++ b/cli/tests/testdata/run/003_relative_import.ts.out @@ -1,2 +1 @@ -[WILDCARD] Hello diff --git a/cli/tests/testdata/run/020_json_modules.ts.out b/cli/tests/testdata/run/020_json_modules.ts.out index 750363da2c..948901724a 100644 --- a/cli/tests/testdata/run/020_json_modules.ts.out +++ b/cli/tests/testdata/run/020_json_modules.ts.out @@ -1,4 +1,3 @@ -[WILDCARD] error: Expected a JavaScript or TypeScript module, but identified a Json module. Consider importing Json modules with an import attribute with the type of "json". Specifier: [WILDCARD]/subdir/config.json [WILDCARD] \ No newline at end of file diff --git a/cli/tests/testdata/run/070_location.ts.out b/cli/tests/testdata/run/070_location.ts.out index 6827a555d4..a03cf6477c 100644 --- a/cli/tests/testdata/run/070_location.ts.out +++ b/cli/tests/testdata/run/070_location.ts.out @@ -1,4 +1,4 @@ -[WILDCARD][class Location] +[class Location] Object [Location] {} Location { hash: "#bat", @@ -13,4 +13,3 @@ Location { } NotSupportedError: Cannot set "location". NotSupportedError: Cannot set "location.hostname". -[WILDCARD] diff --git a/cli/tests/testdata/run/071_location_unset.ts.out b/cli/tests/testdata/run/071_location_unset.ts.out index cf4a9d6059..c9482011f5 100644 --- a/cli/tests/testdata/run/071_location_unset.ts.out +++ b/cli/tests/testdata/run/071_location_unset.ts.out @@ -1,5 +1,4 @@ -[WILDCARD][class Location] +[class Location] Object [Location] {} undefined /bar -[WILDCARD] diff --git a/cli/tests/testdata/run/079_location_authentication.ts.out b/cli/tests/testdata/run/079_location_authentication.ts.out index bb24584971..0dd73f9996 100644 --- a/cli/tests/testdata/run/079_location_authentication.ts.out +++ b/cli/tests/testdata/run/079_location_authentication.ts.out @@ -1,3 +1 @@ -[WILDCARD] https://baz/qux -[WILDCARD] diff --git a/cli/tests/testdata/run/092_import_map_unmapped_bare_specifier.ts.out b/cli/tests/testdata/run/092_import_map_unmapped_bare_specifier.ts.out index 6e82ad4108..7f35b8b4f1 100644 --- a/cli/tests/testdata/run/092_import_map_unmapped_bare_specifier.ts.out +++ b/cli/tests/testdata/run/092_import_map_unmapped_bare_specifier.ts.out @@ -1,4 +1,3 @@ -[WILDCARD] 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 diff --git a/cli/tests/testdata/run/error_026_remote_import_error.ts.out b/cli/tests/testdata/run/error_026_remote_import_error.ts.out index 84f926f245..cc88284af5 100644 --- a/cli/tests/testdata/run/error_026_remote_import_error.ts.out +++ b/cli/tests/testdata/run/error_026_remote_import_error.ts.out @@ -1,7 +1,7 @@ -[WILDCARD]error: Uncaught Error: bad +[WILDCARD] +error: Uncaught Error: bad throw Error("bad"); ^ at foo (http://localhost:4545/run/error_001.ts:2:9) at bar (http://localhost:4545/run/error_001.ts:6:3) at http://localhost:4545/run/error_001.ts:9:1 -[WILDCARD] diff --git a/cli/tests/testdata/run/error_cause.ts.out b/cli/tests/testdata/run/error_cause.ts.out index 2aab020d97..6a8555646f 100644 --- a/cli/tests/testdata/run/error_cause.ts.out +++ b/cli/tests/testdata/run/error_cause.ts.out @@ -1,4 +1,3 @@ -[WILDCARD] error: Uncaught Error: foo throw new Error("foo", { cause: new Error("bar", { cause: "deno" as any }) }); ^ @@ -12,4 +11,3 @@ Caused by: Error: bar at c (file:///[WILDCARD]/error_cause.ts:11:3) at file:///[WILDCARD]/error_cause.ts:14:1 Caused by: "deno" -[WILDCARD] diff --git a/cli/tests/testdata/run/error_cause_recursive.ts.out b/cli/tests/testdata/run/error_cause_recursive.ts.out index 3ae440cf79..9c381a9744 100644 --- a/cli/tests/testdata/run/error_cause_recursive.ts.out +++ b/cli/tests/testdata/run/error_cause_recursive.ts.out @@ -1,4 +1,3 @@ -[WILDCARD] error: Uncaught Error: bar const y = new Error("bar", { cause: x }); ^ diff --git a/cli/tests/testdata/run/error_missing_module_named_import.ts.out b/cli/tests/testdata/run/error_missing_module_named_import.ts.out index 3dccaffb1c..700377d654 100644 --- a/cli/tests/testdata/run/error_missing_module_named_import.ts.out +++ b/cli/tests/testdata/run/error_missing_module_named_import.ts.out @@ -1,3 +1,2 @@ -[WILDCARD] error: Module not found "file://[WILDCARD]/does_not_exist.js". at file:///[WILDCARD]/error_missing_module_named_import.ts:[WILDCARD] diff --git a/cli/tests/testdata/run/fix_emittable_skipped.ts.out b/cli/tests/testdata/run/fix_emittable_skipped.ts.out index 8b31d3dbce..8657592993 100644 --- a/cli/tests/testdata/run/fix_emittable_skipped.ts.out +++ b/cli/tests/testdata/run/fix_emittable_skipped.ts.out @@ -1,2 +1 @@ -[WILDCARD] [Function (anonymous)] diff --git a/cli/tests/testdata/run/import_blob_url_error_stack.ts.out b/cli/tests/testdata/run/import_blob_url_error_stack.ts.out index 52b76fd5c2..201556b8ab 100644 --- a/cli/tests/testdata/run/import_blob_url_error_stack.ts.out +++ b/cli/tests/testdata/run/import_blob_url_error_stack.ts.out @@ -1,6 +1,5 @@ -[WILDCARD]error: Uncaught (in promise) Error: Hello 2 +error: Uncaught (in promise) Error: Hello 2 throw new Error(`Hello ${A.C}`); ^ at a (blob:null/[WILDCARD]:8:10) at file:///[WILDCARD]/import_blob_url_error_stack.ts:13:1 -[WILDCARD] diff --git a/cli/tests/testdata/run/import_data_url_error_stack.ts.out b/cli/tests/testdata/run/import_data_url_error_stack.ts.out index ccd72e4add..e79b52b30a 100644 --- a/cli/tests/testdata/run/import_data_url_error_stack.ts.out +++ b/cli/tests/testdata/run/import_data_url_error_stack.ts.out @@ -1,6 +1,5 @@ -[WILDCARD]error: Uncaught Error: Hello 2 +error: Uncaught Error: Hello 2 throw new Error(`Hello ${A.C}`); ^ at a (data:application/typescript;base64,ZW51bSBBIHsKICBBLAog......JHtBLkN9YCk7CiB9CiA=:8:10) at file:///[WILDCARD]/import_data_url_error_stack.ts:3:1 -[WILDCARD] diff --git a/cli/tests/testdata/run/import_extensionless.ts.out b/cli/tests/testdata/run/import_extensionless.ts.out index 699b756edc..e965047ad7 100644 --- a/cli/tests/testdata/run/import_extensionless.ts.out +++ b/cli/tests/testdata/run/import_extensionless.ts.out @@ -1,2 +1 @@ -[WILDCARD] Hello diff --git a/cli/tests/testdata/run/import_type.ts.out b/cli/tests/testdata/run/import_type.ts.out index 188c5e25db..e35539e352 100644 --- a/cli/tests/testdata/run/import_type.ts.out +++ b/cli/tests/testdata/run/import_type.ts.out @@ -1,2 +1 @@ -[WILDCARD] B { a: "a" } diff --git a/cli/tests/testdata/run/issue13562.ts.out b/cli/tests/testdata/run/issue13562.ts.out index 699b756edc..e965047ad7 100644 --- a/cli/tests/testdata/run/issue13562.ts.out +++ b/cli/tests/testdata/run/issue13562.ts.out @@ -1,2 +1 @@ -[WILDCARD] Hello diff --git a/cli/tests/testdata/run/mts_dmts_mjs.out b/cli/tests/testdata/run/mts_dmts_mjs.out index 5647bdfb99..7898192261 100644 --- a/cli/tests/testdata/run/mts_dmts_mjs.out +++ b/cli/tests/testdata/run/mts_dmts_mjs.out @@ -1,2 +1 @@ -[WILDCARD] a diff --git a/cli/tests/testdata/run/private_field_presence.ts.out b/cli/tests/testdata/run/private_field_presence.ts.out index f582fb47ad..1d474d5255 100644 --- a/cli/tests/testdata/run/private_field_presence.ts.out +++ b/cli/tests/testdata/run/private_field_presence.ts.out @@ -1,3 +1,2 @@ -[WILDCARD] false true diff --git a/cli/tests/testdata/run/unbuffered_stderr.ts.out b/cli/tests/testdata/run/unbuffered_stderr.ts.out index 5000197382..c1b0730e01 100644 --- a/cli/tests/testdata/run/unbuffered_stderr.ts.out +++ b/cli/tests/testdata/run/unbuffered_stderr.ts.out @@ -1,2 +1 @@ -[WILDCARD] x \ No newline at end of file diff --git a/cli/tests/testdata/run/webstorage/serialization.ts.out b/cli/tests/testdata/run/webstorage/serialization.ts.out index fea76aa43d..b5581828f2 100644 --- a/cli/tests/testdata/run/webstorage/serialization.ts.out +++ b/cli/tests/testdata/run/webstorage/serialization.ts.out @@ -1,3 +1,2 @@ -[WILDCARD] Storage {[WILDCARD] Storage { length: 1, hello: "deno" } diff --git a/cli/tests/testdata/task/deno_json/task_deno_exe_no_env.out b/cli/tests/testdata/task/deno_json/task_deno_exe_no_env.out index cf4a51b688..7ed6ff82de 100644 --- a/cli/tests/testdata/task/deno_json/task_deno_exe_no_env.out +++ b/cli/tests/testdata/task/deno_json/task_deno_exe_no_env.out @@ -1,2 +1 @@ -[WILDCARD] 5 diff --git a/cli/tests/testdata/task/deno_json/task_piped_stdin.out b/cli/tests/testdata/task/deno_json/task_piped_stdin.out index f0a236c867..a0d636f154 100644 --- a/cli/tests/testdata/task/deno_json/task_piped_stdin.out +++ b/cli/tests/testdata/task/deno_json/task_piped_stdin.out @@ -1,3 +1,2 @@ -[WILDCARD] Uint8Array(1) [ 49 ] Uint8Array(1) [ 50 ] diff --git a/cli/tests/testdata/test/steps/failing_steps.dot.out b/cli/tests/testdata/test/steps/failing_steps.dot.out index 3895a967ef..f8ba8d8e62 100644 --- a/cli/tests/testdata/test/steps/failing_steps.dot.out +++ b/cli/tests/testdata/test/steps/failing_steps.dot.out @@ -1,4 +1,3 @@ -[WILDCARD] ! . ! diff --git a/cli/tests/testdata/test/steps/ignored_steps.dot.out b/cli/tests/testdata/test/steps/ignored_steps.dot.out index e3d3865d48..442a06c62d 100644 --- a/cli/tests/testdata/test/steps/ignored_steps.dot.out +++ b/cli/tests/testdata/test/steps/ignored_steps.dot.out @@ -1,4 +1,3 @@ -[WILDCARD] , . . diff --git a/cli/tests/testdata/workers/custom_inspect/main.out b/cli/tests/testdata/workers/custom_inspect/main.out index b34300c402..40d9b88ad7 100644 --- a/cli/tests/testdata/workers/custom_inspect/main.out +++ b/cli/tests/testdata/workers/custom_inspect/main.out @@ -1,2 +1 @@ -[WILDCARD]ReadableStream { locked: false } -[WILDCARD] +ReadableStream { locked: false } diff --git a/cli/tests/testdata/workers/error_worker_permissions_local.ts.out b/cli/tests/testdata/workers/error_worker_permissions_local.ts.out index e6404e8e3f..cacc8ae385 100644 --- a/cli/tests/testdata/workers/error_worker_permissions_local.ts.out +++ b/cli/tests/testdata/workers/error_worker_permissions_local.ts.out @@ -1,3 +1,2 @@ -[WILDCARD] error: Uncaught (in worker "") Requires read access to "[WILDCARD]worker_types.ts", run again with the --allow-read flag [WILDCARD] diff --git a/cli/tests/testdata/workers/error_worker_permissions_remote.ts.out b/cli/tests/testdata/workers/error_worker_permissions_remote.ts.out index 74c7c3974e..afecff0a88 100644 --- a/cli/tests/testdata/workers/error_worker_permissions_remote.ts.out +++ b/cli/tests/testdata/workers/error_worker_permissions_remote.ts.out @@ -1,3 +1,2 @@ -[WILDCARD] error: Uncaught (in worker "") Requires net access to "localhost:4545", run again with the --allow-net flag [WILDCARD] diff --git a/test_util/Cargo.toml b/test_util/Cargo.toml index 6ea9d870dd..47f7d1d52f 100644 --- a/test_util/Cargo.toml +++ b/test_util/Cargo.toml @@ -26,6 +26,7 @@ hyper = { workspace = true, features = ["server", "http1", "http2", "runtime"] } lazy-regex.workspace = true libc.workspace = true lsp-types.workspace = true +monch.workspace = true nix.workspace = true once_cell.workspace = true os_pipe.workspace = true @@ -41,6 +42,7 @@ serde.workspace = true serde_json.workspace = true tar.workspace = true tempfile.workspace = true +termcolor.workspace = true tokio.workspace = true tokio-rustls.workspace = true url.workspace = true diff --git a/test_util/src/assertions.rs b/test_util/src/assertions.rs index 239e0e99c0..91ac9dd1c5 100644 --- a/test_util/src/assertions.rs +++ b/test_util/src/assertions.rs @@ -1,5 +1,7 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use crate::colors; + #[macro_export] macro_rules! assert_starts_with { ($string:expr, $($test:expr),+) => { @@ -52,11 +54,40 @@ macro_rules! assert_not_contains { #[track_caller] pub fn assert_wildcard_match(actual: &str, expected: &str) { - if !expected.contains("[WILDCARD]") { + if !expected.contains("[WILDCARD]") && !expected.contains("[IGNORE_START]") { pretty_assertions::assert_eq!(actual, expected); - } else if !crate::wildcard_match(expected, actual) { - println!("OUTPUT START\n{actual}\nOUTPUT END"); - println!("EXPECTED START\n{expected}\nEXPECTED END"); - panic!("pattern match failed"); + } else { + match crate::wildcard_match_detailed(expected, actual) { + crate::WildcardMatchResult::Success => { + // ignore + } + crate::WildcardMatchResult::Fail(debug_output) => { + println!( + "{}{}{}", + colors::bold("-- "), + colors::bold_red("OUTPUT"), + colors::bold(" START --"), + ); + println!("{}", actual); + println!("{}", colors::bold("-- OUTPUT END --")); + println!( + "{}{}{}", + colors::bold("-- "), + colors::bold_green("EXPECTED"), + colors::bold(" START --"), + ); + println!("{}", expected); + println!("{}", colors::bold("-- EXPECTED END --")); + println!( + "{}{}{}", + colors::bold("-- "), + colors::bold_blue("DEBUG"), + colors::bold(" START --"), + ); + println!("{debug_output}"); + println!("{}", colors::bold("-- DEBUG END --")); + panic!("pattern match failed"); + } + } } } diff --git a/test_util/src/lib.rs b/test_util/src/lib.rs index fa0cb8a848..0600867d74 100644 --- a/test_util/src/lib.rs +++ b/test_util/src/lib.rs @@ -2410,57 +2410,266 @@ impl<'a> CheckOutputIntegrationTest<'a> { } } -pub fn wildcard_match(pattern: &str, s: &str) -> bool { - pattern_match(pattern, s, "[WILDCARD]") +pub fn wildcard_match(pattern: &str, text: &str) -> bool { + match wildcard_match_detailed(pattern, text) { + WildcardMatchResult::Success => true, + WildcardMatchResult::Fail(debug_output) => { + eprintln!("{}", debug_output); + false + } + } } -pub fn pattern_match(pattern: &str, s: &str, wildcard: &str) -> bool { +pub enum WildcardMatchResult { + Success, + Fail(String), +} + +pub fn wildcard_match_detailed( + pattern: &str, + text: &str, +) -> WildcardMatchResult { + fn annotate_whitespace(text: &str) -> String { + text.replace('\t', "\u{2192}").replace(' ', "\u{00B7}") + } + // Normalize line endings - let mut s = s.replace("\r\n", "\n"); + let original_text = text.replace("\r\n", "\n"); + let mut current_text = original_text.as_str(); let pattern = pattern.replace("\r\n", "\n"); + let mut output_lines = Vec::new(); - if pattern == wildcard { - return true; - } - - let parts = pattern.split(wildcard).collect::>(); - if parts.len() == 1 { - return pattern == s; - } - - if !s.starts_with(parts[0]) { - return false; - } - - // If the first line of the pattern is just a wildcard the newline character - // needs to be pre-pended so it can safely match anything or nothing and - // continue matching. - if pattern.lines().next() == Some(wildcard) { - s.insert(0, '\n'); - } - - let mut t = s.split_at(parts[0].len()); + let parts = parse_wildcard_pattern_text(&pattern).unwrap(); + let mut was_last_wildcard = false; for (i, part) in parts.iter().enumerate() { - if i == 0 { - continue; + match part { + WildcardPatternPart::Wildcard => { + output_lines.push("".to_string()); + } + WildcardPatternPart::Text(search_text) => { + let is_last = i + 1 == parts.len(); + let search_index = if is_last && was_last_wildcard { + // search from the end of the file + current_text.rfind(search_text) + } else { + current_text.find(search_text) + }; + match search_index { + Some(found_index) if was_last_wildcard || found_index == 0 => { + output_lines.push(format!( + "{}", + colors::gray(annotate_whitespace(search_text)) + )); + current_text = ¤t_text[found_index + search_text.len()..]; + } + Some(index) => { + output_lines.push( + "==== FOUND SEARCH TEXT IN WRONG POSITION ====".to_string(), + ); + output_lines.push(colors::gray(annotate_whitespace(search_text))); + output_lines + .push("==== HAD UNKNOWN PRECEEDING TEXT ====".to_string()); + output_lines + .push(colors::red(annotate_whitespace(¤t_text[..index]))); + return WildcardMatchResult::Fail(output_lines.join("\n")); + } + None => { + let mut max_found_index = 0; + for (index, _) in search_text.char_indices() { + let sub_string = &search_text[..index]; + if let Some(found_index) = current_text.find(sub_string) { + if was_last_wildcard || found_index == 0 { + max_found_index = index; + } else { + break; + } + } else { + break; + } + } + if !was_last_wildcard && max_found_index > 0 { + output_lines.push(format!( + "{}", + colors::gray(annotate_whitespace( + &search_text[..max_found_index] + )) + )); + } + output_lines + .push("==== COULD NOT FIND SEARCH TEXT ====".to_string()); + output_lines.push(colors::green(annotate_whitespace( + if was_last_wildcard { + search_text + } else { + &search_text[max_found_index..] + }, + ))); + if was_last_wildcard && max_found_index > 0 { + output_lines.push(format!( + "==== MAX FOUND ====\n{}", + colors::red(annotate_whitespace( + &search_text[..max_found_index] + )) + )); + } + return WildcardMatchResult::Fail(output_lines.join("\n")); + } + } + } + WildcardPatternPart::UnorderedLines(expected_lines) => { + assert!(!was_last_wildcard, "unsupported"); + let mut actual_lines = Vec::with_capacity(expected_lines.len()); + for _ in 0..expected_lines.len() { + match current_text.find('\n') { + Some(end_line_index) => { + actual_lines.push(¤t_text[..end_line_index]); + current_text = ¤t_text[end_line_index + 1..]; + } + None => { + break; + } + } + } + actual_lines.sort_unstable(); + let mut expected_lines = expected_lines.clone(); + expected_lines.sort_unstable(); + + if actual_lines.len() != expected_lines.len() { + output_lines + .push("==== HAD WRONG NUMBER OF UNORDERED LINES ====".to_string()); + output_lines.push("# ACTUAL".to_string()); + output_lines.extend( + actual_lines + .iter() + .map(|l| colors::green(annotate_whitespace(l))), + ); + output_lines.push("# EXPECTED".to_string()); + output_lines.extend( + expected_lines + .iter() + .map(|l| colors::green(annotate_whitespace(l))), + ); + return WildcardMatchResult::Fail(output_lines.join("\n")); + } + for (actual, expected) in actual_lines.iter().zip(expected_lines.iter()) + { + if actual != expected { + output_lines + .push("==== UNORDERED LINE DID NOT MATCH ====".to_string()); + output_lines.push(format!( + " ACTUAL: {}", + colors::red(annotate_whitespace(actual)) + )); + output_lines.push(format!( + "EXPECTED: {}", + colors::green(annotate_whitespace(expected)) + )); + return WildcardMatchResult::Fail(output_lines.join("\n")); + } + } + output_lines.push("# Found matching unordered lines".to_string()); + } } - dbg!(part, i); - if i == parts.len() - 1 && (part.is_empty() || *part == "\n") { - dbg!("exit 1 true", i); - return true; - } - if let Some(found) = t.1.find(*part) { - dbg!("found ", found); - t = t.1.split_at(found + part.len()); - } else { - dbg!("exit false ", i); - return false; + was_last_wildcard = matches!(part, WildcardPatternPart::Wildcard); + } + + if was_last_wildcard || current_text.is_empty() { + WildcardMatchResult::Success + } else { + output_lines.push("==== HAD TEXT AT END OF FILE ====".to_string()); + output_lines.push(colors::red(annotate_whitespace(current_text))); + WildcardMatchResult::Fail(output_lines.join("\n")) + } +} + +#[derive(Debug)] +enum WildcardPatternPart<'a> { + Wildcard, + Text(&'a str), + UnorderedLines(Vec<&'a str>), +} + +fn parse_wildcard_pattern_text( + text: &str, +) -> Result, monch::ParseErrorFailureError> { + use monch::*; + + fn parse_unordered_lines(input: &str) -> ParseResult> { + const END_TEXT: &str = "\n[UNORDERED_END]\n"; + let (input, _) = tag("[UNORDERED_START]\n")(input)?; + match input.find(END_TEXT) { + Some(end_index) => ParseResult::Ok(( + &input[end_index + END_TEXT.len()..], + input[..end_index].lines().collect::>(), + )), + None => ParseError::fail(input, "Could not find [UNORDERED_END]"), } } - dbg!("end ", t.1.len()); - t.1.is_empty() + enum InnerPart<'a> { + Wildcard, + UnorderedLines(Vec<&'a str>), + Char, + } + + struct Parser<'a> { + current_input: &'a str, + last_text_input: &'a str, + parts: Vec>, + } + + impl<'a> Parser<'a> { + fn parse(mut self) -> ParseResult<'a, Vec>> { + while !self.current_input.is_empty() { + let (next_input, inner_part) = or3( + map(tag("[WILDCARD]"), |_| InnerPart::Wildcard), + map(parse_unordered_lines, |lines| { + InnerPart::UnorderedLines(lines) + }), + map(next_char, |_| InnerPart::Char), + )(self.current_input)?; + match inner_part { + InnerPart::Wildcard => { + self.queue_previous_text(next_input); + self.parts.push(WildcardPatternPart::Wildcard); + } + InnerPart::UnorderedLines(expected_lines) => { + self.queue_previous_text(next_input); + self + .parts + .push(WildcardPatternPart::UnorderedLines(expected_lines)); + } + InnerPart::Char => { + // ignore + } + } + self.current_input = next_input; + } + + self.queue_previous_text(""); + + ParseResult::Ok(("", self.parts)) + } + + fn queue_previous_text(&mut self, next_input: &'a str) { + let previous_text = &self.last_text_input + [..self.last_text_input.len() - self.current_input.len()]; + if !previous_text.is_empty() { + self.parts.push(WildcardPatternPart::Text(previous_text)); + } + self.last_text_input = next_input; + } + } + + with_failure_handling(|input| { + Parser { + current_input: input, + last_text_input: input, + parts: Vec::new(), + } + .parse() + })(text) } pub fn with_pty(deno_args: &[&str], action: impl FnMut(Pty)) { @@ -2604,6 +2813,67 @@ pub fn parse_max_mem(output: &str) -> Option { None } +pub(crate) mod colors { + use std::io::Write; + + use termcolor::Ansi; + use termcolor::Color; + use termcolor::ColorSpec; + use termcolor::WriteColor; + + pub fn bold>(s: S) -> String { + let mut style_spec = ColorSpec::new(); + style_spec.set_bold(true); + style(s, style_spec) + } + + pub fn red>(s: S) -> String { + fg_color(s, Color::Red) + } + + pub fn bold_red>(s: S) -> String { + bold_fg_color(s, Color::Red) + } + + pub fn green>(s: S) -> String { + fg_color(s, Color::Green) + } + + pub fn bold_green>(s: S) -> String { + bold_fg_color(s, Color::Green) + } + + pub fn bold_blue>(s: S) -> String { + bold_fg_color(s, Color::Blue) + } + + pub fn gray>(s: S) -> String { + fg_color(s, Color::Ansi256(245)) + } + + fn bold_fg_color>(s: S, color: Color) -> String { + let mut style_spec = ColorSpec::new(); + style_spec.set_bold(true); + style_spec.set_fg(Some(color)); + style(s, style_spec) + } + + fn fg_color>(s: S, color: Color) -> String { + let mut style_spec = ColorSpec::new(); + style_spec.set_fg(Some(color)); + style(s, style_spec) + } + + fn style>(s: S, colorspec: ColorSpec) -> String { + let mut v = Vec::new(); + let mut ansi_writer = Ansi::new(&mut v); + ansi_writer.set_color(&colorspec).unwrap(); + ansi_writer.write_all(s.as_ref().as_bytes()).unwrap(); + ansi_writer.reset().unwrap(); + String::from_utf8_lossy(&v).into_owned() + } +} + #[cfg(test)] mod tests { use super::*; @@ -2689,6 +2959,15 @@ mod tests { assert_eq!(strace.get("total").unwrap().usecs_per_call, Some(6)); } + #[test] + fn parse_parse_wildcard_match_text() { + let result = + parse_wildcard_pattern_text("[UNORDERED_START]\ntesting\ntesting") + .err() + .unwrap(); + assert_contains!(result.to_string(), "Could not find [UNORDERED_END]"); + } + #[test] fn test_wildcard_match() { let fixtures = vec![ @@ -2733,16 +3012,15 @@ mod tests { } #[test] - fn test_pattern_match() { + fn test_wildcard_match2() { // foo, bar, baz, qux, quux, quuz, corge, grault, garply, waldo, fred, plugh, xyzzy - let wildcard = "[BAR]"; - assert!(pattern_match("foo[BAR]baz", "foobarbaz", wildcard)); - assert!(!pattern_match("foo[BAR]baz", "foobazbar", wildcard)); + assert!(wildcard_match("foo[WILDCARD]baz", "foobarbaz")); + assert!(!wildcard_match("foo[WILDCARD]baz", "foobazbar")); - let multiline_pattern = "[BAR] + let multiline_pattern = "[WILDCARD] foo: -[BAR]baz[BAR]"; +[WILDCARD]baz[WILDCARD]"; fn multi_line_builder(input: &str, leading_text: Option<&str>) -> String { // If there is leading text add a newline so it's on it's own line @@ -2767,31 +3045,52 @@ grault", ); // Correct input & leading line - assert!(pattern_match( + assert!(wildcard_match( multiline_pattern, &multi_line_builder("baz", Some("QUX=quux")), - wildcard )); - // Correct input & no leading line - assert!(pattern_match( + // Should fail when leading line + assert!(!wildcard_match( multiline_pattern, &multi_line_builder("baz", None), - wildcard )); // Incorrect input & leading line - assert!(!pattern_match( + assert!(!wildcard_match( multiline_pattern, &multi_line_builder("garply", Some("QUX=quux")), - wildcard )); // Incorrect input & no leading line - assert!(!pattern_match( + assert!(!wildcard_match( multiline_pattern, &multi_line_builder("garply", None), - wildcard + )); + } + + #[test] + fn test_wildcard_match_unordered_lines() { + // matching + assert!(wildcard_match( + concat!("[UNORDERED_START]\n", "B\n", "A\n", "[UNORDERED_END]\n"), + concat!("A\n", "B\n",) + )); + // different line + assert!(!wildcard_match( + concat!("[UNORDERED_START]\n", "Ba\n", "A\n", "[UNORDERED_END]\n"), + concat!("A\n", "B\n",) + )); + // different number of lines + assert!(!wildcard_match( + concat!( + "[UNORDERED_START]\n", + "B\n", + "A\n", + "C\n", + "[UNORDERED_END]\n" + ), + concat!("A\n", "B\n",) )); }