Use ui_test from crates.io instead of having it in-tree
This commit is contained in:
parent
9f99aa9d0d
commit
fb071a14bd
4
Cargo.lock
generated
4
Cargo.lock
generated
@ -756,7 +756,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ui_test"
|
||||
version = "0.1.0"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43a7bfdb147f57c498ca629c7802b57899de0bb82ae36b6f01f1540da41832f1"
|
||||
dependencies = [
|
||||
"cargo_metadata",
|
||||
"color-eyre",
|
||||
|
@ -38,7 +38,7 @@ libc = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
colored = "2"
|
||||
ui_test = { path = "ui_test" }
|
||||
ui_test = "0.1"
|
||||
# Features chosen to match those required by env_logger, to avoid rebuilds
|
||||
regex = { version = "1.5.5", default-features = false, features = ["perf", "std"] }
|
||||
lazy_static = "1.4.0"
|
||||
|
4
miri
4
miri
@ -186,9 +186,8 @@ test|bless)
|
||||
export MIRI_BLESS="Gesundheit"
|
||||
fi
|
||||
# Then test, and let caller control flags.
|
||||
# Only in root project and ui_test as `cargo-miri` has no tests.
|
||||
# Only in root project as `cargo-miri` has no tests.
|
||||
$CARGO test $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml "$@"
|
||||
$CARGO test $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/ui_test/Cargo.toml "$@"
|
||||
;;
|
||||
run)
|
||||
# Scan for "--target" to overwrite the "MIRI_TEST_TARGET" env var so
|
||||
@ -219,7 +218,6 @@ fmt)
|
||||
;;
|
||||
clippy)
|
||||
$CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/Cargo.toml --all-targets "$@"
|
||||
$CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/ui_test/Cargo.toml --all-targets "$@"
|
||||
$CARGO clippy $CARGO_EXTRA_FLAGS --manifest-path "$MIRIDIR"/cargo-miri/Cargo.toml "$@"
|
||||
;;
|
||||
cargo)
|
||||
|
578
ui_test/Cargo.lock
generated
578
ui_test/Cargo.lock
generated
@ -1,578 +0,0 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.17.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b"
|
||||
dependencies = [
|
||||
"gimli",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atty"
|
||||
version = "0.2.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"cc",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "camino"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "869119e97797867fd90f5e22af7d0bd274bd4635ebb9eb68c04f3f513ae6c412"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo-platform"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cbdb825da8a5df079a43676dbe042702f1707b1109f713a01420fbb4cc71fa27"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cargo_metadata"
|
||||
version = "0.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3abb7553d5b9b8421c6de7cb02606ff15e0c6eea7d8eadd75ef013fd636bec36"
|
||||
dependencies = [
|
||||
"camino",
|
||||
"cargo-platform",
|
||||
"semver",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.73"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "color-eyre"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ebf286c900a6d5867aeff75cfee3192857bb7f24b547d4f0df2ed6baa812c90"
|
||||
dependencies = [
|
||||
"backtrace",
|
||||
"color-spantrace",
|
||||
"eyre",
|
||||
"indenter",
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "color-spantrace"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ba75b3d9449ecdccb27ecbc479fdc0b87fa2dd43d2f8298f9bf0e59aacc8dce"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"owo-colors",
|
||||
"tracing-core",
|
||||
"tracing-error",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "colored"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
|
||||
dependencies = [
|
||||
"atty",
|
||||
"lazy_static",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-channel",
|
||||
"crossbeam-deque",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-channel"
|
||||
version = "0.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f25d8400f4a7a5778f0e4e52384a48cbd9b5c495d110786187fc750075277a2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "diff"
|
||||
version = "0.1.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
|
||||
|
||||
[[package]]
|
||||
name = "eyre"
|
||||
version = "0.6.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c2b6b5a29c02cdc822728b7d7b8ae1bab3e3b05d44522770ddd49722eeac7eb"
|
||||
dependencies = [
|
||||
"indenter",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.26.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4"
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indenter"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
|
||||
|
||||
[[package]]
|
||||
name = "itoa"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.6.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc"
|
||||
dependencies = [
|
||||
"adler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.28.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18a6dbe30758c9f83eb00cbea4ac95966305f5a7772f3f42ebfc7fc7eddbd8e1"
|
||||
|
||||
[[package]]
|
||||
name = "output_vt100"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "owo-colors"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "decf7381921fea4dcb2549c5667eda59b3ec297ab7e2b5fc33eac69d2e7da87b"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
|
||||
|
||||
[[package]]
|
||||
name = "pretty_assertions"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"ctor",
|
||||
"diff",
|
||||
"output_vt100",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c54b25569025b7fc9651de43004ae593a75ad88543b17178aa5e1b9c4f15f56f"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1feb54ed693b93a84e14094943b84b7c4eae204c512b7ccb95ab0c66d278ad1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8cb243bdfdb5936c8dc3c45762a19d12ab4550cdc753bc247637d4ec35a040fd"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.137"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_json"
|
||||
version = "1.0.81"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "1.1.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.35"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"pin-project-lite",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"valuable",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-error"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d686ec1c0f384b1277f097b2f279a2ecc11afe8c133c1aabf036a27cb4cd206e"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-subscriber"
|
||||
version = "0.3.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a713421342a5a666b7577783721d3117f1b69a393df803ee17bb73b1e122a59"
|
||||
dependencies = [
|
||||
"sharded-slab",
|
||||
"thread_local",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ui_test"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cargo_metadata",
|
||||
"color-eyre",
|
||||
"colored",
|
||||
"crossbeam",
|
||||
"lazy_static",
|
||||
"pretty_assertions",
|
||||
"regex",
|
||||
"rustc_version",
|
||||
"serde",
|
||||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee"
|
||||
|
||||
[[package]]
|
||||
name = "valuable"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
@ -1,21 +0,0 @@
|
||||
[package]
|
||||
name = "ui_test"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
test = true # we have unit tests
|
||||
doctest = false # but no doc tests
|
||||
|
||||
[dependencies]
|
||||
rustc_version = "0.4"
|
||||
colored = "2"
|
||||
# Features chosen to match those required by env_logger, to avoid rebuilds
|
||||
regex = { version = "1.5.5", default-features = false, features = ["perf", "std"] }
|
||||
pretty_assertions = "1.2.1"
|
||||
crossbeam = "0.8.1"
|
||||
lazy_static = "1.4.0"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
color-eyre = { version = "0.6.1", default-features = false, features = ["capture-spantrace"] }
|
||||
cargo_metadata = "0.15"
|
@ -1,51 +0,0 @@
|
||||
A smaller version of compiletest-rs
|
||||
|
||||
## Magic behavior
|
||||
|
||||
* Tests are run in order of their filenames (files first, then recursing into folders).
|
||||
So if you have any slow tests, prepend them with a small integral number to make them get run first, taking advantage of parallelism as much as possible (instead of waiting for the slow tests at the end).
|
||||
|
||||
## Supported magic comment annotations
|
||||
|
||||
If your test tests for failure, you need to add a `//~` annotation where the error is happening
|
||||
to make sure that the test will always keep failing with a specific message at the annotated line.
|
||||
|
||||
`//~ ERROR: XXX` make sure the stderr output contains `XXX` for an error in the line where this comment is written
|
||||
|
||||
* Also supports `HELP`, `WARN` or `NOTE` for different kind of message
|
||||
* if one of those levels is specified explicitly, *all* diagnostics of this level or higher need an annotation. If you want to avoid this, just leave out the all caps level note entirely.
|
||||
* If the all caps note is left out, a message of any level is matched. Leaving it out is not allowed for `ERROR` levels.
|
||||
* This checks the output *before* normalization, so you can check things that get normalized away, but need to
|
||||
be careful not to accidentally have a pattern that differs between platforms.
|
||||
* if `XXX` is of the form `/XXX/` it is treated as a regex instead of a substring and will succeed if the regex matches.
|
||||
|
||||
In order to change how a single test is tested, you can add various `//@` comments to the test.
|
||||
Any other comments will be ignored, and all `//@` comments must be formatted precisely as
|
||||
their command specifies, or the test will fail without even being run.
|
||||
|
||||
* `//@ignore-C` avoids running the test when condition `C` is met.
|
||||
* `C` can be `target-XXX`, which checks whether the target triple contains `XXX`.
|
||||
* `C` can also be one of `64bit`, `32bit` or `16bit`.
|
||||
* `C` can also be `on-host`, which will only run the test during cross compilation testing.
|
||||
* `//@only-C` **only** runs the test when condition `C` is met. The conditions are the same as with `ignore`.
|
||||
* `//@stderr-per-bitwidth` produces one stderr file per bitwidth, as they may differ significantly sometimes
|
||||
* `//@error-pattern: XXX` make sure the stderr output contains `XXX`
|
||||
* `//@revisions: XXX YYY` runs the test once for each space separated name in the list
|
||||
* emits one stderr file per revision
|
||||
* `//~` comments can be restricted to specific revisions by adding the revision name before the `~` in square brackets: `//[XXX]~`
|
||||
* `//@compile-flags: XXX` appends `XXX` to the command line arguments passed to the rustc driver
|
||||
* you can specify this multiple times, and all the flags will accumulate
|
||||
* `//@rustc-env: XXX=YYY` sets the env var `XXX` to `YYY` for the rustc driver execution.
|
||||
* for Miri these env vars are used during compilation via rustc and during the emulation of the program
|
||||
* you can specify this multiple times, accumulating all the env vars
|
||||
* `//@normalize-stderr-test: "REGEX" -> "REPLACEMENT"` replaces all matches of `REGEX` in the stderr with `REPLACEMENT`. The replacement may specify `$1` and similar backreferences to paste captures.
|
||||
* you can specify multiple such commands, there is no need to create a single regex that handles multiple replacements that you want to perform.
|
||||
* `//@require-annotations-for-level: LEVEL` can be used to change the level of diagnostics that require a corresponding annotation.
|
||||
* this is only useful if there are any annotations like `HELP`, `WARN` or `NOTE`, as these would automatically require annotations for all other diagnostics of the same or higher level.
|
||||
|
||||
## Significant differences to compiletest-rs
|
||||
|
||||
* `ignore-target-*` and `only-target-*` opereate solely on the triple, instead of supporting things like `macos`
|
||||
* only `//~` comments can be individualized per revision
|
||||
* only supports `ui` tests
|
||||
* tests are run in named order, so you can prefix slow tests with `0` in order to make them get run first
|
@ -1,137 +0,0 @@
|
||||
use color_eyre::eyre::{bail, Result};
|
||||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use crate::{Config, OutputConflictHandling};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct Dependencies {
|
||||
/// All paths that must be imported with `-L dependency=`. This is for
|
||||
/// finding proc macros run on the host and dependencies for the target.
|
||||
pub import_paths: Vec<PathBuf>,
|
||||
/// The name as chosen in the `Cargo.toml` and its corresponding rmeta file.
|
||||
pub dependencies: Vec<(String, PathBuf)>,
|
||||
}
|
||||
|
||||
/// Compiles dependencies and returns the crate names and corresponding rmeta files.
|
||||
pub fn build_dependencies(config: &Config) -> Result<Dependencies> {
|
||||
let manifest_path = match &config.dependencies_crate_manifest_path {
|
||||
Some(path) => path,
|
||||
None => return Ok(Default::default()),
|
||||
};
|
||||
let (program, args, envs): (&Path, &[_], &[_]) = match &config.dependency_builder {
|
||||
Some(db) => (&db.program, &db.args, &db.envs),
|
||||
None => (Path::new("cargo"), &[], &[]),
|
||||
};
|
||||
let mut build = Command::new(program);
|
||||
build.args(args);
|
||||
// HACK: we're using `cargo run` (or `cargo miri run`), because the latter does not
|
||||
// support `cargo miri build` yet.
|
||||
build.arg("run");
|
||||
|
||||
if let Some(target) = &config.target {
|
||||
build.arg(format!("--target={target}"));
|
||||
}
|
||||
|
||||
// Reusable closure for setting up the environment both for artifact generation and `cargo_metadata`
|
||||
let setup_command = |cmd: &mut Command| {
|
||||
cmd.envs(envs.iter().map(|(k, v)| (k, v)));
|
||||
cmd.arg("--manifest-path").arg(manifest_path);
|
||||
if matches!(config.output_conflict_handling, OutputConflictHandling::Error) {
|
||||
cmd.arg("--locked");
|
||||
}
|
||||
};
|
||||
|
||||
setup_command(&mut build);
|
||||
build.arg("--message-format=json");
|
||||
|
||||
let output = build.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
let stderr = String::from_utf8(output.stderr)?;
|
||||
bail!("failed to compile dependencies:\nstderr:\n{stderr}\n\nstdout:{stdout}");
|
||||
}
|
||||
|
||||
// Collect all artifacts generated
|
||||
let output = output.stdout;
|
||||
let output = String::from_utf8(output)?;
|
||||
let mut import_paths: HashSet<PathBuf> = HashSet::new();
|
||||
let mut artifacts: HashMap<_, _> = output
|
||||
.lines()
|
||||
.filter_map(|line| {
|
||||
let message = serde_json::from_str::<cargo_metadata::Message>(line).ok()?;
|
||||
if let cargo_metadata::Message::CompilerArtifact(artifact) = message {
|
||||
for filename in &artifact.filenames {
|
||||
import_paths.insert(filename.parent().unwrap().into());
|
||||
}
|
||||
let filename = artifact
|
||||
.filenames
|
||||
.into_iter()
|
||||
.find(|filename| filename.extension() == Some("rmeta"))?;
|
||||
Some((artifact.package_id, filename.into_std_path_buf()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Check which crates are mentioned in the crate itself
|
||||
let mut metadata = cargo_metadata::MetadataCommand::new().cargo_command();
|
||||
setup_command(&mut metadata);
|
||||
let output = metadata.output()?;
|
||||
|
||||
if !output.status.success() {
|
||||
let stdout = String::from_utf8(output.stdout)?;
|
||||
let stderr = String::from_utf8(output.stderr)?;
|
||||
bail!("failed to run cargo-metadata:\nstderr:\n{stderr}\n\nstdout:{stdout}");
|
||||
}
|
||||
|
||||
let output = output.stdout;
|
||||
let output = String::from_utf8(output)?;
|
||||
|
||||
for line in output.lines() {
|
||||
if !line.starts_with('{') {
|
||||
continue;
|
||||
}
|
||||
let metadata: cargo_metadata::Metadata = serde_json::from_str(line)?;
|
||||
// Only take artifacts that are defined in the Cargo.toml
|
||||
|
||||
// First, find the root artifact
|
||||
let root = metadata
|
||||
.packages
|
||||
.iter()
|
||||
.find(|package| {
|
||||
package.manifest_path.as_std_path().canonicalize().unwrap()
|
||||
== manifest_path.canonicalize().unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
// Then go over all of its dependencies
|
||||
let dependencies = root
|
||||
.dependencies
|
||||
.iter()
|
||||
.map(|package| {
|
||||
// Get the id for the package matching the version requirement of the dep
|
||||
let id = &metadata
|
||||
.packages
|
||||
.iter()
|
||||
.find(|&dep| dep.name == package.name && package.req.matches(&dep.version))
|
||||
.expect("dependency does not exist")
|
||||
.id;
|
||||
// Return the name chosen in `Cargo.toml` and the path to the corresponding artifact
|
||||
(
|
||||
package.rename.clone().unwrap_or_else(|| package.name.clone()),
|
||||
artifacts.remove(id).expect("package without artifact"),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let import_paths = import_paths.into_iter().collect();
|
||||
return Ok(Dependencies { dependencies, import_paths });
|
||||
}
|
||||
|
||||
bail!("no json found in cargo-metadata output")
|
||||
}
|
@ -1,637 +0,0 @@
|
||||
#![allow(
|
||||
clippy::enum_variant_names,
|
||||
clippy::useless_format,
|
||||
clippy::too_many_arguments,
|
||||
rustc::internal
|
||||
)]
|
||||
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::OsString;
|
||||
use std::fmt::Write;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::{Command, ExitStatus};
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
use std::sync::Mutex;
|
||||
|
||||
pub use color_eyre;
|
||||
use color_eyre::eyre::Result;
|
||||
use colored::*;
|
||||
use parser::{ErrorMatch, Pattern};
|
||||
use regex::Regex;
|
||||
use rustc_stderr::{Level, Message};
|
||||
|
||||
use crate::dependencies::build_dependencies;
|
||||
use crate::parser::{Comments, Condition};
|
||||
|
||||
mod dependencies;
|
||||
mod parser;
|
||||
mod rustc_stderr;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Config {
|
||||
/// Arguments passed to the binary that is executed.
|
||||
pub args: Vec<OsString>,
|
||||
/// `None` to run on the host, otherwise a target triple
|
||||
pub target: Option<String>,
|
||||
/// Filters applied to stderr output before processing it
|
||||
pub stderr_filters: Filter,
|
||||
/// Filters applied to stdout output before processing it
|
||||
pub stdout_filters: Filter,
|
||||
/// The folder in which to start searching for .rs files
|
||||
pub root_dir: PathBuf,
|
||||
pub mode: Mode,
|
||||
pub program: PathBuf,
|
||||
pub output_conflict_handling: OutputConflictHandling,
|
||||
/// Only run tests with one of these strings in their path/name
|
||||
pub path_filter: Vec<String>,
|
||||
/// Path to a `Cargo.toml` that describes which dependencies the tests can access.
|
||||
pub dependencies_crate_manifest_path: Option<PathBuf>,
|
||||
/// Can be used to override what command to run instead of `cargo` to build the
|
||||
/// dependencies in `manifest_path`
|
||||
pub dependency_builder: Option<DependencyBuilder>,
|
||||
/// Print one character per test instead of one line
|
||||
pub quiet: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DependencyBuilder {
|
||||
pub program: PathBuf,
|
||||
pub args: Vec<String>,
|
||||
pub envs: Vec<(String, OsString)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OutputConflictHandling {
|
||||
/// The default: emit a diff of the expected/actual output.
|
||||
Error,
|
||||
/// Ignore mismatches in the stderr/stdout files.
|
||||
Ignore,
|
||||
/// Instead of erroring if the stderr/stdout differs from the expected
|
||||
/// automatically replace it with the found output (after applying filters).
|
||||
Bless,
|
||||
}
|
||||
|
||||
pub type Filter = Vec<(Regex, &'static str)>;
|
||||
|
||||
pub fn run_tests(mut config: Config) -> Result<()> {
|
||||
eprintln!(" Compiler flags: {:?}", config.args);
|
||||
|
||||
// Get the triple with which to run the tests
|
||||
let target = config.target.clone().unwrap_or_else(|| config.get_host());
|
||||
|
||||
eprintln!(" Building test dependencies...");
|
||||
let dependencies = build_dependencies(&config)?;
|
||||
for (name, dependency) in dependencies.dependencies {
|
||||
config.args.push("--extern".into());
|
||||
let mut dep = OsString::from(name);
|
||||
dep.push("=");
|
||||
dep.push(dependency);
|
||||
config.args.push(dep);
|
||||
}
|
||||
for import_path in dependencies.import_paths {
|
||||
config.args.push("-L".into());
|
||||
config.args.push(import_path.into());
|
||||
}
|
||||
let config = config;
|
||||
|
||||
// A channel for files to process
|
||||
let (submit, receive) = crossbeam::channel::unbounded();
|
||||
|
||||
// Some statistics and failure reports.
|
||||
let failures = Mutex::new(vec![]);
|
||||
let succeeded = AtomicUsize::default();
|
||||
let ignored = AtomicUsize::default();
|
||||
let filtered = AtomicUsize::default();
|
||||
|
||||
crossbeam::scope(|s| -> Result<()> {
|
||||
// Create a thread that is in charge of walking the directory and submitting jobs.
|
||||
// It closes the channel when it is done.
|
||||
s.spawn(|_| {
|
||||
let mut todo = VecDeque::new();
|
||||
todo.push_back(config.root_dir.clone());
|
||||
while let Some(path) = todo.pop_front() {
|
||||
if path.is_dir() {
|
||||
// Enqueue everything inside this directory.
|
||||
// We want it sorted, to have some control over scheduling of slow tests.
|
||||
let mut entries =
|
||||
std::fs::read_dir(path).unwrap().collect::<Result<Vec<_>, _>>().unwrap();
|
||||
entries.sort_by_key(|e| e.file_name());
|
||||
for entry in entries {
|
||||
todo.push_back(entry.path());
|
||||
}
|
||||
} else if path.extension().map(|ext| ext == "rs").unwrap_or(false) {
|
||||
// Forward .rs files to the test workers.
|
||||
submit.send(path).unwrap();
|
||||
}
|
||||
}
|
||||
// There will be no more jobs. This signals the workers to quit.
|
||||
// (This also ensures `submit` is moved into this closure.)
|
||||
drop(submit);
|
||||
});
|
||||
|
||||
// A channel for the messages emitted by the individual test threads.
|
||||
let (finished_files_sender, finished_files_recv) = crossbeam::channel::unbounded();
|
||||
enum TestResult {
|
||||
Ok,
|
||||
Failed,
|
||||
Ignored,
|
||||
}
|
||||
|
||||
s.spawn(|_| {
|
||||
if config.quiet {
|
||||
for (i, (_, result)) in finished_files_recv.into_iter().enumerate() {
|
||||
// Humans start counting at 1
|
||||
let i = i + 1;
|
||||
match result {
|
||||
TestResult::Ok => eprint!("{}", ".".green()),
|
||||
TestResult::Failed => eprint!("{}", "F".red().bold()),
|
||||
TestResult::Ignored => eprint!("{}", "i".yellow()),
|
||||
}
|
||||
if i % 100 == 0 {
|
||||
eprintln!(" {i}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (msg, result) in finished_files_recv {
|
||||
eprint!("{msg} ... ");
|
||||
eprintln!(
|
||||
"{}",
|
||||
match result {
|
||||
TestResult::Ok => "ok".green(),
|
||||
TestResult::Failed => "FAILED".red().bold(),
|
||||
TestResult::Ignored => "ignored (in-test comment)".yellow(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let mut threads = vec![];
|
||||
|
||||
// Create N worker threads that receive files to test.
|
||||
for _ in 0..std::thread::available_parallelism().unwrap().get() {
|
||||
let finished_files_sender = finished_files_sender.clone();
|
||||
threads.push(s.spawn(|_| -> Result<()> {
|
||||
let finished_files_sender = finished_files_sender;
|
||||
for path in &receive {
|
||||
if !config.path_filter.is_empty() {
|
||||
let path_display = path.display().to_string();
|
||||
if !config.path_filter.iter().any(|filter| path_display.contains(filter)) {
|
||||
filtered.fetch_add(1, Ordering::Relaxed);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let comments = Comments::parse_file(&path)?;
|
||||
// Ignore file if only/ignore rules do (not) apply
|
||||
if !test_file_conditions(&comments, &target, &config) {
|
||||
ignored.fetch_add(1, Ordering::Relaxed);
|
||||
finished_files_sender
|
||||
.send((path.display().to_string(), TestResult::Ignored))?;
|
||||
continue;
|
||||
}
|
||||
// Run the test for all revisions
|
||||
for revision in
|
||||
comments.revisions.clone().unwrap_or_else(|| vec![String::new()])
|
||||
{
|
||||
let (m, errors, stderr) =
|
||||
run_test(&path, &config, &target, &revision, &comments);
|
||||
|
||||
// Using a single `eprintln!` to prevent messages from threads from getting intermingled.
|
||||
let mut msg = format!("{}", path.display());
|
||||
if !revision.is_empty() {
|
||||
write!(msg, " (revision `{revision}`) ").unwrap();
|
||||
}
|
||||
if errors.is_empty() {
|
||||
finished_files_sender.send((msg, TestResult::Ok))?;
|
||||
succeeded.fetch_add(1, Ordering::Relaxed);
|
||||
} else {
|
||||
finished_files_sender.send((msg, TestResult::Failed))?;
|
||||
failures.lock().unwrap().push((
|
||||
path.clone(),
|
||||
m,
|
||||
revision,
|
||||
errors,
|
||||
stderr,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}));
|
||||
}
|
||||
|
||||
for thread in threads {
|
||||
thread.join().unwrap()?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
.unwrap()?;
|
||||
|
||||
// Print all errors in a single thread to show reliable output
|
||||
let failures = failures.into_inner().unwrap();
|
||||
let succeeded = succeeded.load(Ordering::Relaxed);
|
||||
let ignored = ignored.load(Ordering::Relaxed);
|
||||
let filtered = filtered.load(Ordering::Relaxed);
|
||||
if !failures.is_empty() {
|
||||
for (path, miri, revision, errors, stderr) in &failures {
|
||||
eprintln!();
|
||||
eprint!("{}", path.display().to_string().underline().bold());
|
||||
if !revision.is_empty() {
|
||||
eprint!(" (revision `{}`)", revision);
|
||||
}
|
||||
eprint!(" {}", "FAILED:".red().bold());
|
||||
eprintln!();
|
||||
eprintln!("command: {:?}", miri);
|
||||
eprintln!();
|
||||
for error in errors {
|
||||
match error {
|
||||
Error::ExitStatus(mode, exit_status) => eprintln!("{mode:?} got {exit_status}"),
|
||||
Error::PatternNotFound { pattern, definition_line } => {
|
||||
match pattern {
|
||||
Pattern::SubString(s) =>
|
||||
eprintln!("substring `{s}` {} in stderr output", "not found".red()),
|
||||
Pattern::Regex(r) =>
|
||||
eprintln!("`/{r}/` does {} stderr output", "not match".red()),
|
||||
}
|
||||
eprintln!(
|
||||
"expected because of pattern here: {}:{definition_line}",
|
||||
path.display().to_string().bold()
|
||||
);
|
||||
}
|
||||
Error::NoPatternsFound => {
|
||||
eprintln!("{}", "no error patterns found in failure test".red());
|
||||
}
|
||||
Error::PatternFoundInPassTest =>
|
||||
eprintln!("{}", "error pattern found in success test".red()),
|
||||
Error::OutputDiffers { path, actual, expected } => {
|
||||
eprintln!("actual output differed from expected {}", path.display());
|
||||
eprintln!("{}", pretty_assertions::StrComparison::new(expected, actual));
|
||||
eprintln!()
|
||||
}
|
||||
Error::ErrorsWithoutPattern { path: None, msgs } => {
|
||||
eprintln!(
|
||||
"There were {} unmatched diagnostics that occurred outside the testfile and had no pattern",
|
||||
msgs.len(),
|
||||
);
|
||||
for Message { level, message } in msgs {
|
||||
eprintln!(" {level:?}: {message}")
|
||||
}
|
||||
}
|
||||
Error::ErrorsWithoutPattern { path: Some((path, line)), msgs } => {
|
||||
eprintln!(
|
||||
"There were {} unmatched diagnostics at {}:{line}",
|
||||
msgs.len(),
|
||||
path.display()
|
||||
);
|
||||
for Message { level, message } in msgs {
|
||||
eprintln!(" {level:?}: {message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
eprintln!();
|
||||
}
|
||||
eprintln!("full stderr:");
|
||||
eprintln!("{}", stderr);
|
||||
eprintln!();
|
||||
}
|
||||
eprintln!("{}", "FAILURES:".red().underline().bold());
|
||||
for (path, _miri, _revision, _errors, _stderr) in &failures {
|
||||
eprintln!(" {}", path.display());
|
||||
}
|
||||
eprintln!();
|
||||
eprintln!(
|
||||
"test result: {}. {} tests failed, {} tests passed, {} ignored, {} filtered out",
|
||||
"FAIL".red(),
|
||||
failures.len().to_string().red().bold(),
|
||||
succeeded.to_string().green(),
|
||||
ignored.to_string().yellow(),
|
||||
filtered.to_string().yellow(),
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
eprintln!();
|
||||
eprintln!(
|
||||
"test result: {}. {} tests passed, {} ignored, {} filtered out",
|
||||
"ok".green(),
|
||||
succeeded.to_string().green(),
|
||||
ignored.to_string().yellow(),
|
||||
filtered.to_string().yellow(),
|
||||
);
|
||||
eprintln!();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Error {
|
||||
/// Got an invalid exit status for the given mode.
|
||||
ExitStatus(Mode, ExitStatus),
|
||||
PatternNotFound {
|
||||
pattern: Pattern,
|
||||
definition_line: usize,
|
||||
},
|
||||
/// A ui test checking for failure does not have any failure patterns
|
||||
NoPatternsFound,
|
||||
/// A ui test checking for success has failure patterns
|
||||
PatternFoundInPassTest,
|
||||
/// Stderr/Stdout differed from the `.stderr`/`.stdout` file present.
|
||||
OutputDiffers {
|
||||
path: PathBuf,
|
||||
actual: String,
|
||||
expected: String,
|
||||
},
|
||||
ErrorsWithoutPattern {
|
||||
msgs: Vec<Message>,
|
||||
path: Option<(PathBuf, usize)>,
|
||||
},
|
||||
}
|
||||
|
||||
type Errors = Vec<Error>;
|
||||
|
||||
fn run_test(
|
||||
path: &Path,
|
||||
config: &Config,
|
||||
target: &str,
|
||||
revision: &str,
|
||||
comments: &Comments,
|
||||
) -> (Command, Errors, String) {
|
||||
// Run miri
|
||||
let mut miri = Command::new(&config.program);
|
||||
miri.args(config.args.iter());
|
||||
miri.arg(path);
|
||||
if !revision.is_empty() {
|
||||
miri.arg(format!("--cfg={revision}"));
|
||||
}
|
||||
miri.arg("--error-format=json");
|
||||
for arg in &comments.compile_flags {
|
||||
miri.arg(arg);
|
||||
}
|
||||
miri.envs(comments.env_vars.iter().map(|(k, v)| (k, v)));
|
||||
let output = miri.output().expect("could not execute miri");
|
||||
let mut errors = config.mode.ok(output.status);
|
||||
let stderr = check_test_result(
|
||||
path,
|
||||
config,
|
||||
target,
|
||||
revision,
|
||||
comments,
|
||||
&mut errors,
|
||||
&output.stdout,
|
||||
&output.stderr,
|
||||
);
|
||||
(miri, errors, stderr)
|
||||
}
|
||||
|
||||
fn check_test_result(
|
||||
path: &Path,
|
||||
config: &Config,
|
||||
target: &str,
|
||||
revision: &str,
|
||||
comments: &Comments,
|
||||
errors: &mut Errors,
|
||||
stdout: &[u8],
|
||||
stderr: &[u8],
|
||||
) -> String {
|
||||
// Always remove annotation comments from stderr.
|
||||
let diagnostics = rustc_stderr::process(path, stderr);
|
||||
let stdout = std::str::from_utf8(stdout).unwrap();
|
||||
// Check output files (if any)
|
||||
let revised = |extension: &str| {
|
||||
if revision.is_empty() {
|
||||
extension.to_string()
|
||||
} else {
|
||||
format!("{}.{}", revision, extension)
|
||||
}
|
||||
};
|
||||
// Check output files against actual output
|
||||
check_output(
|
||||
&diagnostics.rendered,
|
||||
path,
|
||||
errors,
|
||||
revised("stderr"),
|
||||
target,
|
||||
&config.stderr_filters,
|
||||
config,
|
||||
comments,
|
||||
);
|
||||
check_output(
|
||||
stdout,
|
||||
path,
|
||||
errors,
|
||||
revised("stdout"),
|
||||
target,
|
||||
&config.stdout_filters,
|
||||
config,
|
||||
comments,
|
||||
);
|
||||
// Check error annotations in the source against output
|
||||
check_annotations(
|
||||
diagnostics.messages,
|
||||
diagnostics.messages_from_unknown_file_or_line,
|
||||
path,
|
||||
errors,
|
||||
config,
|
||||
revision,
|
||||
comments,
|
||||
);
|
||||
diagnostics.rendered
|
||||
}
|
||||
|
||||
fn check_annotations(
|
||||
mut messages: Vec<Vec<Message>>,
|
||||
mut messages_from_unknown_file_or_line: Vec<Message>,
|
||||
path: &Path,
|
||||
errors: &mut Errors,
|
||||
config: &Config,
|
||||
revision: &str,
|
||||
comments: &Comments,
|
||||
) {
|
||||
if let Some((ref error_pattern, definition_line)) = comments.error_pattern {
|
||||
// first check the diagnostics messages outside of our file. We check this first, so that
|
||||
// you can mix in-file annotations with //@error-pattern annotations, even if there is overlap
|
||||
// in the messages.
|
||||
if let Some(i) = messages_from_unknown_file_or_line
|
||||
.iter()
|
||||
.position(|msg| error_pattern.matches(&msg.message))
|
||||
{
|
||||
messages_from_unknown_file_or_line.remove(i);
|
||||
} else {
|
||||
errors.push(Error::PatternNotFound { pattern: error_pattern.clone(), definition_line });
|
||||
}
|
||||
}
|
||||
|
||||
// The order on `Level` is such that `Error` is the highest level.
|
||||
// We will ensure that *all* diagnostics of level at least `lowest_annotation_level`
|
||||
// are matched.
|
||||
let mut lowest_annotation_level = Level::Error;
|
||||
for &ErrorMatch { ref pattern, revision: ref rev, definition_line, line, level } in
|
||||
&comments.error_matches
|
||||
{
|
||||
if let Some(rev) = rev {
|
||||
if rev != revision {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If we found a diagnostic with a level annotation, make sure that all
|
||||
// diagnostics of that level have annotations, even if we don't end up finding a matching diagnostic
|
||||
// for this pattern.
|
||||
lowest_annotation_level = std::cmp::min(lowest_annotation_level, level);
|
||||
|
||||
if let Some(msgs) = messages.get_mut(line) {
|
||||
let found =
|
||||
msgs.iter().position(|msg| pattern.matches(&msg.message) && msg.level == level);
|
||||
if let Some(found) = found {
|
||||
msgs.remove(found);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
errors.push(Error::PatternNotFound { pattern: pattern.clone(), definition_line });
|
||||
}
|
||||
|
||||
let filter = |msgs: Vec<Message>| -> Vec<_> {
|
||||
msgs.into_iter()
|
||||
.filter(|msg| {
|
||||
msg.level
|
||||
>= comments.require_annotations_for_level.unwrap_or(lowest_annotation_level)
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
let messages_from_unknown_file_or_line = filter(messages_from_unknown_file_or_line);
|
||||
if !messages_from_unknown_file_or_line.is_empty() {
|
||||
errors.push(Error::ErrorsWithoutPattern {
|
||||
path: None,
|
||||
msgs: messages_from_unknown_file_or_line,
|
||||
});
|
||||
}
|
||||
|
||||
for (line, msgs) in messages.into_iter().enumerate() {
|
||||
let msgs = filter(msgs);
|
||||
if !msgs.is_empty() {
|
||||
errors
|
||||
.push(Error::ErrorsWithoutPattern { path: Some((path.to_path_buf(), line)), msgs });
|
||||
}
|
||||
}
|
||||
|
||||
match (config.mode, comments.error_pattern.is_some() || !comments.error_matches.is_empty()) {
|
||||
(Mode::Pass, true) | (Mode::Panic, true) => errors.push(Error::PatternFoundInPassTest),
|
||||
(Mode::Fail, false) => errors.push(Error::NoPatternsFound),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_output(
|
||||
output: &str,
|
||||
path: &Path,
|
||||
errors: &mut Errors,
|
||||
kind: String,
|
||||
target: &str,
|
||||
filters: &Filter,
|
||||
config: &Config,
|
||||
comments: &Comments,
|
||||
) {
|
||||
let output = normalize(path, output, filters, comments);
|
||||
let path = output_path(path, comments, kind, target);
|
||||
match config.output_conflict_handling {
|
||||
OutputConflictHandling::Bless =>
|
||||
if output.is_empty() {
|
||||
let _ = std::fs::remove_file(path);
|
||||
} else {
|
||||
std::fs::write(path, &output).unwrap();
|
||||
},
|
||||
OutputConflictHandling::Error => {
|
||||
let expected_output = std::fs::read_to_string(&path).unwrap_or_default();
|
||||
if output != expected_output {
|
||||
errors.push(Error::OutputDiffers {
|
||||
path,
|
||||
actual: output,
|
||||
expected: expected_output,
|
||||
});
|
||||
}
|
||||
}
|
||||
OutputConflictHandling::Ignore => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn output_path(path: &Path, comments: &Comments, kind: String, target: &str) -> PathBuf {
|
||||
if comments.stderr_per_bitwidth {
|
||||
return path.with_extension(format!("{}bit.{kind}", get_pointer_width(target)));
|
||||
}
|
||||
path.with_extension(kind)
|
||||
}
|
||||
|
||||
fn test_condition(condition: &Condition, target: &str, config: &Config) -> bool {
|
||||
match condition {
|
||||
Condition::Bitwidth(bits) => get_pointer_width(target) == *bits,
|
||||
Condition::Target(t) => target.contains(t),
|
||||
Condition::OnHost => config.target.is_none(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns whether according to the in-file conditions, this file should be run.
|
||||
fn test_file_conditions(comments: &Comments, target: &str, config: &Config) -> bool {
|
||||
if comments.ignore.iter().any(|c| test_condition(c, target, config)) {
|
||||
return false;
|
||||
}
|
||||
comments.only.iter().all(|c| test_condition(c, target, config))
|
||||
}
|
||||
|
||||
// Taken 1:1 from compiletest-rs
|
||||
fn get_pointer_width(triple: &str) -> u8 {
|
||||
if (triple.contains("64") && !triple.ends_with("gnux32") && !triple.ends_with("gnu_ilp32"))
|
||||
|| triple.starts_with("s390x")
|
||||
{
|
||||
64
|
||||
} else if triple.starts_with("avr") {
|
||||
16
|
||||
} else {
|
||||
32
|
||||
}
|
||||
}
|
||||
|
||||
fn normalize(path: &Path, text: &str, filters: &Filter, comments: &Comments) -> String {
|
||||
// Useless paths
|
||||
let mut text = text.replace(&path.parent().unwrap().display().to_string(), "$DIR");
|
||||
if let Some(lib_path) = option_env!("RUSTC_LIB_PATH") {
|
||||
text = text.replace(lib_path, "RUSTLIB");
|
||||
}
|
||||
|
||||
for (regex, replacement) in filters.iter() {
|
||||
text = regex.replace_all(&text, *replacement).to_string();
|
||||
}
|
||||
|
||||
for (from, to) in &comments.normalize_stderr {
|
||||
text = from.replace_all(&text, to).to_string();
|
||||
}
|
||||
text
|
||||
}
|
||||
|
||||
impl Config {
|
||||
fn get_host(&self) -> String {
|
||||
rustc_version::VersionMeta::for_command(std::process::Command::new(&self.program))
|
||||
.expect("failed to parse rustc version info")
|
||||
.host
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub enum Mode {
|
||||
// The test passes a full execution of the rustc driver
|
||||
Pass,
|
||||
// The rustc driver panicked
|
||||
Panic,
|
||||
// The rustc driver emitted an error
|
||||
Fail,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
fn ok(self, status: ExitStatus) -> Errors {
|
||||
match (status.code(), self) {
|
||||
(Some(1), Mode::Fail) | (Some(101), Mode::Panic) | (Some(0), Mode::Pass) => vec![],
|
||||
_ => vec![Error::ExitStatus(self, status)],
|
||||
}
|
||||
}
|
||||
}
|
@ -1,327 +0,0 @@
|
||||
use std::path::Path;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
use crate::rustc_stderr::Level;
|
||||
|
||||
use color_eyre::eyre::{bail, ensure, eyre, Result};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
/// This crate supports various magic comments that get parsed as file-specific
|
||||
/// configuration values. This struct parses them all in one go and then they
|
||||
/// get processed by their respective use sites.
|
||||
#[derive(Default, Debug)]
|
||||
pub(crate) struct Comments {
|
||||
/// List of revision names to execute. Can only be speicified once
|
||||
pub revisions: Option<Vec<String>>,
|
||||
/// Don't run this test if any of these filters apply
|
||||
pub ignore: Vec<Condition>,
|
||||
/// Only run this test if all of these filters apply
|
||||
pub only: Vec<Condition>,
|
||||
/// Generate one .stderr file per bit width, by prepending with `.64bit` and similar
|
||||
pub stderr_per_bitwidth: bool,
|
||||
/// Additional flags to pass to the executable
|
||||
pub compile_flags: Vec<String>,
|
||||
/// Additional env vars to set for the executable
|
||||
pub env_vars: Vec<(String, String)>,
|
||||
/// Normalizations to apply to the stderr output before emitting it to disk
|
||||
pub normalize_stderr: Vec<(Regex, String)>,
|
||||
/// An arbitrary pattern to look for in the stderr.
|
||||
pub error_pattern: Option<(Pattern, usize)>,
|
||||
pub error_matches: Vec<ErrorMatch>,
|
||||
/// Ignore diagnostics below this level.
|
||||
/// `None` means pick the lowest level from the `error_pattern`s.
|
||||
pub require_annotations_for_level: Option<Level>,
|
||||
}
|
||||
|
||||
/// The conditions used for "ignore" and "only" filters.
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum Condition {
|
||||
/// The given string must appear in the target.
|
||||
Target(String),
|
||||
/// Tests that the bitwidth is the given one.
|
||||
Bitwidth(u8),
|
||||
/// Tests that the target is the host.
|
||||
OnHost,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum Pattern {
|
||||
SubString(String),
|
||||
Regex(Regex),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ErrorMatch {
|
||||
pub pattern: Pattern,
|
||||
pub revision: Option<String>,
|
||||
pub level: Level,
|
||||
/// The line where the message was defined, for reporting issues with it (e.g. in case it wasn't found).
|
||||
pub definition_line: usize,
|
||||
/// The line this pattern is expecting to find a message in.
|
||||
pub line: usize,
|
||||
}
|
||||
|
||||
impl Condition {
|
||||
fn parse(c: &str) -> Result<Self> {
|
||||
if c == "on-host" {
|
||||
Ok(Condition::OnHost)
|
||||
} else if let Some(bits) = c.strip_suffix("bit") {
|
||||
let bits: u8 = bits.parse().map_err(|_err| {
|
||||
eyre!("invalid ignore/only filter ending in 'bit': {c:?} is not a valid bitwdith")
|
||||
})?;
|
||||
Ok(Condition::Bitwidth(bits))
|
||||
} else if let Some(target) = c.strip_prefix("target-") {
|
||||
Ok(Condition::Target(target.to_owned()))
|
||||
} else {
|
||||
Err(eyre!("invalid ignore/only condition {c:?}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Comments {
|
||||
pub(crate) fn parse_file(path: &Path) -> Result<Self> {
|
||||
let content = std::fs::read_to_string(path)?;
|
||||
Self::parse(path, &content)
|
||||
}
|
||||
|
||||
/// Parse comments in `content`.
|
||||
/// `path` is only used to emit diagnostics if parsing fails.
|
||||
pub(crate) fn parse(path: &Path, content: &str) -> Result<Self> {
|
||||
let mut this = Self::default();
|
||||
|
||||
let mut fallthrough_to = None; // The line that a `|` will refer to.
|
||||
for (l, line) in content.lines().enumerate() {
|
||||
let l = l + 1; // enumerate starts at 0, but line numbers start at 1
|
||||
this.parse_checked_line(l, &mut fallthrough_to, line).map_err(|err| {
|
||||
err.wrap_err(format!("{}:{l}: failed to parse annotation", path.display()))
|
||||
})?;
|
||||
}
|
||||
Ok(this)
|
||||
}
|
||||
|
||||
fn parse_checked_line(
|
||||
&mut self,
|
||||
l: usize,
|
||||
fallthrough_to: &mut Option<usize>,
|
||||
line: &str,
|
||||
) -> Result<()> {
|
||||
if let Some((_, command)) = line.split_once("//@") {
|
||||
self.parse_command(command.trim(), l)
|
||||
} else if let Some((_, pattern)) = line.split_once("//~") {
|
||||
self.parse_pattern(pattern, fallthrough_to, l)
|
||||
} else if let Some((_, pattern)) = line.split_once("//[") {
|
||||
self.parse_revisioned_pattern(pattern, fallthrough_to, l)
|
||||
} else {
|
||||
*fallthrough_to = None;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_command(&mut self, command: &str, l: usize) -> Result<()> {
|
||||
// Commands are letters or dashes, grab everything until the first character that is neither of those.
|
||||
let (command, args) =
|
||||
match command.chars().position(|c: char| !c.is_alphanumeric() && c != '-') {
|
||||
None => (command, ""),
|
||||
Some(i) => {
|
||||
let (command, args) = command.split_at(i);
|
||||
let mut args = args.chars();
|
||||
// Commands are separated from their arguments by ':' or ' '
|
||||
let next = args
|
||||
.next()
|
||||
.expect("the `position` above guarantees that there is at least one char");
|
||||
ensure!(next == ':', "test command must be followed by : (or end the line)");
|
||||
(command, args.as_str().trim())
|
||||
}
|
||||
};
|
||||
|
||||
match command {
|
||||
"revisions" => {
|
||||
ensure!(self.revisions.is_none(), "cannot specifiy revisions twice");
|
||||
self.revisions = Some(args.split_whitespace().map(|s| s.to_string()).collect());
|
||||
}
|
||||
"compile-flags" => {
|
||||
self.compile_flags.extend(args.split_whitespace().map(|s| s.to_string()));
|
||||
}
|
||||
"rustc-env" =>
|
||||
for env in args.split_whitespace() {
|
||||
let (k, v) = env.split_once('=').ok_or_else(|| {
|
||||
eyre!("environment variables must be key/value pairs separated by a `=`")
|
||||
})?;
|
||||
self.env_vars.push((k.to_string(), v.to_string()));
|
||||
},
|
||||
"normalize-stderr-test" => {
|
||||
/// Parses a string literal. `s` has to start with `"`; everything until the next `"` is
|
||||
/// returned in the first component. `\` can be used to escape arbitrary character.
|
||||
/// Second return component is the rest of the string with leading whitespace removed.
|
||||
fn parse_str(s: &str) -> Result<(&str, &str)> {
|
||||
let mut chars = s.char_indices();
|
||||
match chars.next().ok_or_else(|| eyre!("missing arguments"))?.1 {
|
||||
'"' => {
|
||||
let s = chars.as_str();
|
||||
let mut escaped = false;
|
||||
for (i, c) in chars {
|
||||
if escaped {
|
||||
// Accept any character as literal after a `\`.
|
||||
escaped = false;
|
||||
} else if c == '"' {
|
||||
return Ok((&s[..(i - 1)], s[i..].trim_start()));
|
||||
} else {
|
||||
escaped = c == '\\';
|
||||
}
|
||||
}
|
||||
bail!("no closing quotes found for {s}")
|
||||
}
|
||||
c => bail!("expected '\"', got {c}"),
|
||||
}
|
||||
}
|
||||
|
||||
let (from, rest) = parse_str(args)?;
|
||||
|
||||
let to = rest.strip_prefix("->").ok_or_else(|| {
|
||||
eyre!("normalize-stderr-test needs a pattern and replacement separated by `->`")
|
||||
})?.trim_start();
|
||||
let (to, rest) = parse_str(to)?;
|
||||
|
||||
ensure!(rest.is_empty(), "trailing text after pattern replacement: {rest}");
|
||||
|
||||
let from = Regex::new(from)?;
|
||||
self.normalize_stderr.push((from, to.to_string()));
|
||||
}
|
||||
"error-pattern" => {
|
||||
ensure!(
|
||||
self.error_pattern.is_none(),
|
||||
"cannot specifiy error_pattern twice, previous: {:?}",
|
||||
self.error_pattern
|
||||
);
|
||||
self.error_pattern = Some((Pattern::parse(args.trim())?, l));
|
||||
}
|
||||
"stderr-per-bitwidth" => {
|
||||
// args are ignored (can be used as comment)
|
||||
ensure!(!self.stderr_per_bitwidth, "cannot specifiy stderr-per-bitwidth twice");
|
||||
self.stderr_per_bitwidth = true;
|
||||
}
|
||||
"require-annotations-for-level" => {
|
||||
ensure!(
|
||||
self.require_annotations_for_level.is_none(),
|
||||
"cannot specify `require-annotations-for-level` twice"
|
||||
);
|
||||
self.require_annotations_for_level = Some(args.trim().parse()?);
|
||||
}
|
||||
command => {
|
||||
if let Some(s) = command.strip_prefix("ignore-") {
|
||||
// args are ignored (can be sue as comment)
|
||||
self.ignore.push(Condition::parse(s)?);
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if let Some(s) = command.strip_prefix("only-") {
|
||||
// args are ignored (can be sue as comment)
|
||||
self.only.push(Condition::parse(s)?);
|
||||
return Ok(());
|
||||
}
|
||||
bail!("unknown command {command}");
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn parse_pattern(
|
||||
&mut self,
|
||||
pattern: &str,
|
||||
fallthrough_to: &mut Option<usize>,
|
||||
l: usize,
|
||||
) -> Result<()> {
|
||||
self.parse_pattern_inner(pattern, fallthrough_to, None, l)
|
||||
}
|
||||
|
||||
fn parse_revisioned_pattern(
|
||||
&mut self,
|
||||
pattern: &str,
|
||||
fallthrough_to: &mut Option<usize>,
|
||||
l: usize,
|
||||
) -> Result<()> {
|
||||
let (revision, pattern) =
|
||||
pattern.split_once(']').ok_or_else(|| eyre!("`//[` without corresponding `]`"))?;
|
||||
if let Some(pattern) = pattern.strip_prefix('~') {
|
||||
self.parse_pattern_inner(pattern, fallthrough_to, Some(revision.to_owned()), l)
|
||||
} else {
|
||||
bail!("revisioned pattern must have `~` following the `]`");
|
||||
}
|
||||
}
|
||||
|
||||
// parse something like (?P<offset>\||[\^]+)? *(?P<level>ERROR|HELP|WARN|NOTE): (?P<text>.*)
|
||||
fn parse_pattern_inner(
|
||||
&mut self,
|
||||
pattern: &str,
|
||||
fallthrough_to: &mut Option<usize>,
|
||||
revision: Option<String>,
|
||||
l: usize,
|
||||
) -> Result<()> {
|
||||
let (match_line, pattern) =
|
||||
match pattern.chars().next().ok_or_else(|| eyre!("no pattern specified"))? {
|
||||
'|' =>
|
||||
(
|
||||
*fallthrough_to
|
||||
.as_mut()
|
||||
.ok_or_else(|| eyre!("`//~|` pattern without preceding line"))?,
|
||||
&pattern[1..],
|
||||
),
|
||||
'^' => {
|
||||
let offset = pattern.chars().take_while(|&c| c == '^').count();
|
||||
(l - offset, &pattern[offset..])
|
||||
}
|
||||
_ => (l, pattern),
|
||||
};
|
||||
|
||||
let pattern = pattern.trim_start();
|
||||
let offset = pattern
|
||||
.chars()
|
||||
.position(|c| !matches!(c, 'A'..='Z' | 'a'..='z'))
|
||||
.ok_or_else(|| eyre!("pattern without level"))?;
|
||||
|
||||
let level = pattern[..offset].parse()?;
|
||||
let pattern = &pattern[offset..];
|
||||
let pattern = pattern.strip_prefix(':').ok_or_else(|| eyre!("no `:` after level found"))?;
|
||||
|
||||
let pattern = pattern.trim();
|
||||
|
||||
ensure!(!pattern.is_empty(), "no pattern specified");
|
||||
|
||||
let pattern = Pattern::parse(pattern)?;
|
||||
|
||||
*fallthrough_to = Some(match_line);
|
||||
|
||||
self.error_matches.push(ErrorMatch {
|
||||
pattern,
|
||||
revision,
|
||||
level,
|
||||
definition_line: l,
|
||||
line: match_line,
|
||||
});
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern {
|
||||
pub(crate) fn matches(&self, message: &str) -> bool {
|
||||
match self {
|
||||
Pattern::SubString(s) => message.contains(s),
|
||||
Pattern::Regex(r) => r.is_match(message),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn parse(pattern: &str) -> Result<Self> {
|
||||
if let Some(pattern) = pattern.strip_prefix('/') {
|
||||
let regex =
|
||||
pattern.strip_suffix('/').ok_or_else(|| eyre!("regex must end with `/`"))?;
|
||||
Ok(Pattern::Regex(Regex::new(regex)?))
|
||||
} else {
|
||||
Ok(Pattern::SubString(pattern.to_string()))
|
||||
}
|
||||
}
|
||||
}
|
@ -1,85 +0,0 @@
|
||||
use std::path::Path;
|
||||
|
||||
use crate::parser::Pattern;
|
||||
|
||||
use super::Comments;
|
||||
|
||||
#[test]
|
||||
fn parse_simple_comment() {
|
||||
let s = r"
|
||||
use std::mem;
|
||||
|
||||
fn main() {
|
||||
let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address $HEX is unallocated)
|
||||
}
|
||||
";
|
||||
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
|
||||
println!("parsed comments: {:#?}", comments);
|
||||
assert_eq!(comments.error_matches[0].definition_line, 5);
|
||||
assert_eq!(comments.error_matches[0].revision, None);
|
||||
match &comments.error_matches[0].pattern {
|
||||
Pattern::SubString(s) =>
|
||||
assert_eq!(s, "encountered a dangling reference (address $HEX is unallocated)"),
|
||||
other => panic!("expected substring, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_missing_level() {
|
||||
let s = r"
|
||||
use std::mem;
|
||||
|
||||
fn main() {
|
||||
let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ encountered a dangling reference (address $HEX is unallocated)
|
||||
}
|
||||
";
|
||||
assert!(Comments::parse(Path::new("<dummy>"), s).is_err(), "expected parsing to fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_slash_slash_at() {
|
||||
let s = r"
|
||||
//@ error-pattern: foomp
|
||||
use std::mem;
|
||||
|
||||
";
|
||||
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
|
||||
println!("parsed comments: {:#?}", comments);
|
||||
let pat = comments.error_pattern.unwrap();
|
||||
assert_eq!(format!("{:?}", pat.0), r#"SubString("foomp")"#);
|
||||
assert_eq!(pat.1, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_regex_error_pattern() {
|
||||
let s = r"
|
||||
//@ error-pattern: /foomp/
|
||||
use std::mem;
|
||||
|
||||
";
|
||||
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
|
||||
println!("parsed comments: {:#?}", comments);
|
||||
let pat = comments.error_pattern.unwrap();
|
||||
assert_eq!(format!("{:?}", pat.0), r#"Regex(foomp)"#);
|
||||
assert_eq!(pat.1, 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_slash_slash_at_fail() {
|
||||
let s = r"
|
||||
//@ error-patttern foomp
|
||||
use std::mem;
|
||||
|
||||
";
|
||||
assert!(Comments::parse(Path::new("<dummy>"), s).is_err(), "expected parsing to fail");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_colon_fail() {
|
||||
let s = r"
|
||||
//@stderr-per-bitwidth hello
|
||||
use std::mem;
|
||||
|
||||
";
|
||||
assert!(Comments::parse(Path::new("<dummy>"), s).is_err(), "expected parsing to fail");
|
||||
}
|
@ -1,155 +0,0 @@
|
||||
use std::{
|
||||
fmt::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use color_eyre::eyre::{eyre, Error};
|
||||
use regex::Regex;
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct RustcMessage {
|
||||
rendered: Option<String>,
|
||||
spans: Vec<Span>,
|
||||
level: String,
|
||||
message: String,
|
||||
children: Vec<RustcMessage>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialOrd, Ord, PartialEq, Eq)]
|
||||
pub(crate) enum Level {
|
||||
Ice = 5,
|
||||
Error = 4,
|
||||
Warn = 3,
|
||||
Help = 2,
|
||||
Note = 1,
|
||||
/// Only used for "For more information about this error, try `rustc --explain EXXXX`".
|
||||
FailureNote = 0,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Message {
|
||||
pub(crate) level: Level,
|
||||
pub(crate) message: String,
|
||||
}
|
||||
|
||||
/// Information about macro expansion.
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Expansion {
|
||||
span: Span,
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize, Debug)]
|
||||
struct Span {
|
||||
line_start: usize,
|
||||
file_name: PathBuf,
|
||||
expansion: Option<Box<Expansion>>,
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Level {
|
||||
type Err = Error;
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s {
|
||||
"ERROR" | "error" => Ok(Self::Error),
|
||||
"WARN" | "warning" => Ok(Self::Warn),
|
||||
"HELP" | "help" => Ok(Self::Help),
|
||||
"NOTE" | "note" => Ok(Self::Note),
|
||||
"failure-note" => Ok(Self::FailureNote),
|
||||
"error: internal compiler error" => Ok(Self::Ice),
|
||||
_ => Err(eyre!("unknown level `{s}`")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Diagnostics {
|
||||
/// Rendered and concatenated version of all diagnostics.
|
||||
/// This is equivalent to non-json diagnostics.
|
||||
pub rendered: String,
|
||||
/// Per line, a list of messages for that line.
|
||||
pub messages: Vec<Vec<Message>>,
|
||||
/// Messages not on any line (usually because they are from libstd)
|
||||
pub messages_from_unknown_file_or_line: Vec<Message>,
|
||||
}
|
||||
|
||||
impl RustcMessage {
|
||||
fn line(&self, file: &Path) -> Option<usize> {
|
||||
self.spans.iter().find_map(|span| span.line(file))
|
||||
}
|
||||
|
||||
/// Put the message and its children into the line-indexed list.
|
||||
fn insert_recursive(
|
||||
self,
|
||||
file: &Path,
|
||||
messages: &mut Vec<Vec<Message>>,
|
||||
messages_from_unknown_file_or_line: &mut Vec<Message>,
|
||||
line: Option<usize>,
|
||||
) {
|
||||
let line = self.line(file).or(line);
|
||||
let msg = Message { level: self.level.parse().unwrap(), message: self.message };
|
||||
if let Some(line) = line {
|
||||
if messages.len() <= line {
|
||||
messages.resize_with(line + 1, Vec::new);
|
||||
}
|
||||
messages[line].push(msg);
|
||||
// All other messages go into the general bin, unless they are specifically of the
|
||||
// "aborting due to X previous errors" variety, as we never want to match those. They
|
||||
// only count the number of errors and provide no useful information about the tests.
|
||||
} else if !(msg.message.starts_with("aborting due to")
|
||||
&& msg.message.contains("previous error"))
|
||||
{
|
||||
messages_from_unknown_file_or_line.push(msg);
|
||||
}
|
||||
for child in self.children {
|
||||
child.insert_recursive(file, messages, messages_from_unknown_file_or_line, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Span {
|
||||
/// Returns a line number *in the given file*, if possible.
|
||||
fn line(&self, file: &Path) -> Option<usize> {
|
||||
if self.file_name == file {
|
||||
Some(self.line_start)
|
||||
} else {
|
||||
self.expansion.as_ref()?.span.line(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn filter_annotations_from_rendered(rendered: &str) -> std::borrow::Cow<'_, str> {
|
||||
let annotations = Regex::new(r" *//(\[[a-z,]+\])?~.*").unwrap();
|
||||
annotations.replace_all(rendered, "")
|
||||
}
|
||||
|
||||
pub(crate) fn process(file: &Path, stderr: &[u8]) -> Diagnostics {
|
||||
let stderr = std::str::from_utf8(stderr).unwrap();
|
||||
let mut rendered = String::new();
|
||||
let mut messages = vec![];
|
||||
let mut messages_from_unknown_file_or_line = vec![];
|
||||
for line in stderr.lines() {
|
||||
if line.starts_with('{') {
|
||||
match serde_json::from_str::<RustcMessage>(line) {
|
||||
Ok(msg) => {
|
||||
write!(
|
||||
rendered,
|
||||
"{}",
|
||||
filter_annotations_from_rendered(msg.rendered.as_ref().unwrap())
|
||||
)
|
||||
.unwrap();
|
||||
msg.insert_recursive(
|
||||
file,
|
||||
&mut messages,
|
||||
&mut messages_from_unknown_file_or_line,
|
||||
None,
|
||||
);
|
||||
}
|
||||
Err(err) =>
|
||||
panic!("failed to parse rustc JSON output at line: {}\nerr:{}", line, err),
|
||||
}
|
||||
} else {
|
||||
// FIXME: do we want to throw interpreter stderr into a separate file?
|
||||
writeln!(rendered, "{}", line).unwrap();
|
||||
}
|
||||
}
|
||||
Diagnostics { rendered, messages, messages_from_unknown_file_or_line }
|
||||
}
|
@ -1,294 +0,0 @@
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use crate::rustc_stderr::Level;
|
||||
use crate::rustc_stderr::Message;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn config() -> Config {
|
||||
Config {
|
||||
args: vec![],
|
||||
target: None,
|
||||
stderr_filters: vec![],
|
||||
stdout_filters: vec![],
|
||||
root_dir: PathBuf::from("$RUSTROOT"),
|
||||
mode: Mode::Fail,
|
||||
path_filter: vec![],
|
||||
program: PathBuf::from("cake"),
|
||||
output_conflict_handling: OutputConflictHandling::Error,
|
||||
dependencies_crate_manifest_path: None,
|
||||
dependency_builder: None,
|
||||
quiet: false,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn issue_2156() {
|
||||
let s = r"
|
||||
use std::mem;
|
||||
|
||||
fn main() {
|
||||
let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address $HEX is unallocated)
|
||||
}
|
||||
";
|
||||
let path = Path::new("$DIR/<dummy>");
|
||||
let comments = Comments::parse(path, s).unwrap();
|
||||
let mut errors = vec![];
|
||||
let config = config();
|
||||
let messages = vec![
|
||||
vec![], vec![], vec![], vec![], vec![],
|
||||
vec![
|
||||
Message {
|
||||
message:"Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(),
|
||||
level: Level::Error,
|
||||
}
|
||||
]
|
||||
];
|
||||
check_annotations(messages, vec![], Path::new("moobar"), &mut errors, &config, "", &comments);
|
||||
match &errors[..] {
|
||||
[
|
||||
Error::PatternNotFound { definition_line: 5, .. },
|
||||
Error::ErrorsWithoutPattern { path: Some((_, 5)), .. },
|
||||
] => {}
|
||||
_ => panic!("{:#?}", errors),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn find_pattern() {
|
||||
let s = r"
|
||||
use std::mem;
|
||||
|
||||
fn main() {
|
||||
let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address 0x10 is unallocated)
|
||||
}
|
||||
";
|
||||
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
|
||||
let config = config();
|
||||
{
|
||||
let messages = vec![vec![], vec![], vec![], vec![], vec![], vec![
|
||||
Message {
|
||||
message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(),
|
||||
level: Level::Error,
|
||||
}
|
||||
]
|
||||
];
|
||||
let mut errors = vec![];
|
||||
check_annotations(
|
||||
messages,
|
||||
vec![],
|
||||
Path::new("moobar"),
|
||||
&mut errors,
|
||||
&config,
|
||||
"",
|
||||
&comments,
|
||||
);
|
||||
match &errors[..] {
|
||||
[] => {}
|
||||
_ => panic!("{:#?}", errors),
|
||||
}
|
||||
}
|
||||
|
||||
// only difference to above is a wrong line number
|
||||
{
|
||||
let messages = vec![vec![], vec![], vec![], vec![], vec![
|
||||
Message {
|
||||
message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(),
|
||||
level: Level::Error,
|
||||
}
|
||||
]
|
||||
];
|
||||
let mut errors = vec![];
|
||||
check_annotations(
|
||||
messages,
|
||||
vec![],
|
||||
Path::new("moobar"),
|
||||
&mut errors,
|
||||
&config,
|
||||
"",
|
||||
&comments,
|
||||
);
|
||||
match &errors[..] {
|
||||
[
|
||||
Error::PatternNotFound { definition_line: 5, .. },
|
||||
Error::ErrorsWithoutPattern { path: Some((_, 4)), .. },
|
||||
] => {}
|
||||
_ => panic!("not the expected error: {:#?}", errors),
|
||||
}
|
||||
}
|
||||
|
||||
// only difference to first is a wrong level
|
||||
{
|
||||
let messages = vec![
|
||||
vec![], vec![], vec![], vec![], vec![],
|
||||
vec![
|
||||
Message {
|
||||
message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(),
|
||||
level: Level::Note,
|
||||
}
|
||||
]
|
||||
];
|
||||
let mut errors = vec![];
|
||||
check_annotations(
|
||||
messages,
|
||||
vec![],
|
||||
Path::new("moobar"),
|
||||
&mut errors,
|
||||
&config,
|
||||
"",
|
||||
&comments,
|
||||
);
|
||||
match &errors[..] {
|
||||
// Note no `ErrorsWithoutPattern`, because there are no `//~NOTE` in the test file, so we ignore them
|
||||
[Error::PatternNotFound { definition_line: 5, .. }] => {}
|
||||
_ => panic!("not the expected error: {:#?}", errors),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duplicate_pattern() {
|
||||
let s = r"
|
||||
use std::mem;
|
||||
|
||||
fn main() {
|
||||
let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address 0x10 is unallocated)
|
||||
//~^ ERROR: encountered a dangling reference (address 0x10 is unallocated)
|
||||
}
|
||||
";
|
||||
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
|
||||
let config = config();
|
||||
let messages = vec![
|
||||
vec![], vec![], vec![], vec![], vec![],
|
||||
vec![
|
||||
Message {
|
||||
message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(),
|
||||
level: Level::Error,
|
||||
}
|
||||
]
|
||||
];
|
||||
let mut errors = vec![];
|
||||
check_annotations(messages, vec![], Path::new("moobar"), &mut errors, &config, "", &comments);
|
||||
match &errors[..] {
|
||||
[Error::PatternNotFound { definition_line: 6, .. }] => {}
|
||||
_ => panic!("{:#?}", errors),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_pattern() {
|
||||
let s = r"
|
||||
use std::mem;
|
||||
|
||||
fn main() {
|
||||
let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address 0x10 is unallocated)
|
||||
}
|
||||
";
|
||||
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
|
||||
let config = config();
|
||||
let messages = vec![
|
||||
vec![], vec![], vec![], vec![], vec![],
|
||||
vec![
|
||||
Message {
|
||||
message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(),
|
||||
level: Level::Error,
|
||||
},
|
||||
Message {
|
||||
message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(),
|
||||
level: Level::Error,
|
||||
}
|
||||
]
|
||||
];
|
||||
let mut errors = vec![];
|
||||
check_annotations(messages, vec![], Path::new("moobar"), &mut errors, &config, "", &comments);
|
||||
match &errors[..] {
|
||||
[Error::ErrorsWithoutPattern { path: Some((_, 5)), .. }] => {}
|
||||
_ => panic!("{:#?}", errors),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_warn_pattern() {
|
||||
let s = r"
|
||||
use std::mem;
|
||||
|
||||
fn main() {
|
||||
let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address 0x10 is unallocated)
|
||||
//~^ WARN: cake
|
||||
}
|
||||
";
|
||||
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
|
||||
let config = config();
|
||||
let messages= vec![
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
Message {
|
||||
message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(),
|
||||
level: Level::Error,
|
||||
},
|
||||
Message {
|
||||
message: "kaboom".to_string(),
|
||||
level: Level::Warn,
|
||||
},
|
||||
Message {
|
||||
message: "cake".to_string(),
|
||||
level: Level::Warn,
|
||||
},
|
||||
],
|
||||
];
|
||||
let mut errors = vec![];
|
||||
check_annotations(messages, vec![], Path::new("moobar"), &mut errors, &config, "", &comments);
|
||||
match &errors[..] {
|
||||
[Error::ErrorsWithoutPattern { path: Some((_, 5)), msgs, .. }] =>
|
||||
match &msgs[..] {
|
||||
[Message { message, level: Level::Warn }] if message == "kaboom" => {}
|
||||
_ => panic!("{:#?}", msgs),
|
||||
},
|
||||
_ => panic!("{:#?}", errors),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_implicit_warn_pattern() {
|
||||
let s = r"
|
||||
use std::mem;
|
||||
//@require-annotations-for-level: ERROR
|
||||
fn main() {
|
||||
let _x: &i32 = unsafe { mem::transmute(16usize) }; //~ ERROR: encountered a dangling reference (address 0x10 is unallocated)
|
||||
//~^ WARN: cake
|
||||
}
|
||||
";
|
||||
let comments = Comments::parse(Path::new("<dummy>"), s).unwrap();
|
||||
let config = config();
|
||||
let messages = vec![
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![],
|
||||
vec![
|
||||
Message {
|
||||
message: "Undefined Behavior: type validation failed: encountered a dangling reference (address 0x10 is unallocated)".to_string(),
|
||||
level: Level::Error,
|
||||
},
|
||||
Message {
|
||||
message: "kaboom".to_string(),
|
||||
level: Level::Warn,
|
||||
},
|
||||
Message {
|
||||
message: "cake".to_string(),
|
||||
level: Level::Warn,
|
||||
},
|
||||
],
|
||||
];
|
||||
let mut errors = vec![];
|
||||
check_annotations(messages, vec![], Path::new("moobar"), &mut errors, &config, "", &comments);
|
||||
match &errors[..] {
|
||||
[] => {}
|
||||
_ => panic!("{:#?}", errors),
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user