diff --git a/.github/workflows/testsuite_preview.yaml b/.github/workflows/testsuite_preview.yaml
new file mode 100644
index 0000000000..0fd2ab00a6
--- /dev/null
+++ b/.github/workflows/testsuite_preview.yaml
@@ -0,0 +1,102 @@
+name: Testsuite Preview
+
+on:
+ - pull_request_target
+
+permissions:
+ pull-requests: write
+ contents: read
+
+jobs:
+ generate-base-report:
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ runs-on: ${{ matrix.os }}
+ env:
+ RUSTFLAGS: "-D warnings"
+ RUSTDOCFLAGS: "-D warnings"
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: ${{ github.event.pull_request.base.ref }}
+ submodules: true
+ repository: ${{ github.event.pull_request.base.repo.full_name }}
+
+ - name: Generate old report
+ run: |
+ TESTSUITE_SAVE=1 cargo test -- spec_tests --show-output
+ cp testsuite_results.json old.json || :
+
+ - name: Upload Base Report
+ uses: actions/upload-artifact@v4
+ with:
+ name: base-report
+ path: old.json
+ retention-days: 1
+
+ - run: cargo clean
+
+ generate-head-report:
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ runs-on: ${{ matrix.os }}
+ env:
+ RUSTFLAGS: "-D warnings"
+ RUSTDOCFLAGS: "-D warnings"
+
+ steps:
+ - uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ ref: ${{ github.event.pull_request.head.ref }}
+ submodules: true
+ repository: ${{ github.event.pull_request.head.repo.full_name }}
+
+ - name: Generate new report
+ run: |
+ TESTSUITE_SAVE=1 cargo test -- spec_tests --show-output
+ cp testsuite_results.json new.json || :
+
+ - name: Upload Head Report
+ uses: actions/upload-artifact@v4
+ with:
+ name: head-report
+ path: new.json
+ retention-days: 1
+
+ - run: cargo clean
+
+ compare-reports:
+ needs: [generate-base-report, generate-head-report]
+ strategy:
+ matrix:
+ os: [ubuntu-latest]
+ runs-on: ${{ matrix.os }}
+
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Download Base Report
+ continue-on-error: true
+ uses: actions/download-artifact@v4
+ with:
+ name: base-report
+
+ - name: Download Head Report
+ continue-on-error: true
+ uses: actions/download-artifact@v4
+ with:
+ name: head-report
+
+ - name: Compare reports
+ run: python3 ./ci-tools/compare_testsuite.py old.json new.json > testsuite_report.md
+
+ - name: Sticky Pull Request Comment
+ uses: marocchino/sticky-pull-request-comment@v2.9.1
+ with:
+ header: testsuite
+ path: testsuite_report.md
diff --git a/.gitignore b/.gitignore
index 637468b741..27354775fc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@
/target
result
whitepaper/main.pdf
+testsuite_results.json
diff --git a/Cargo.lock b/Cargo.lock
index 4a0ea52408..eeaa377275 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4,9 +4,9 @@ version = 3
[[package]]
name = "aho-corasick"
-version = "1.1.2"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b2969dcb958b36655471fc61f7e416fa76033bdd4bfed0678d8fee1e2d07a1f0"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
@@ -17,29 +17,73 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
+[[package]]
+name = "anstream"
+version = "0.6.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
+dependencies = [
+ "anstyle",
+ "anstyle-parse",
+ "anstyle-query",
+ "anstyle-wincon",
+ "colorchoice",
+ "is_terminal_polyfill",
+ "utf8parse",
+]
+
[[package]]
name = "anstyle"
-version = "1.0.7"
+version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
+checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
+
+[[package]]
+name = "anstyle-parse"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
+dependencies = [
+ "utf8parse",
+]
+
+[[package]]
+name = "anstyle-query"
+version = "1.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
+dependencies = [
+ "windows-sys",
+]
+
+[[package]]
+name = "anstyle-wincon"
+version = "3.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
+dependencies = [
+ "anstyle",
+ "once_cell",
+ "windows-sys",
+]
[[package]]
name = "autocfg"
-version = "1.3.0"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
-version = "2.4.2"
+version = "2.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
+checksum = "8f68f53c83ab957f72c32642f3868eec03eb974d1fb82e453128456482613d36"
[[package]]
name = "bumpalo"
-version = "3.15.3"
+version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8ea184aa71bb362a1157c896979544cc23974e08fd265f29ea96b59f0b4a555b"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "cast"
@@ -82,18 +126,18 @@ dependencies = [
[[package]]
name = "clap"
-version = "4.5.7"
+version = "4.5.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f"
+checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff"
dependencies = [
"clap_builder",
]
[[package]]
name = "clap_builder"
-version = "4.5.7"
+version = "4.5.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f"
+checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
dependencies = [
"anstyle",
"clap_lex",
@@ -101,9 +145,15 @@ dependencies = [
[[package]]
name = "clap_lex"
-version = "0.7.1"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
+checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
+
+[[package]]
+name = "colorchoice"
+version = "1.0.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
[[package]]
name = "criterion"
@@ -143,9 +193,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
-version = "0.8.5"
+version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
@@ -162,27 +212,27 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.20"
+version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
+checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
[[package]]
name = "either"
-version = "1.10.0"
+version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a"
+checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "env_filter"
-version = "0.1.0"
+version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a009aa4810eb158359dda09d0c87378e4bbb89b5a801f016885a4707ba24f7ea"
+checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
dependencies = [
"log",
]
@@ -202,10 +252,12 @@ dependencies = [
[[package]]
name = "env_logger"
-version = "0.11.2"
+version = "0.11.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6c012a26a7f605efc424dd53697843a72be7dc86ad2d01f7814337794a12231d"
+checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0"
dependencies = [
+ "anstream",
+ "anstyle",
"env_filter",
"log",
]
@@ -228,15 +280,15 @@ dependencies = [
[[package]]
name = "hashbrown"
-version = "0.14.3"
+version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
+checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "hermit-abi"
-version = "0.3.6"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bd5256b483761cd23699d0da46cc6fd2ee3be420bbe6d020ae4a091e70b7e9fd"
+checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc"
[[package]]
name = "hexf"
@@ -262,9 +314,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "indexmap"
-version = "2.2.3"
+version = "2.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177"
+checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652"
dependencies = [
"equivalent",
"hashbrown",
@@ -272,15 +324,21 @@ dependencies = [
[[package]]
name = "is-terminal"
-version = "0.4.12"
+version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b"
+checksum = "e19b23d53f35ce9f56aebc7d1bb4e6ac1e9c0db7ac85c8d1760c04379edced37"
dependencies = [
"hermit-abi",
"libc",
"windows-sys",
]
+[[package]]
+name = "is_terminal_polyfill"
+version = "1.70.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
+
[[package]]
name = "itertools"
version = "0.10.5"
@@ -301,25 +359,38 @@ dependencies = [
[[package]]
name = "itoa"
-version = "1.0.11"
+version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
+checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674"
[[package]]
name = "js-sys"
-version = "0.3.69"
+version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
dependencies = [
+ "once_cell",
"wasm-bindgen",
]
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+
[[package]]
name = "leb128"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
+[[package]]
+name = "leb128fmt"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2"
+
[[package]]
name = "libc"
version = "0.2.169"
@@ -328,9 +399,9 @@ checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libm"
-version = "0.2.8"
+version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058"
+checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa"
[[package]]
name = "log"
@@ -338,11 +409,30 @@ version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+[[package]]
+name = "matchers"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
+dependencies = [
+ "regex-automata 0.1.10",
+]
+
[[package]]
name = "memchr"
-version = "2.7.1"
+version = "2.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
+
+[[package]]
+name = "nu-ansi-term"
+version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
+checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
+dependencies = [
+ "overload",
+ "winapi",
+]
[[package]]
name = "num-traits"
@@ -355,21 +445,33 @@ dependencies = [
[[package]]
name = "once_cell"
-version = "1.19.0"
+version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]]
name = "oorandom"
-version = "11.1.3"
+version = "11.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575"
+checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9"
+
+[[package]]
+name = "overload"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
+
+[[package]]
+name = "pin-project-lite"
+version = "0.2.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "plotters"
-version = "0.3.6"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3"
+checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
dependencies = [
"num-traits",
"plotters-backend",
@@ -380,33 +482,33 @@ dependencies = [
[[package]]
name = "plotters-backend"
-version = "0.3.6"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7"
+checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
[[package]]
name = "plotters-svg"
-version = "0.3.6"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705"
+checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
dependencies = [
"plotters-backend",
]
[[package]]
name = "proc-macro2"
-version = "1.0.79"
+version = "1.0.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e"
+checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
-version = "1.0.35"
+version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef"
+checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
@@ -433,38 +535,59 @@ dependencies = [
[[package]]
name = "regex"
-version = "1.10.3"
+version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
dependencies = [
"aho-corasick",
"memchr",
- "regex-automata",
- "regex-syntax",
+ "regex-automata 0.4.9",
+ "regex-syntax 0.8.5",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
+dependencies = [
+ "regex-syntax 0.6.29",
]
[[package]]
name = "regex-automata"
-version = "0.4.5"
+version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5bb987efffd3c6d0d8f5f89510bb458559eab11e4f869acb20bf845e016259cd"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
dependencies = [
"aho-corasick",
"memchr",
- "regex-syntax",
+ "regex-syntax 0.8.5",
]
[[package]]
name = "regex-syntax"
-version = "0.8.2"
+version = "0.6.29"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c08c74e62047bb2de4ff487b251e4a92e24f48745648451635cec7d591162d9f"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "rustversion"
+version = "1.0.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]]
name = "ryu"
-version = "1.0.18"
+version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
+checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
[[package]]
name = "same-file"
@@ -477,41 +600,51 @@ dependencies = [
[[package]]
name = "semver"
-version = "1.0.22"
+version = "1.0.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca"
+checksum = "f79dfe2d285b0488816f30e700a7438c5a73d816b5b7d3ac72fbc48b0d185e03"
[[package]]
name = "serde"
-version = "1.0.203"
+version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
+checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
-version = "1.0.203"
+version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
+checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.52",
+ "syn 2.0.98",
]
[[package]]
name = "serde_json"
-version = "1.0.118"
+version = "1.0.138"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4"
+checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
dependencies = [
"itoa",
+ "memchr",
"ryu",
"serde",
]
+[[package]]
+name = "sharded-slab"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
+dependencies = [
+ "lazy_static",
+]
+
[[package]]
name = "syn"
version = "1.0.109"
@@ -525,9 +658,9 @@ dependencies = [
[[package]]
name = "syn"
-version = "2.0.52"
+version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b699d15b36d1f02c3e7c69f8ffef53de37aefae075d8488d4ba1a7788d574a07"
+checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [
"proc-macro2",
"quote",
@@ -545,23 +678,34 @@ dependencies = [
[[package]]
name = "test-log"
-version = "0.2.15"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7b319995299c65d522680decf80f2c108d85b861d81dfe340a10d16cee29d9e6"
+checksum = "e7f46083d221181166e5b6f6b1e5f1d499f3a76888826e6cb1d057554157cd0f"
dependencies = [
- "env_logger 0.11.2",
+ "env_logger 0.11.6",
"test-log-macros",
+ "tracing-subscriber",
]
[[package]]
name = "test-log-macros"
-version = "0.2.15"
+version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c8f546451eaa38373f549093fe9fd05e7d2bade739e2ddf834b9968621d60107"
+checksum = "888d0c3c6db53c0fdab160d2ed5e12ba745383d3e85813f2ea0f2b1475ab553f"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.52",
+ "syn 2.0.98",
+]
+
+[[package]]
+name = "thread_local"
+version = "1.1.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
+dependencies = [
+ "cfg-if",
+ "once_cell",
]
[[package]]
@@ -574,17 +718,83 @@ dependencies = [
"serde_json",
]
+[[package]]
+name = "tracing"
+version = "0.1.41"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
+dependencies = [
+ "pin-project-lite",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-core"
+version = "0.1.33"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c"
+dependencies = [
+ "once_cell",
+ "valuable",
+]
+
+[[package]]
+name = "tracing-log"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
+dependencies = [
+ "log",
+ "once_cell",
+ "tracing-core",
+]
+
+[[package]]
+name = "tracing-subscriber"
+version = "0.3.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008"
+dependencies = [
+ "matchers",
+ "nu-ansi-term",
+ "once_cell",
+ "regex",
+ "sharded-slab",
+ "thread_local",
+ "tracing",
+ "tracing-core",
+ "tracing-log",
+]
+
[[package]]
name = "unicode-ident"
-version = "1.0.12"
+version = "1.0.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
+
+[[package]]
+name = "unicode-width"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
+checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "unicode-width"
-version = "0.1.11"
+version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85"
+checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd"
+
+[[package]]
+name = "utf8parse"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
+
+[[package]]
+name = "valuable"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65"
[[package]]
name = "walkdir"
@@ -598,34 +808,35 @@ dependencies = [
[[package]]
name = "wasm-bindgen"
-version = "0.2.92"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
dependencies = [
"cfg-if",
+ "once_cell",
+ "rustversion",
"wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.92"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
dependencies = [
"bumpalo",
"log",
- "once_cell",
"proc-macro2",
"quote",
- "syn 2.0.52",
+ "syn 2.0.98",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.92"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@@ -633,39 +844,43 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.92"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
dependencies = [
"proc-macro2",
"quote",
- "syn 2.0.52",
+ "syn 2.0.98",
"wasm-bindgen-backend",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.92"
+version = "0.2.100"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
[[package]]
name = "wasm-encoder"
-version = "0.200.0"
+version = "0.212.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b9e3fb0c8fbddd78aa6095b850dfeedbc7506cf5f81e633f69cf8f2333ab84b9"
+checksum = "501940df4418b8929eb6d52f1aade1fdd15a5b86c92453cb696e3c906bd3fc33"
dependencies = [
"leb128",
]
[[package]]
name = "wasm-encoder"
-version = "0.212.0"
+version = "0.225.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "501940df4418b8929eb6d52f1aade1fdd15a5b86c92453cb696e3c906bd3fc33"
+checksum = "6f7eac0445cac73bcf09e6a97f83248d64356dccf9f2b100199769b6b42464e5"
dependencies = [
- "leb128",
+ "leb128fmt",
+ "wasmparser 0.225.0",
]
[[package]]
@@ -678,8 +893,10 @@ dependencies = [
"itertools 0.12.1",
"libm",
"log",
+ "serde",
+ "serde_json",
"test-log",
- "wasmparser",
+ "wasmparser 0.119.0",
"wast 212.0.0",
"wat",
]
@@ -696,16 +913,14 @@ dependencies = [
]
[[package]]
-name = "wast"
-version = "200.0.0"
+name = "wasmparser"
+version = "0.225.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d1810d14e6b03ebb8fb05eef4009ad5749c989b65197d83bce7de7172ed91366"
+checksum = "36e5456165f81e64cb9908a0fe9b9d852c2c74582aa3fe2be3c2da57f937d3ae"
dependencies = [
- "bumpalo",
- "leb128",
- "memchr",
- "unicode-width",
- "wasm-encoder 0.200.0",
+ "bitflags",
+ "indexmap",
+ "semver",
]
[[package]]
@@ -717,24 +932,37 @@ dependencies = [
"bumpalo",
"leb128",
"memchr",
- "unicode-width",
+ "unicode-width 0.1.14",
"wasm-encoder 0.212.0",
]
+[[package]]
+name = "wast"
+version = "225.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c61496027ff707f9fa9e0b22c34ec163eb7adb1070df565e32a9180a76e4300b"
+dependencies = [
+ "bumpalo",
+ "leb128fmt",
+ "memchr",
+ "unicode-width 0.2.0",
+ "wasm-encoder 0.225.0",
+]
+
[[package]]
name = "wat"
-version = "1.200.0"
+version = "1.225.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "776cbd10e217f83869beaa3f40e312bb9e91d5eee29bbf6f560db1261b6a4c3d"
+checksum = "89e72a33942234fd0794bcdac30e43b448de3187512414267678e511c6755f11"
dependencies = [
- "wast 200.0.0",
+ "wast 225.0.0",
]
[[package]]
name = "web-sys"
-version = "0.3.69"
+version = "0.3.77"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
dependencies = [
"js-sys",
"wasm-bindgen",
@@ -758,11 +986,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
-version = "0.1.6"
+version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f29e6f9198ba0d26b4c9f07dbe6f9ed633e1f3d5b8b414090084349e46a52596"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
- "winapi",
+ "windows-sys",
]
[[package]]
@@ -773,22 +1001,23 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
-version = "0.52.0"
+version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
-version = "0.52.3"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d380ba1dc7187569a8a9e91ed34b8ccfc33123bbacb8c0aed2d1ad7f3ef2dc5f"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
+ "windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
@@ -797,42 +1026,48 @@ dependencies = [
[[package]]
name = "windows_aarch64_gnullvm"
-version = "0.52.3"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "68e5dcfb9413f53afd9c8f86e56a7b4d86d9a2fa26090ea2dc9e40fba56c6ec6"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
-version = "0.52.3"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8dab469ebbc45798319e69eebf92308e541ce46760b49b18c6b3fe5e8965b30f"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
-version = "0.52.3"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2a4e9b6a7cac734a8b4138a4e1044eac3404d8326b6c0f939276560687a033fb"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
-version = "0.52.3"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "28b0ec9c422ca95ff34a78755cfa6ad4a51371da2a5ace67500cf7ca5f232c58"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
-version = "0.52.3"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "704131571ba93e89d7cd43482277d6632589b18ecf4468f591fbae0a8b101614"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
-version = "0.52.3"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "42079295511643151e98d61c38c0acc444e52dd42ab456f7ccfd5152e8ecf21c"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
-version = "0.52.3"
+version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0770833d60a970638e989b3fa9fd2bb1aaadcf88963d1659fd7d9990196ed2d6"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/Cargo.toml b/Cargo.toml
index e0103d720c..f038c90e59 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -31,6 +31,8 @@ wat = "1.0.83"
wast = "212.0.0"
criterion = { version = "0.5.1", features = ["html_reports"] }
hexf = "0.2.1"
+serde = { version = "1.0.217", features = ["derive"] }
+serde_json = "1.0.138"
[features]
default = ["hooks"]
diff --git a/ci-tools/compare_testsuite.py b/ci-tools/compare_testsuite.py
new file mode 100644
index 0000000000..e489309717
--- /dev/null
+++ b/ci-tools/compare_testsuite.py
@@ -0,0 +1,239 @@
+#!/usr/bin/env python3
+
+import json
+import sys
+import os.path
+
+
+def sanitize_filepath(path: str) -> str:
+ if path.startswith("./tests/specification/testsuite/"):
+ path = path[len("./tests/specification/testsuite/") :]
+ return path
+
+
+def sanatize_table_item(item: str) -> str:
+ if item is None:
+ return "-"
+ new = ""
+ new = item.replace("`", "'")
+ new = new.replace("|", "/")
+ new = new.replace("\n", " ")
+
+ return new
+
+
+def print_missing(original: bool, new: bool):
+ if not original and new:
+ print(
+ "Uh-oh! It looks like the original testsuite results do not exist! "
+ "This is likely due to the testsuite not being updated on the target branch. "
+ "This PR will fix this issue. No comparison possible. "
+ )
+ elif original and not new:
+ print(
+ "Uh-oh! It looks like this PR couldn't generate the testsuite results. "
+ "Please make sure this isn't indicative of a bigger error! "
+ )
+ elif not original and not new:
+ print(
+ "Uh-oh! It looks like the original testsuite results and the new results are both missing! "
+ "This is certainly indicative of an error in either the branches or this GH action! "
+ )
+
+
+def get_summary(entries) -> str:
+ """
+ Logic:
+ - For each entry
+ - If script error, print error
+ - If Assert:
+ - Count number of passing and failing asserts, calculate percentage
+ """
+ result = ""
+ result += "Click here to open
\n\n"
+ result += "| **File** | **Passed Asserts** | **Failed Asserts** | **% Passed** | **Notes** |\n"
+ result += "|:--------:|:------------------:|:------------------:|:------------:|-----------|\n"
+
+ for entry in entries:
+ file = sanitize_filepath(entry["filepath"])
+ result += f"| {file}"
+ if "Assert" in entry["data"]:
+ failed = 0
+ passed = 0
+ for an_assert in entry["data"]["Assert"]["results"]:
+ if an_assert["error"] is not None:
+ failed += 1
+ else:
+ passed += 1
+
+ total = failed + passed
+ if total != 0:
+ percent = round(passed / total * 100, 2)
+ else:
+ percent = 0
+ result += f"| {passed} / {total} | {failed} / {total} | {percent}% | - |\n"
+ else: # "ScriptError"
+ script_error = entry["data"]["ScriptError"]
+ result += f"| - | - | - |"
+ # result += f'Error: `{sanatize_table_item(script_error["error"])}`
'
+ result += f'Context: `{sanatize_table_item(script_error["context"])}`
'
+ result += f'Line: `{script_error["line_number"]}`
'
+ # result += f'Command: `{sanatize_table_item(script_error["command"])}`'
+ result += "|\n"
+
+ result += "\n\n \n"
+ return result
+
+
+def get_delta(old_entries, new_entries) -> str:
+ """
+ Delta cases:
+ 1. File deleted
+ - in old entries (?)
+ - in new entries (?)
+ 2. ScriptError
+ - in old but not in new (good)
+ - in new but not in old (bad)
+ 3. Asserts
+ - modified file/asserts (?)
+ - Error status:
+ i. Failing in old, Passing in new (good)
+ ii. Failing in new, Passing in old (bad)
+ iii. Some failing and some passing in new (good and bad = ?)
+ - (Basically a combination of case [i] and case [ii])
+ """
+ result = ""
+ header = ""
+ header += "| **File** | **Notes** | ❓ |\n"
+ header += "|:--------:|:---------:|:--:|\n"
+
+ def find_entry(haystack, filepath):
+ for entry in haystack:
+ if entry["filepath"] == filepath:
+ return entry
+ return None
+
+ def find_assert(haystack, line, command):
+ for an_assert in haystack:
+ if an_assert["line_number"] == line and an_assert["command"] == command:
+ return an_assert
+ return None
+
+ # Get reunion of entries
+ all_entries = list(old_entries)
+ for entry in new_entries:
+ if find_entry(old_entries, entry["filepath"]) is None:
+ all_entries.append(entry)
+
+ for entry in all_entries:
+ full_file = entry["filepath"]
+ file = sanitize_filepath(entry["filepath"])
+
+ old_entry = find_entry(old_entries, full_file)
+ new_entry = find_entry(new_entries, full_file)
+
+ if old_entry is not None and new_entry is not None:
+ # First, compare if script error
+ se_old = "ScriptError" in old_entry["data"]
+ se_new = "ScriptError" in new_entry["data"]
+
+ if se_old and not se_new:
+ result += f"| {file} | File now compiles | ✅ |\n"
+ elif not se_old and se_new:
+ result += f"| {file} | File no longer compiles | ❌ |\n"
+ elif not se_old and not se_new:
+ # Secondly, test if file is the same.
+ asserts_old = old_entry["data"]["Assert"]["results"]
+ asserts_new = new_entry["data"]["Assert"]["results"]
+ same_file_contents = len(asserts_old) == len(asserts_new) and all(
+ [
+ find_assert(asserts_new, old["line_number"], old["command"]) is not None
+ for old in asserts_old
+ ]
+ )
+
+ if not same_file_contents:
+ result += f"| {file} | File has changed. Cannot check | ⚠️ |\n"
+ else:
+ # Sort by line number
+ asserts_old = sorted(
+ asserts_old, key=lambda an_assert: an_assert["line_number"]
+ )
+ asserts_new = sorted(
+ asserts_new, key=lambda an_assert: an_assert["line_number"]
+ )
+
+ new_passing = 0
+ new_failing = 0
+ for i in range(len(asserts_old)):
+ old_is_err = asserts_old[i]["error"] is not None
+ new_is_err = asserts_new[i]["error"] is not None
+
+ if old_is_err and not new_is_err:
+ new_passing += 1
+ elif not old_is_err and new_is_err:
+ new_failing += 1
+
+ if new_passing != 0 or new_failing != 0:
+ result += f"| {file} | "
+ if new_passing != 0 and new_failing == 0:
+ result += f"+{new_passing} asserts PASS | ✅ |\n"
+ elif new_passing == 0 and new_failing != 0:
+ result += f"-{new_failing} asserts FAIL | ❌ |\n"
+ else:
+ result += f"+{new_passing} asserts PASS
-{new_failing} asserts FAIL | ⚠️ |\n"
+
+ else: # Script error both in old and new
+ pass
+ elif new_entry is None:
+ result += f"| {file} | File missing in this PR | ⚠️ |\n"
+ elif old_entry is None:
+ result += f"| {file} | File missing in target branch | ⚠️ |\n"
+
+ if result != "":
+ return header + result
+ else:
+ return " No changes detected. "
+
+
+def main():
+ if len(sys.argv) != 3:
+ print("Usage: script.py ")
+ sys.exit(1)
+
+ original_path = sys.argv[1]
+ new_path = sys.argv[2]
+
+ print("# 🗒️ Testsuite Report\n\n")
+
+ original_exists = os.path.isfile(original_path)
+ new_exists = os.path.isfile(new_path)
+ if not original_exists or not new_exists:
+ print_missing(original_exists, new_exists)
+ return
+
+ old_data = None
+ with open(original_path, "r") as f:
+ old_data = json.load(f)
+
+ new_data = None
+ with open(new_path, "r") as f:
+ new_data = json.load(f)
+
+ if original_exists and new_exists:
+ print("## PR delta: \n")
+ print(get_delta(old_data["entries"], new_data["entries"]))
+ print("\n")
+
+ # print("## Target summary: \n")
+ # print(get_overview(old_data["entries"]))
+ # print("\n")
+
+ if new_exists:
+ print("## PR summary: \n")
+ print(get_summary(new_data["entries"]))
+ print("\n")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/flake.nix b/flake.nix
index ab5e230e16..f000bf5279 100644
--- a/flake.nix
+++ b/flake.nix
@@ -132,6 +132,7 @@
nodePackages.prettier
treefmtEval.config.build.wrapper
typst # for the whitepaper
+ python3 # for comparing official testsuite results
];
env = [
{
@@ -187,6 +188,26 @@
'';
help = "start typst watch loop for the whitepaper";
}
+ {
+ name = "generate-testsuite-report";
+ # TODO maybe accept the name of the target branch as an argument and use it instead of `main`
+ command = ''
+ (
+ cd $PRJ_ROOT
+ TESTSUITE_SAVE=1 cargo test -- spec_tests --show-output
+ cp testsuite_results.json new.json
+ mkdir .main_clone
+ git clone --depth 1 --single-branch --no-tags --recursive -b main $(git config --get remote.origin.url) .main_clone
+ cd .main_clone
+ TESTSUITE_SAVE=1 cargo test -- spec_tests --show-output
+ mv testsuite_results.json ../old.json
+ cd ..
+ rm -rf .main_clone
+ python3 ./ci-tools/compare_testsuite.py old.json new.json > testsuite_report.md
+ )
+ '';
+ help = "generates a comparison document for the official wasm testsuite w.r.t. project main branch";
+ }
];
}
);
diff --git a/tests/specification/ci_reports.rs b/tests/specification/ci_reports.rs
new file mode 100644
index 0000000000..3349f49155
--- /dev/null
+++ b/tests/specification/ci_reports.rs
@@ -0,0 +1,84 @@
+use super::reports::{WastError, WastSuccess, WastTestReport};
+
+#[derive(serde::Serialize)]
+pub struct CIFullReport {
+ pub entries: Vec,
+}
+
+impl CIFullReport {
+ pub fn new(report: &[WastTestReport]) -> Self {
+ Self {
+ entries: report.iter().map(CIReportHeader::new).collect(),
+ }
+ }
+}
+
+#[derive(serde::Serialize)]
+pub struct CIReportHeader {
+ pub filepath: String,
+ pub data: CIReportData,
+}
+impl CIReportHeader {
+ fn new(report: &WastTestReport) -> Self {
+ let filepath = match report {
+ WastTestReport::Asserts(assert_report) => assert_report.filename.clone(),
+ WastTestReport::ScriptError(script_error) => script_error.filename.clone(),
+ };
+
+ Self {
+ filepath,
+ data: CIReportData::new(report),
+ }
+ }
+}
+
+#[derive(serde::Serialize)]
+pub enum CIReportData {
+ Assert {
+ results: Vec,
+ },
+ ScriptError {
+ error: String,
+ context: String,
+ line_number: Option,
+ command: Option,
+ },
+}
+impl CIReportData {
+ fn new(report: &WastTestReport) -> Self {
+ match report {
+ WastTestReport::Asserts(assert_report) => Self::Assert {
+ results: assert_report.results.iter().map(CIAssert::new).collect(),
+ },
+ WastTestReport::ScriptError(script_error) => Self::ScriptError {
+ error: script_error.error.to_string(),
+ context: script_error.context.clone(),
+ line_number: script_error.line_number,
+ command: script_error.command.clone(),
+ },
+ }
+ }
+}
+
+#[derive(serde::Serialize)]
+pub struct CIAssert {
+ pub error: Option,
+ pub line_number: u32,
+ pub command: String,
+}
+impl CIAssert {
+ fn new(res: &Result) -> Self {
+ match res {
+ Ok(success) => Self {
+ error: None,
+ line_number: success.line_number,
+ command: success.command.clone(),
+ },
+ Err(err) => Self {
+ error: Some(err.inner.to_string()),
+ line_number: err.line_number,
+ command: err.command.clone(),
+ },
+ }
+ }
+}
diff --git a/tests/specification/files.rs b/tests/specification/files.rs
new file mode 100644
index 0000000000..c99c32ee67
--- /dev/null
+++ b/tests/specification/files.rs
@@ -0,0 +1,64 @@
+use std::ffi::{OsStr, OsString};
+use std::path::{Path, PathBuf};
+
+#[derive(Debug, Clone)]
+pub struct Filter {
+ pub mode: FilterMode,
+ pub files: Vec,
+}
+
+#[allow(dead_code)] // Currently, we can always use only one variant at a time
+#[derive(Debug, Copy, Clone, PartialEq, Eq)]
+pub enum FilterMode {
+ Include,
+ Exclude,
+}
+
+impl Filter {
+ fn allows(&self, file_path: &Path) -> bool {
+ // If the file does not have a file name, we simply disallow it
+ let Some(file_name) = file_path.file_name() else {
+ return false;
+ };
+
+ // Assume filter includes for now
+ let result = self
+ .files
+ .iter()
+ .any(|os_string| os_string.as_os_str() == file_name);
+
+ // Now if the filter excludes, invert the result
+ result ^ (self.mode == FilterMode::Exclude)
+ }
+}
+
+/// Simple depth-first directory traversal with a filter for `wast` files
+pub fn find_wast_files(base_path: &Path, filter: &Filter) -> std::io::Result> {
+ let mut paths = vec![];
+
+ // The stack containing all directories we still have to traverse. At first contains only the base directory.
+ let mut read_dirs = vec![std::fs::read_dir(base_path)?];
+
+ while let Some(last_read_dir) = read_dirs.last_mut() {
+ let Some(entry) = last_read_dir.next() else {
+ read_dirs.pop();
+ continue;
+ };
+
+ let entry = entry?;
+ let meta = entry.metadata()?;
+ let path = entry.path();
+
+ if filter.allows(&path) {
+ if meta.is_file() && entry.path().extension() == Some(OsStr::new("wast")) {
+ paths.push(entry.path())
+ }
+
+ if meta.is_dir() {
+ read_dirs.push(std::fs::read_dir(path)?);
+ }
+ }
+ }
+
+ Ok(paths)
+}
diff --git a/tests/specification/mod.rs b/tests/specification/mod.rs
index dc80cce6bd..e8fcbd922f 100644
--- a/tests/specification/mod.rs
+++ b/tests/specification/mod.rs
@@ -1,129 +1,94 @@
-use std::{
- fs,
- path::{Path, PathBuf},
-};
-
+use ci_reports::CIFullReport;
+use files::{Filter, FilterMode};
use reports::WastTestReport;
+use std::ffi::OsString;
+use std::fmt::Write as _;
+use std::path::Path;
+mod ci_reports;
+mod files;
mod reports;
mod run;
mod test_errors;
-enum Filter {
- #[allow(dead_code)]
- Include(FnF),
- #[allow(dead_code)]
- Exclude(FnF),
-}
-
-struct Report {
- #[allow(dead_code)]
- fp: PathBuf,
- report: WastTestReport,
-}
-
-struct FnF {
- #[allow(dead_code)]
- files: Option>,
- #[allow(dead_code)]
- folders: Option>,
-}
-
-impl Default for FnF {
- fn default() -> Self {
- Self {
- files: None,
- folders: None,
- }
- }
-}
-
#[test_log::test]
pub fn spec_tests() {
- // so we don't see unnecessary stacktraces of catch_unwind (sadly this also means we don't see panics from outside catch_unwind either)
- std::panic::set_hook(Box::new(|_| {}));
+ let filter = Filter {
+ mode: FilterMode::Exclude,
+ files: vec![OsString::from("proposals")],
+ };
- let filters = Filter::Exclude(FnF {
- folders: Some(vec!["proposals".to_string()]),
- ..Default::default()
- });
-
- let paths = get_wast_files(Path::new("./tests/specification/testsuite/"), &filters)
+ let paths = files::find_wast_files(Path::new("./tests/specification/testsuite/"), &filter)
.expect("Failed to find testsuite");
- // let pb: PathBuf = "./tests/specification/testsuite/table_get.wast".into();
- // let mut paths = Vec::new();
- // paths.push(pb);
-
- assert!(paths.len() > 0, "Submodules not instantiated");
+ assert!(!paths.is_empty(), "Submodules not instantiated");
- let mut successful_reports = 0;
- let mut failed_reports = 0;
- let mut compile_error_reports = 0;
- let mut reports: Vec = Vec::with_capacity(paths.len());
+ // Some statistics about the reports
+ let mut num_failures = 0;
+ let mut num_script_errors = 0;
- let mut longest_string_len: usize = 0;
+ // Used for padding of filenames with spaces later
+ let mut longest_filename_len: usize = 0;
- for test_path in paths {
- let mut report = run::run_spec_test(test_path.to_str().unwrap());
+ let reports: Vec = paths
+ .into_iter()
+ .map(|path| run::run_spec_test(path.to_str().unwrap()))
+ .inspect(|report| {
+ match report {
+ reports::WastTestReport::Asserts(assert_report) => {
+ longest_filename_len = longest_filename_len.max(assert_report.filename.len());
- match &mut report {
- reports::WastTestReport::Asserts(ref mut assert_report) => {
- // compute auxiliary data
- assert_report.compute_data();
- if assert_report.filename.len() > longest_string_len {
- longest_string_len = assert_report.filename.len();
+ if assert_report.has_errors() {
+ num_failures += 1;
+ }
}
- if assert_report.has_errors() {
- failed_reports += 1;
- } else {
- successful_reports += 1;
+ reports::WastTestReport::ScriptError(_) => {
+ num_script_errors += 1;
}
- }
- reports::WastTestReport::CompilationError(_) => {
- compile_error_reports += 1;
- }
- };
-
- let rep = Report {
- fp: test_path.clone(),
- report,
- };
+ };
+ })
+ .collect();
- reports.push(rep);
- }
+ // Calculate another required statistic
+ let num_successes = reports.len() - num_script_errors - num_failures;
- let mut no_compile_errors_reports = reports
+ // Collect all reports without errors along with some statistic
+ let mut successful_mini_tests = 0;
+ let mut total_mini_tests = 0;
+ let mut assert_reports: Vec<&reports::AssertReport> = reports
.iter()
- .filter_map(|e| match &e.report {
+ .filter_map(|r| match r {
WastTestReport::Asserts(asserts) => Some(asserts),
- _ => None,
+ WastTestReport::ScriptError(_) => None,
+ })
+ .inspect(|assert_report| {
+ successful_mini_tests += assert_report.passed_asserts();
+ total_mini_tests += assert_report.total_asserts();
})
- .collect::>();
- no_compile_errors_reports.sort_by(|a, b| b.percentage.total_cmp(&a.percentage));
+ .collect();
- let mut successful_mini_tests = 0;
- let mut total_mini_tests = 0;
+ // Sort all reports without errors for displaying it to the user later
+ assert_reports.sort_by(|a, b| {
+ b.percentage_asserts_passed()
+ .total_cmp(&a.percentage_asserts_passed())
+ });
let mut final_status: String = String::new();
// Printing success rate per file for those that did NOT error out when compiling
- for report in no_compile_errors_reports {
- final_status += format!(
- "Report for {:filename_width$}: Tests: {:passed_width$} Passed, {:failed_width$} Failed --- {:percentage_width$.2}%\n",
+ for report in assert_reports {
+ writeln!(final_status,
+ "Report for {:filename_width$}: Tests: {:passed_width$} Passed, {:failed_width$} Failed --- {:percentage_width$.2}%",
report.filename,
- report.successful,
- report.failed,
- report.percentage,
- filename_width = longest_string_len + 1,
+ report.passed_asserts(),
+ report.failed_asserts(),
+ report.percentage_asserts_passed(),
+ filename_width = longest_filename_len + 1,
passed_width = 7,
failed_width = 7,
percentage_width = 6
- ).as_str();
+ ).expect("writing into strings to never fail");
- successful_mini_tests += report.successful;
- total_mini_tests += report.total;
-
- if report.successful < report.total {
+ if report.passed_asserts() < report.total_asserts() {
println!("{}", report);
}
}
@@ -136,7 +101,7 @@ pub fn spec_tests() {
successful_mini_tests,
total_mini_tests - successful_mini_tests,
if total_mini_tests == 0 { 0.0 } else {(successful_mini_tests as f64) * 100.0 / (total_mini_tests as f64)},
- filename_width = longest_string_len + 1,
+ filename_width = longest_filename_len + 1,
passed_width = 7,
failed_width = 7,
percentage_width = 6
@@ -144,119 +109,14 @@ pub fn spec_tests() {
println!(
"Tests: {} Passed, {} Failed, {} Compilation Errors",
- successful_reports, failed_reports, compile_error_reports
+ num_successes, num_failures, num_script_errors
);
-}
-
-#[allow(dead_code)]
-// See: https://stackoverflow.com/a/76820878
-fn get_wast_files(
- base_path: &Path,
- // run_only_these_tests: &Vec,
- filters: &Filter,
-) -> Result, std::io::Error> {
- let mut buf = vec![];
- let entries = fs::read_dir(base_path)?;
- for entry in entries {
- let entry = entry?;
- let meta = entry.metadata()?;
+ // Optional: We need to save the result to a file for CI Regression Analysis
+ if std::option_env!("TESTSUITE_SAVE").is_some() {
+ let ci_report = CIFullReport::new(&reports);
+ let output_file = std::fs::File::create("./testsuite_results.json").unwrap();
- if meta.is_dir() {
- if should_add_folder_to_buffer(&entry.path(), &filters) {
- let mut subdir = get_wast_files(&entry.path(), &filters)?;
- buf.append(&mut subdir);
- }
- }
-
- if meta.is_file() && entry.path().extension().unwrap_or_default() == "wast" {
- if should_add_file_to_buffer(&entry.path(), &filters) {
- buf.push(entry.path())
- }
- }
- }
-
- Ok(buf)
-}
-
-fn should_add_file_to_buffer(file_path: &PathBuf, filters: &Filter) -> bool {
- match filters {
- Filter::Exclude(ref fnf) => match &fnf.files {
- None => true,
- Some(files) => {
- if files.is_empty() {
- return true;
- }
-
- if let Some(file_name) = file_path.file_name() {
- if files.contains(&file_name.to_str().unwrap().to_owned()) {
- false
- } else {
- true
- }
- } else {
- false
- }
- }
- },
- Filter::Include(ref fnf) => match &fnf.files {
- None => false,
- Some(files) => {
- if files.is_empty() {
- return false;
- }
-
- if let Some(file_name) = file_path.file_name() {
- if files.contains(&file_name.to_str().unwrap().to_owned()) {
- true
- } else {
- false
- }
- } else {
- false
- }
- }
- },
- }
-}
-
-fn should_add_folder_to_buffer(file_path: &PathBuf, filters: &Filter) -> bool {
- match filters {
- Filter::Exclude(ref fnf) => match &fnf.folders {
- None => true,
- Some(folders) => {
- if folders.is_empty() {
- return true;
- }
-
- if let Some(file_name) = file_path.file_name() {
- if folders.contains(&file_name.to_str().unwrap().to_owned()) {
- false
- } else {
- true
- }
- } else {
- false
- }
- }
- },
- Filter::Include(ref fnf) => match &fnf.folders {
- None => false,
- Some(folders) => {
- if folders.is_empty() {
- return false;
- }
-
- if let Some(file_name) = file_path.file_name() {
- if folders.contains(&file_name.to_str().unwrap().to_owned()) {
- true
- } else {
- false
- }
- } else {
- false
- }
- }
- },
+ serde_json::to_writer_pretty(output_file, &ci_report).unwrap();
}
}
diff --git a/tests/specification/reports.rs b/tests/specification/reports.rs
index e32baf1639..d6e7527ead 100644
--- a/tests/specification/reports.rs
+++ b/tests/specification/reports.rs
@@ -1,8 +1,8 @@
use std::error::Error;
pub struct WastSuccess {
- line_number: u32,
- command: String,
+ pub line_number: u32,
+ pub command: String,
}
impl WastSuccess {
@@ -15,39 +15,69 @@ impl WastSuccess {
}
pub struct WastError {
- inner: Box,
- line_number: Option,
- command: String,
+ pub inner: Box,
+ pub line_number: u32,
+ pub command: String,
}
impl WastError {
pub fn new(error: Box, line_number: u32, command: &str) -> Self {
Self {
inner: error,
- line_number: Some(line_number),
+ line_number,
command: command.to_string(),
}
}
}
+/// Wast script executed successfuly. The results of asserts (pass/fail) are
+/// stored here.
pub struct AssertReport {
pub filename: String,
pub results: Vec>,
- pub successful: usize,
- pub failed: usize,
- pub total: usize,
- pub percentage: f64,
}
-impl Default for AssertReport {
- fn default() -> Self {
+impl AssertReport {
+ pub fn new(filename: &str) -> Self {
Self {
- filename: String::from(""),
+ filename: filename.to_string(),
results: Vec::new(),
- successful: 0,
- failed: 0,
- total: 0,
- percentage: 0.0,
+ }
+ }
+
+ pub fn push_success(&mut self, success: WastSuccess) {
+ self.results.push(Ok(success));
+ }
+
+ pub fn push_error(&mut self, error: WastError) {
+ self.results.push(Err(error));
+ }
+
+ pub fn compile_report(self) -> WastTestReport {
+ WastTestReport::Asserts(self)
+ }
+
+ pub fn has_errors(&self) -> bool {
+ self.results.iter().any(|r| r.is_err())
+ }
+
+ pub fn total_asserts(&self) -> u32 {
+ self.results.len() as u32
+ }
+
+ pub fn passed_asserts(&self) -> u32 {
+ self.results.iter().filter(|el| el.is_ok()).count() as u32
+ }
+
+ pub fn failed_asserts(&self) -> u32 {
+ self.total_asserts() - self.passed_asserts()
+ }
+
+ pub fn percentage_asserts_passed(&self) -> f32 {
+ if self.total_asserts() == 0 {
+ 0.0
+ } else {
+ (self.passed_asserts() as f32) * 100.0 / (self.total_asserts() as f32)
}
}
}
@@ -73,17 +103,7 @@ impl std::fmt::Display for AssertReport {
writeln!(
f,
"❌ {}:{} -> {}",
- self.filename,
- match error.line_number {
- None => u32::MAX.to_string(),
- Some(line_number) =>
- if line_number == u32::MAX {
- "?".to_string()
- } else {
- line_number.to_string()
- },
- },
- error.command
+ self.filename, error.line_number, error.command
)?;
writeln!(f, " Error: {}", error.inner)?;
}
@@ -94,82 +114,69 @@ impl std::fmt::Display for AssertReport {
}
}
-impl AssertReport {
- pub fn new(filename: &str) -> Self {
+/// An error originating from within the WAST Script. If a non-assert directive
+/// fails, a ScriptError will be raised.
+pub struct ScriptError {
+ pub filename: String,
+ pub error: Box,
+ pub context: String,
+ #[allow(unused)]
+ pub line_number: Option,
+ #[allow(unused)]
+ pub command: Option,
+}
+
+impl ScriptError {
+ pub fn new(
+ filename: &str,
+ error: Box,
+ context: &str,
+ line_number: u32,
+ command: &str,
+ ) -> Self {
Self {
filename: filename.to_string(),
- results: Vec::new(),
- ..Default::default()
+ error,
+ context: context.to_string(),
+ line_number: Some(line_number),
+ command: Some(command.to_string()),
}
}
- pub fn compute_data(&mut self) {
- self.total = self.results.len();
- self.successful = self.results.iter().filter(|el| el.is_ok()).count();
- self.failed = self.total - self.successful;
- self.percentage = if self.total == 0 {
- 0.0
- } else {
- (self.successful as f64) * 100.0 / (self.total as f64)
- };
- }
-
- pub fn push_success(&mut self, success: WastSuccess) {
- self.results.push(Ok(success));
- }
-
- pub fn push_error(&mut self, error: WastError) {
- self.results.push(Err(error));
- }
-
- pub fn compile_report(self) -> WastTestReport {
- return WastTestReport::Asserts(self);
- }
-
- pub fn has_errors(&self) -> bool {
- self.results.iter().any(|r| r.is_err())
- }
-}
-
-pub struct CompilationError {
- inner: Box,
- filename: String,
- context: String,
-}
-
-impl CompilationError {
- pub fn new(error: Box, filename: &str, context: &str) -> Self {
+ pub fn new_lineless(filename: &str, error: Box, context: &str) -> Self {
Self {
- inner: error,
filename: filename.to_string(),
+ error,
context: context.to_string(),
+ line_number: None,
+ command: None,
}
}
pub fn compile_report(self) -> WastTestReport {
- return WastTestReport::CompilationError(self);
+ WastTestReport::ScriptError(self)
}
}
pub enum WastTestReport {
+ /// The script ran successfully, having directives run successfuly (though
+ /// not necessarily meaning all asserts pass!)
Asserts(AssertReport),
- CompilationError(CompilationError),
+ /// The script could not run successfully, a non-assert directive failed in
+ /// such a way the script cannot continue running.
+ ScriptError(ScriptError),
}
-// .------------------------.
-// | Display Implementation |
-// '------------------------'
-
impl std::fmt::Display for WastTestReport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
- WastTestReport::CompilationError(error) => {
+ WastTestReport::ScriptError(error) => {
writeln!(f, "------ {} ------", error.filename)?;
writeln!(f, "⚠ Compilation Failed ⚠")?;
writeln!(f, "Context: {}", error.context)?;
- writeln!(f, "Error: {}", error.inner)?;
+ writeln!(f, "Error: {}", error.error)?;
writeln!(f, "~~~~~~~~~~~~~~~~")?;
- writeln!(f, "")?;
+ writeln!(f)?;
}
WastTestReport::Asserts(assert_report) => {
writeln!(f, "------ {} ------", assert_report.filename)?;
@@ -178,14 +185,14 @@ impl std::fmt::Display for WastTestReport {
let failed_asserts = assert_report.results.iter().filter(|r| r.is_err()).count();
let total_asserts = assert_report.results.len();
- writeln!(f, "")?;
+ writeln!(f)?;
writeln!(
f,
"Execution finished. Passed: {}, Failed: {}, Total: {}",
passed_asserts, failed_asserts, total_asserts
)?;
writeln!(f, "~~~~~~~~~~~~~~~~")?;
- writeln!(f, "")?;
+ writeln!(f)?;
}
}
diff --git a/tests/specification/run.rs b/tests/specification/run.rs
index c9f8a6bdd8..1a5a521583 100644
--- a/tests/specification/run.rs
+++ b/tests/specification/run.rs
@@ -1,22 +1,7 @@
-/*
-# This file incorporates code from Wasmtime, originally
-# available at https://github.com/bytecodealliance/wasmtime.
-#
-# The original code is licensed under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-*/
+use std::any::Any;
use std::error::Error;
-use std::panic::catch_unwind;
use std::panic::AssertUnwindSafe;
+use std::panic::UnwindSafe;
use wasm::function_ref::FunctionRef;
use wasm::RuntimeError;
@@ -25,32 +10,37 @@ use wasm::DEFAULT_MODULE;
use wasm::{validate, RuntimeInstance};
use wast::core::WastArgCore;
use wast::core::WastRetCore;
+use wast::QuoteWat;
use wast::WastArg;
use crate::specification::reports::*;
use crate::specification::test_errors::*;
-pub fn to_wasm_testsuite_string(runtime_error: RuntimeError) -> std::string::String {
+pub fn to_wasm_testsuite_string(runtime_error: RuntimeError) -> Result> {
+ let not_represented = Err(GenericError::new_boxed(
+ "Runtime error not represented in WAST",
+ ));
+
match runtime_error {
- RuntimeError::DivideBy0 => "integer divide by zero",
- RuntimeError::UnrepresentableResult => "integer overflow",
- RuntimeError::FunctionNotFound => todo!(),
- RuntimeError::StackSmash => todo!(),
- RuntimeError::BadConversionToInteger => "invalid conversion to integer",
-
- RuntimeError::MemoryAccessOutOfBounds => "out of bounds memory access",
- RuntimeError::TableAccessOutOfBounds => "out of bounds table access",
- RuntimeError::ElementAccessOutOfBounds => todo!(),
-
- RuntimeError::UninitializedElement => "uninitialized element",
- RuntimeError::SignatureMismatch => "indirect call type mismatch",
- RuntimeError::ExpectedAValueOnTheStack => todo!(),
-
- RuntimeError::UndefinedTableIndex => "undefined element",
- RuntimeError::ModuleNotFound => "module not found",
- RuntimeError::UnmetImport => "unmet import",
+ RuntimeError::DivideBy0 => Ok("integer divide by zero"),
+ RuntimeError::UnrepresentableResult => Ok("integer overflow"),
+ RuntimeError::FunctionNotFound => not_represented,
+ RuntimeError::StackSmash => not_represented,
+ RuntimeError::BadConversionToInteger => Ok("invalid conversion to integer"),
+
+ RuntimeError::MemoryAccessOutOfBounds => Ok("out of bounds memory access"),
+ RuntimeError::TableAccessOutOfBounds => Ok("out of bounds table access"),
+ RuntimeError::ElementAccessOutOfBounds => not_represented,
+
+ RuntimeError::UninitializedElement => Ok("uninitialized element"),
+ RuntimeError::SignatureMismatch => Ok("indirect call type mismatch"),
+ RuntimeError::ExpectedAValueOnTheStack => not_represented,
+
+ RuntimeError::UndefinedTableIndex => Ok("undefined element"),
+ RuntimeError::ModuleNotFound => Ok("module not found"),
+ RuntimeError::UnmetImport => Ok("unmet import"),
}
- .to_string()
+ .map(|s| s.to_string())
}
/// Attempt to unwrap the result of an expression. If the expression is an `Err`, then `return` the
@@ -67,96 +57,56 @@ macro_rules! try_to {
};
}
-#[derive(Debug)]
-enum ErrEVI {
- Encode,
- Validate,
- Instantiate,
-}
+/// Clear the bytes and runtime instance before calling this function
+fn encode(module: &mut wast::QuoteWat) -> Result, Box> {
+ match &module {
+ QuoteWat::QuoteComponent(..) | QuoteWat::Wat(wast::Wat::Component(..)) => {
+ return Err(GenericError::new_boxed(
+ "Component modules are not supported",
+ ))
+ }
+ QuoteWat::Wat(..) | QuoteWat::QuoteModule(..) => (),
+ };
-impl std::fmt::Display for ErrEVI {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self)
- }
+ let inner_bytes = module.encode().map_err(Box::new)?;
+ Ok(inner_bytes)
}
-impl std::error::Error for ErrEVI {}
+fn validate_instantiate(bytes: &[u8]) -> Result, Box> {
+ let validation_info = catch_unwind_and_suppress_panic_handler(|| validate(bytes))
+ .map_err(PanicError::from_panic_boxed)?
+ .map_err(WasmInterpreterError::new_boxed)?;
-/// Clear the bytes and runtime instance before calling this function
-fn encode_validate_instantiate<'a>(
- module: &mut wast::QuoteWat,
- bytes: &'a mut Option>,
- runtime_inst: &'a mut Option>,
-) -> Result<(), ErrEVI> {
- use wast::*;
- let is_module = match &module {
- QuoteWat::QuoteComponent(..) | QuoteWat::Wat(Wat::Component(..)) => false,
- QuoteWat::Wat(..) | QuoteWat::QuoteModule(..) => true,
- };
+ let runtime_instance =
+ catch_unwind_and_suppress_panic_handler(|| RuntimeInstance::new(&validation_info))
+ .map_err(PanicError::from_panic_boxed)?
+ .map_err(WasmInterpreterError::new_boxed)?;
- if is_module {
- let inner_bytes = module.encode();
-
- match inner_bytes {
- Err(_) => Err(ErrEVI::Encode),
- Ok(inner_bytes) => {
- bytes.replace(inner_bytes);
- let validation_info_attempt = catch_unwind(|| validate(bytes.as_ref().unwrap()));
-
- match validation_info_attempt {
- Err(_) => Err(ErrEVI::Validate),
- Ok(validation_info) => match validation_info {
- Err(_) => Err(ErrEVI::Validate),
- Ok(validation_info) => {
- let runtime_instance_result =
- catch_unwind(|| RuntimeInstance::new(&validation_info));
-
- match runtime_instance_result {
- Err(_) => Err(ErrEVI::Instantiate),
- Ok(runtime_instance) => match runtime_instance {
- Err(_) => Err(ErrEVI::Instantiate),
- Ok(runtime_instance) => {
- runtime_inst.replace(runtime_instance);
- Ok(())
- }
- },
- }
- }
- },
- }
- }
- }
- } else {
- Err(ErrEVI::Encode)
- }
+ Ok(runtime_instance)
}
pub fn run_spec_test(filepath: &str) -> WastTestReport {
// -=-= Initialization =-=-
- let contents = try_to!(
- std::fs::read_to_string(filepath).map_err(|err| CompilationError::new(
- Box::new(err),
- filepath,
- "failed to open wast file"
- )
- .compile_report())
- );
-
- let buf =
+ let contents =
try_to!(
- wast::parser::ParseBuffer::new(&contents).map_err(|err| CompilationError::new(
- Box::new(err),
+ std::fs::read_to_string(filepath).map_err(|err| ScriptError::new_lineless(
filepath,
- "failed to create wast buffer"
+ Box::new(err),
+ "failed to open wast file",
)
.compile_report())
);
+ let buf = try_to!(wast::parser::ParseBuffer::new(&contents).map_err(|err| {
+ ScriptError::new_lineless(filepath, Box::new(err), "failed to create wast buffer")
+ .compile_report()
+ }));
+
let wast =
try_to!(
- wast::parser::parse::(&buf).map_err(|err| CompilationError::new(
- Box::new(err),
+ wast::parser::parse::(&buf).map_err(|err| ScriptError::new_lineless(
filepath,
+ Box::new(err),
"failed to parse wast file"
)
.compile_report())
@@ -165,126 +115,71 @@ pub fn run_spec_test(filepath: &str) -> WastTestReport {
// -=-= Testing & Compilation =-=-
let mut asserts = AssertReport::new(filepath);
- // We need to keep the wasm_bytes in-scope for the lifetime of the interpeter.
+ // We need to keep the wasm_bytes in-scope for the lifetime of the interpreter.
// As such, we hoist the bytes into an Option, and assign it once a module directive is found.
#[allow(unused_assignments)]
let mut wasm_bytes: Option> = None;
- let mut interpeter = None;
+ let mut interpreter = None;
for directive in wast.directives {
match directive {
wast::WastDirective::Wat(mut quoted) => {
// If we fail to compile or to validate the main module, then we should treat this
// as a fatal (compilation) error.
- let encoded = try_to!(quoted.encode().map_err(|err| CompilationError::new(
- Box::new(err),
- filepath,
- "failed to encode main module's wat"
- )
- .compile_report()));
-
- wasm_bytes = Some(encoded);
+ wasm_bytes = Some(try_to!(encode(&mut quoted).map_err(|err| {
+ ScriptError::new(
+ filepath,
+ err,
+ "Module directive (WAT) failed in encoding step.",
+ get_linenum(&contents, quoted.span()),
+ get_command(&contents, quoted.span()),
+ )
+ .compile_report()
+ })));
- let validation_attempt = catch_unwind(|| {
- validate(wasm_bytes.as_ref().unwrap()).map_err(|err| {
- CompilationError::new(
- Box::new(WasmInterpreterError(err)),
+ interpreter = Some(try_to!(validate_instantiate(wasm_bytes.as_ref().unwrap())
+ .map_err(|err| {
+ ScriptError::new(
filepath,
- "main module validation failed",
- )
- .compile_report()
- })
- });
-
- let validation_info = match validation_attempt {
- Ok(original_result) => try_to!(original_result),
- Err(panic) => {
- // TODO: Do we want to exit on panic? State may be left in an inconsistent state, and cascading panics may occur.
- let err = if let Ok(msg) = panic.downcast::<&str>() {
- Box::new(PanicError::new(&msg))
- } else {
- Box::new(PanicError::new("Unknown panic"))
- };
-
- return CompilationError::new(
err,
- filepath,
- "main module validation panicked",
- )
- .compile_report();
- }
- };
-
- let runtime_instance = catch_unwind(|| RuntimeInstance::new(&validation_info));
-
- let instance = match runtime_instance {
- Err(_) => {
- return CompilationError::new(
- Box::new(ErrEVI::Instantiate),
- filepath,
- "failed to create runtime instance",
+ "Module directive (WAT) failed in validation or instantiation.",
+ get_linenum(&contents, quoted.span()),
+ get_command(&contents, quoted.span()),
)
- .compile_report();
- }
- Ok(instance_result) => match instance_result {
- Err(e) => {
- return CompilationError::new(
- Box::new(WasmInterpreterError(e)),
- filepath,
- "failed to create runtime instance",
- )
- .compile_report()
- }
- Ok(instance) => instance,
- },
- };
-
- interpeter = Some(instance);
+ .compile_report()
+ })));
}
wast::WastDirective::AssertReturn {
span,
exec,
results,
} => {
- if interpeter.is_none() {
- return CompilationError::new(
- Box::new(GenericError::new(
- "Attempted to assert before module directive",
- )),
+ if interpreter.is_none() {
+ return ScriptError::new(
filepath,
- "no module directive found",
+ GenericError::new_boxed("Attempted to assert before module directive"),
+ "Assert Return",
+ get_linenum(&contents, span),
+ get_command(&contents, span),
)
.compile_report();
}
- let interpeter = interpeter.as_mut().unwrap();
-
- let err_or_panic: Result<(), Box> =
- match catch_unwind(AssertUnwindSafe(|| {
- execute_assert_return(interpeter, exec, results)
- })) {
- Ok(original_result) => original_result,
- Err(inner) => {
- // TODO: Do we want to exit on panic? State may be left in an inconsistent state, and cascading panics may occur.
- if let Ok(msg) = inner.downcast::<&str>() {
- Err(Box::new(PanicError::new(&msg)))
- } else {
- Err(Box::new(PanicError::new("Unknown panic")))
- }
- }
- };
+ let interpreter = interpreter.as_mut().unwrap();
+
+ let err_or_panic = execute_assert_return(interpreter, exec, results);
match err_or_panic {
- Ok(_) => {
+ Ok(()) => {
asserts.push_success(WastSuccess::new(
- span.linecol_in(&contents).0 as u32 + 1,
+ get_linenum(&contents, span),
get_command(&contents, span),
));
}
Err(inner) => {
asserts.push_error(WastError::new(
inner,
- span.linecol_in(&contents).0 as u32 + 1,
+ get_linenum(&contents, span),
get_command(&contents, span),
));
}
@@ -295,45 +190,32 @@ pub fn run_spec_test(filepath: &str) -> WastTestReport {
exec,
message,
} => {
- if interpeter.is_none() {
- return CompilationError::new(
- Box::new(GenericError::new(
- "Attempted to assert before module directive",
- )),
+ if interpreter.is_none() {
+ return ScriptError::new(
filepath,
- "no module directive found",
+ GenericError::new_boxed("Attempted to assert before module directive"),
+ "Assert Trap",
+ get_linenum(&contents, span),
+ get_command(&contents, span),
)
.compile_report();
}
- let interpeter = interpeter.as_mut().unwrap();
-
- let err_or_panic: Result<(), Box> =
- match catch_unwind(AssertUnwindSafe(|| {
- execute_assert_trap(interpeter, exec, message)
- })) {
- Err(inner) => {
- // TODO: Do we want to exit on panic? State may be left in an inconsistent state, and cascading panics may occur.
- if let Ok(msg) = inner.downcast::<&str>() {
- Err(Box::new(PanicError::new(&msg)))
- } else {
- Err(Box::new(PanicError::new("Unknown panic")))
- }
- }
- Ok(original_result) => original_result,
- };
+ let interpreter = interpreter.as_mut().unwrap();
+
+ let err_or_panic = execute_assert_trap(interpreter, exec, message);
match err_or_panic {
Ok(_) => {
asserts.push_success(WastSuccess::new(
- span.linecol_in(&contents).0 as u32 + 1,
+ get_linenum(&contents, span),
get_command(&contents, span),
));
}
Err(inner) => {
asserts.push_error(WastError::new(
inner,
- span.linecol_in(&contents).0 as u32 + 1,
+ get_linenum(&contents, span),
get_command(&contents, span),
));
}
@@ -345,12 +227,16 @@ pub fn run_spec_test(filepath: &str) -> WastTestReport {
mut module,
message: _,
} => {
- let line_number = span.linecol_in(&contents).0 as u32 + 1;
+ let line_number = get_linenum(&contents, span);
let cmd = get_command(&contents, span);
+ let error = GenericError::new_boxed(
+ "Module validated and instantiated successfully, when it shouldn't have",
+ );
- match encode_validate_instantiate(&mut module, &mut None, &mut None) {
+ match encode(&mut module).and_then(|bytes| validate_instantiate(&bytes).map(|_| ()))
+ {
Err(_) => asserts.push_success(WastSuccess::new(line_number, cmd)),
- Ok(_) => asserts.push_error(WastError::new("Module validated and instantiated successfully, when it shouldn't have been".into(), line_number, cmd))
+ Ok(_) => asserts.push_error(WastError::new(error, line_number, cmd)),
};
}
@@ -359,20 +245,30 @@ pub fn run_spec_test(filepath: &str) -> WastTestReport {
mut module,
message: _,
} => {
- let line_number = span.linecol_in(&contents).0 as u32 + 1;
+ let line_number = get_linenum(&contents, span);
let cmd = get_command(&contents, span);
+ let error = GenericError::new_boxed(
+ "Module validated and instantiated successfully, when it shouldn't have",
+ );
- match encode_validate_instantiate(&mut module, &mut None, &mut None) {
+ match encode(&mut module).and_then(|bytes| validate_instantiate(&bytes).map(|_| ()))
+ {
Err(_) => asserts.push_success(WastSuccess::new(line_number, cmd)),
- Ok(_) => asserts.push_error(WastError::new("Module validated and instantiated successfully, when it shouldn't have been".into(), line_number, cmd))
+ Ok(_) => asserts.push_error(WastError::new(error, line_number, cmd)),
};
}
wast::WastDirective::Register {
span,
name: _,
module: _,
+ } => {
+ asserts.push_error(WastError::new(
+ GenericError::new_boxed("Register directive not yet implemented"),
+ get_linenum(&contents, span),
+ get_command(&contents, span),
+ ));
}
- | wast::WastDirective::AssertExhaustion {
+ wast::WastDirective::AssertExhaustion {
span,
call: _,
message: _,
@@ -384,68 +280,95 @@ pub fn run_spec_test(filepath: &str) -> WastTestReport {
}
| wast::WastDirective::AssertException { span, exec: _ } => {
asserts.push_error(WastError::new(
- Box::new(GenericError::new("Assert directive not yet implemented")),
- span.linecol_in(&contents).0 as u32 + 1,
+ GenericError::new_boxed("Assert directive not yet implemented"),
+ get_linenum(&contents, span),
get_command(&contents, span),
));
}
wast::WastDirective::Wait { span, thread: _ } => {
asserts.push_error(WastError::new(
- Box::new(GenericError::new("Wait directive not yet implemented")),
- span.linecol_in(&contents).0 as u32 + 1,
+ GenericError::new_boxed("Wait directive not yet implemented"),
+ get_linenum(&contents, span),
get_command(&contents, span),
));
}
wast::WastDirective::Invoke(invoke) => {
- match interpeter {
- None => asserts.push_error(WastError::new(
- Box::new(GenericError::new(
- "Couldn't invoke, interpreter not present",
- )),
- u32::MAX,
- "invoke",
- )),
- Some(ref mut interpeter) => {
- let args = invoke
- .args
- .into_iter()
- .map(arg_to_value)
- .collect::>();
-
- // TODO: more modules ¯\_(ツ)_/¯
- match interpeter.get_function_by_name(DEFAULT_MODULE, invoke.name) {
- Err(_) => asserts.push_error(WastError::new(
- Box::new(GenericError::new(&format!(
- "Couldn't get the function '{}' from module '{}'",
- invoke.name, "DEFAULT_MODULE"
- ))),
- u32::MAX,
- "invoke",
- )),
- Ok(funcref) => {
- match interpeter.invoke_dynamic_unchecked_return_ty(&funcref, args)
- {
- Err(e) => asserts.push_error(WastError::new(
- Box::new(GenericError::new(&format!(
- "failed to execute function '{}' from module '{}' - error: {:?}",
- invoke.name, "DEFAULT_MODULE", e
- ))),
- u32::MAX,
- "invoke",
- )),
- Ok(_) => {
- asserts.push_success(WastSuccess::new(u32::MAX, "invoke"))
- }
- }
- }
- };
+ if interpreter.is_none() {
+ return ScriptError::new(
+ filepath,
+ GenericError::new_boxed(
+ "Attempted to run invoke directive before interpreter instantiation.",
+ ),
+ "Invoke",
+ get_linenum(&contents, invoke.span),
+ get_command(&contents, invoke.span),
+ )
+ .compile_report();
+ }
+
+ let interpreter = interpreter.as_mut().unwrap();
+
+ let args = invoke
+ .args
+ .into_iter()
+ .map(arg_to_value)
+ .collect::>();
+
+ let function_ref_attempt =
+ catch_unwind_and_suppress_panic_handler(AssertUnwindSafe(|| {
+ interpreter.get_function_by_name(DEFAULT_MODULE, invoke.name)
+ }))
+ .map(|result| {
+ result.map_err(|err| {
+ ScriptError::new(
+ filepath,
+ WasmInterpreterError::new_boxed(wasm::Error::RuntimeError(err)),
+ "Invoke directive failed to find function",
+ get_linenum(&contents, invoke.span),
+ get_command(&contents, invoke.span),
+ )
+ .compile_report()
+ })
+ });
+
+ let function_ref = match function_ref_attempt {
+ Ok(original_result) => try_to!(original_result),
+ Err(panic) => {
+ return ScriptError::new(
+ filepath,
+ PanicError::from_panic_boxed(panic),
+ "main module validation panicked",
+ get_linenum(&contents, invoke.span),
+ get_command(&contents, invoke.span),
+ )
+ .compile_report();
}
};
+
+ let err_or_panic: Result<_, Box> =
+ catch_unwind_and_suppress_panic_handler(AssertUnwindSafe(|| {
+ interpreter.invoke_dynamic_unchecked_return_ty(&function_ref, args)
+ }))
+ .map_err(PanicError::from_panic_boxed)
+ .and_then(|result| {
+ result.map_err(|err| {
+ WasmInterpreterError::new_boxed(wasm::Error::RuntimeError(err))
+ })
+ });
+
+ try_to!(err_or_panic.map_err(|inner| ScriptError::new(
+ filepath,
+ inner,
+ "Invoke returned error or panicked",
+ get_linenum(&contents, invoke.span),
+ get_command(&contents, invoke.span)
+ )
+ .compile_report()));
}
wast::WastDirective::Thread(thread) => {
asserts.push_error(WastError::new(
- Box::new(GenericError::new("Thread directive not yet implemented")),
- thread.span.linecol_in(&contents).0 as u32 + 1,
+ GenericError::new_boxed("Thread directive not yet implemented"),
+ get_linenum(&contents, thread.span),
get_command(&contents, thread.span),
));
}
@@ -456,7 +379,7 @@ pub fn run_spec_test(filepath: &str) -> WastTestReport {
}
fn execute_assert_return(
- interpeter: &mut RuntimeInstance,
+ interpreter: &mut RuntimeInstance,
exec: wast::WastExecute,
results: Vec,
) -> Result<(), Box> {
@@ -468,38 +391,46 @@ fn execute_assert_return(
.map(arg_to_value)
.collect::>();
- let result_vals = results.into_iter().map(result_to_value).collect::>();
+ let result_vals = results
+ .into_iter()
+ .map(result_to_value)
+ .collect::, _>>()?;
let result_types = result_vals
.iter()
.map(|val| val.to_ty())
.collect::>();
// TODO: more modules ¯\_(ツ)_/¯
- let func = interpeter
- .get_function_by_name(DEFAULT_MODULE, invoke_info.name)
- .map_err(|err| Box::new(WasmInterpreterError(wasm::Error::RuntimeError(err))))?;
-
- let actual = interpeter
- .invoke_dynamic(&func, args, &result_types)
- .map_err(|err| Box::new(WasmInterpreterError(wasm::Error::RuntimeError(err))))?;
+ let func = catch_unwind_and_suppress_panic_handler(AssertUnwindSafe(|| {
+ interpreter.get_function_by_name(DEFAULT_MODULE, invoke_info.name)
+ }))
+ .map_err(PanicError::from_panic_boxed)?
+ .map_err(|err| WasmInterpreterError::new_boxed(wasm::Error::RuntimeError(err)))?;
+
+ let actual = catch_unwind_and_suppress_panic_handler(AssertUnwindSafe(|| {
+ interpreter.invoke_dynamic(&func, args, &result_types)
+ }))
+ .map_err(PanicError::from_panic_boxed)?
+ .map_err(|err| WasmInterpreterError::new_boxed(wasm::Error::RuntimeError(err)))?;
AssertEqError::assert_eq(actual, result_vals)?;
+ Ok(())
}
wast::WastExecute::Get {
span: _,
module: _,
global: _,
- } => todo!("`get` directive inside `assert_return` not yet implemented"),
- wast::WastExecute::Wat(_) => {
- todo!("`wat` directive inside `assert_return` not yet implemented")
- }
+ } => Err(GenericError::new_boxed(
+ "`get` directive inside `assert_return` not yet implemented",
+ )),
+ wast::WastExecute::Wat(_) => Err(GenericError::new_boxed(
+ "`wat` directive inside `assert_return` not yet implemented",
+ )),
}
-
- Ok(())
}
fn execute_assert_trap(
- interpeter: &mut RuntimeInstance,
+ interpreter: &mut RuntimeInstance,
exec: wast::WastExecute,
message: &str,
) -> Result<(), Box> {
@@ -512,24 +443,28 @@ fn execute_assert_trap(
.collect::>();
// TODO: more modules ¯\_(ツ)_/¯
- let func_res = interpeter
- .get_function_by_name(DEFAULT_MODULE, invoke_info.name)
- .map_err(|err| Box::new(WasmInterpreterError(wasm::Error::RuntimeError(err))));
+ let func_res = catch_unwind_and_suppress_panic_handler(AssertUnwindSafe(|| {
+ interpreter.get_function_by_name(DEFAULT_MODULE, invoke_info.name)
+ }))
+ .map_err(PanicError::from_panic_boxed)?
+ .map_err(|err| WasmInterpreterError::new_boxed(wasm::Error::RuntimeError(err)));
- let func: FunctionRef;
- match func_res {
+ let func: FunctionRef = match func_res {
Err(e) => {
return Err(e);
}
- Ok(func_ref) => func = func_ref,
+ Ok(func_ref) => func_ref,
};
- let actual = interpeter.invoke_dynamic_unchecked_return_ty(&func, args);
+ let actual = catch_unwind_and_suppress_panic_handler(AssertUnwindSafe(|| {
+ interpreter.invoke_dynamic_unchecked_return_ty(&func, args)
+ }))
+ .map_err(PanicError::from_panic_boxed)?;
match actual {
- Ok(_) => Err(Box::new(GenericError::new("assert_trap did NOT trap"))),
+ Ok(_) => Err(GenericError::new_boxed("assert_trap did NOT trap")),
Err(e) => {
- let actual = to_wasm_testsuite_string(e);
+ let actual = to_wasm_testsuite_string(e)?;
let expected = message;
if actual.contains(expected)
@@ -538,10 +473,10 @@ fn execute_assert_trap(
{
Ok(())
} else {
- Err(Box::new(GenericError::new(
+ Err(GenericError::new_boxed(
format!("'assert_trap': Expected '{expected}' - Actual: '{actual}'")
.as_str(),
- )))
+ ))
}
}
}
@@ -550,10 +485,12 @@ fn execute_assert_trap(
span: _,
module: _,
global: _,
- } => todo!("`get` directive inside `assert_return` not yet implemented"),
- wast::WastExecute::Wat(_) => {
- todo!("`wat` directive inside `assert_return` not yet implemented")
- }
+ } => Err(GenericError::new_boxed(
+ "`get` directive inside `assert_return` not yet implemented",
+ )),
+ wast::WastExecute::Wat(_) => Err(GenericError::new_boxed(
+ "`wat` directive inside `assert_return` not yet implemented",
+ )),
}
}
@@ -590,8 +527,8 @@ pub fn arg_to_value(arg: WastArg) -> Value {
}
}
-fn result_to_value(result: wast::WastRet) -> Value {
- match result {
+fn result_to_value(result: wast::WastRet) -> Result> {
+ let value = match result {
wast::WastRet::Core(core_arg) => match core_arg {
WastRetCore::I32(val) => Value::I32(val as u32),
WastRetCore::I64(val) => Value::I64(val as u64),
@@ -619,46 +556,43 @@ fn result_to_value(result: wast::WastRet) -> Value {
Value::F64(wasm::value::F64(f64::from_bits(val.bits)))
}
},
- WastRetCore::V128(_) => todo!("`V128` result not yet implemented"),
- WastRetCore::RefNull(rref) => match rref {
- None => todo!("RefNull with no type not yet implemented"),
- Some(rref) => match rref {
- wast::core::HeapType::Concrete(_) => {
- unreachable!("Null refs don't point to any specific reference")
- }
- wast::core::HeapType::Abstract { shared: _, ty } => {
- use wasm::value::*;
- use wast::core::AbstractHeapType::*;
- match ty {
- Func => Value::Ref(Ref::Func(FuncAddr::null())),
- Extern => Value::Ref(Ref::Extern(ExternAddr::null())),
- _ => todo!("`GC` proposal not yet implemented"),
- }
+ WastRetCore::RefNull(Some(rref)) => match rref {
+ wast::core::HeapType::Concrete(_) => {
+ unreachable!("Null refs don't point to any specific reference")
+ }
+ wast::core::HeapType::Abstract { shared: _, ty } => {
+ use wasm::value::*;
+ use wast::core::AbstractHeapType::*;
+ match ty {
+ Func => Value::Ref(Ref::Func(FuncAddr::null())),
+ Extern => Value::Ref(Ref::Extern(ExternAddr::null())),
+ _ => todo!("`GC` proposal not yet implemented"),
}
- },
+ }
},
- WastRetCore::RefExtern(_) => {
- todo!("`RefExtern` result not yet implemented")
- }
- WastRetCore::RefHost(_) => todo!("`RefHost` result not yet implemented"),
WastRetCore::RefFunc(index) => match index {
None => unreachable!("Expected a non-null function reference"),
Some(_index) => {
// use wasm::value::*;
// Value::Ref(Ref::Func(FuncAddr::new(Some(index))))
- todo!("RefFuncs not yet implemented")
+
+ return Err(GenericError::new_boxed("RefFuncs not yet implemented"));
}
},
- //todo!("`RefFunc` result not yet implemented"),
- WastRetCore::RefAny => todo!("`RefAny` result not yet implemented"),
- WastRetCore::RefEq => todo!("`RefEq` result not yet implemented"),
- WastRetCore::RefArray => todo!("`RefArray` result not yet implemented"),
- WastRetCore::RefStruct => todo!("`RefStruct` result not yet implemented"),
- WastRetCore::RefI31 => todo!("`RefI31` result not yet implemented"),
- WastRetCore::Either(_) => todo!("`Either` result not yet implemented"),
+ other => {
+ return Err(Box::new(GenericError::new(&format!(
+ "handling of wast ret type {other:?} not yet implemented"
+ ))));
+ }
},
wast::WastRet::Component(_) => todo!("`Component` result not yet implemented"),
- }
+ };
+
+ Ok(value)
+}
+
+pub fn get_linenum(contents: &str, span: wast::token::Span) -> u32 {
+ span.linecol_in(contents).0 as u32 + 1
}
pub fn get_command(contents: &str, span: wast::token::Span) -> &str {
@@ -667,3 +601,12 @@ pub fn get_command(contents: &str, span: wast::token::Span) -> &str {
.next()
.unwrap_or("")
}
+
+pub fn catch_unwind_and_suppress_panic_handler(
+ f: impl FnOnce() -> R + UnwindSafe,
+) -> Result> {
+ std::panic::set_hook(Box::new(|_| {}));
+ let result = std::panic::catch_unwind(f);
+ let _ = std::panic::take_hook();
+ result
+}
diff --git a/tests/specification/test_errors.rs b/tests/specification/test_errors.rs
index 843468e255..a37c4e6b6c 100644
--- a/tests/specification/test_errors.rs
+++ b/tests/specification/test_errors.rs
@@ -41,22 +41,26 @@ impl AssertEqError {
let left_el = left[i];
let right_el = right[i];
- if !match (left_el, right_el) {
+ match (left_el, right_el) {
(Value::F32(a), Value::F32(b)) => {
match_f32(a, b)?;
- true
+ Ok(())
}
(Value::F64(a), Value::F64(b)) => {
match_f64(a, b)?;
- true
+ Ok(())
}
- (a, b) => a == b,
- } {
- return Err(AssertEqError {
- left: format!("{:?}", left),
- right: format!("{:?}", right),
- });
- }
+ (a, b) => {
+ if a != b {
+ Err(AssertEqError {
+ left: format!("{:?}", left),
+ right: format!("{:?}", right),
+ })
+ } else {
+ Ok(())
+ }
+ }
+ }?;
}
Ok(())
}
@@ -80,10 +84,10 @@ fn match_f32(actual: F32, expected: F32) -> Result<(), AssertEqError> {
if (actual_bits & 0x7fff_ffff) == canon_nan {
Ok(())
} else {
- return Err(AssertEqError {
+ Err(AssertEqError {
left: actual_bits.to_string(),
right: canon_nan.to_string(),
- });
+ })
}
}
NanPattern::ArithmeticNan => {
@@ -94,20 +98,20 @@ fn match_f32(actual: F32, expected: F32) -> Result<(), AssertEqError> {
if is_nan && is_msb_set {
Ok(())
} else {
- return Err(AssertEqError {
+ Err(AssertEqError {
left: actual_bits.to_string(),
right: AF32_NAN.to_string(),
- });
+ })
}
}
NanPattern::Value(val) => {
if actual_bits == val {
Ok(())
} else {
- return Err(AssertEqError {
+ Err(AssertEqError {
left: actual_bits.to_string(),
right: val.to_string(),
- });
+ })
}
}
}
@@ -131,10 +135,10 @@ fn match_f64(actual: F64, expected: F64) -> Result<(), AssertEqError> {
if (actual_bits & 0x7fff_ffff_ffff_ffff) == canon_nan {
Ok(())
} else {
- return Err(AssertEqError {
+ Err(AssertEqError {
left: actual_bits.to_string(),
right: canon_nan.to_string(),
- });
+ })
}
}
NanPattern::ArithmeticNan => {
@@ -145,20 +149,20 @@ fn match_f64(actual: F64, expected: F64) -> Result<(), AssertEqError> {
if is_nan && is_msb_set {
Ok(())
} else {
- return Err(AssertEqError {
+ Err(AssertEqError {
left: actual_bits.to_string(),
right: AF64_NAN.to_string(),
- });
+ })
}
}
NanPattern::Value(val) => {
if actual_bits == val {
Ok(())
} else {
- return Err(AssertEqError {
+ Err(AssertEqError {
left: actual_bits.to_string(),
right: val.to_string(),
- });
+ })
}
}
}
@@ -186,6 +190,18 @@ impl PanicError {
message: message.to_string(),
}
}
+
+ pub fn from_panic(panic: Box) -> Self {
+ if let Ok(msg) = panic.downcast::<&str>() {
+ PanicError::new(&msg)
+ } else {
+ PanicError::new("Unknown panic")
+ }
+ }
+
+ pub fn from_panic_boxed(panic: Box) -> Box {
+ Box::new(Self::from_panic(panic))
+ }
}
impl Error for PanicError {}
@@ -198,6 +214,12 @@ impl std::fmt::Display for PanicError {
#[derive(Debug, PartialEq, Eq, Clone)]
pub struct WasmInterpreterError(pub wasm::Error);
+impl WasmInterpreterError {
+ pub fn new_boxed(error: wasm::Error) -> Box {
+ Box::new(Self(error))
+ }
+}
+
impl Error for WasmInterpreterError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match &self.0 {
@@ -220,6 +242,10 @@ impl GenericError {
pub fn new(message: &str) -> Self {
GenericError(message.to_string())
}
+
+ pub fn new_boxed(message: &str) -> Box {
+ Box::new(Self::new(message))
+ }
}
impl Error for GenericError {}