Skip to main content

wasmtime_test_util/
wast.rs

1use serde::de::DeserializeOwned;
2use serde_derive::Deserialize;
3use std::fmt;
4use std::fs;
5use std::path::Path;
6use std::path::PathBuf;
7use wasmtime_environ::prelude::*;
8
9/// Limits for running wast tests.
10///
11/// This is useful for sharing between `tests/wast.rs` and fuzzing, for
12/// example, and is used as the minimum threshold for configuration when
13/// fuzzing.
14///
15/// Note that it's ok to increase these numbers if a test comes along and needs
16/// it, they're just here as empirically found minimum thresholds so far and
17/// they're not too scientific.
18pub mod limits {
19    pub const MEMORY_SIZE: usize = 805 << 16;
20    pub const MEMORIES: u32 = 450;
21    pub const GC_HEAP_SIZE: usize = 2 << 20; // 2MiB
22    pub const TABLES: u32 = 200;
23    pub const MEMORIES_PER_MODULE: u32 = 9;
24    pub const TABLES_PER_MODULE: u32 = 5;
25    pub const COMPONENT_INSTANCES: u32 = 50;
26    pub const CORE_INSTANCES: u32 = 900;
27    pub const TABLE_ELEMENTS: usize = 1000;
28    pub const CORE_INSTANCE_SIZE: usize = 64 * 1024;
29    pub const TOTAL_STACKS: u32 = 20;
30}
31
32/// Local all `*.wast` tests under `root` which should be the path to the root
33/// of the wasmtime repository.
34pub fn find_tests(root: &Path) -> Result<Vec<WastTest>> {
35    let mut tests = Vec::new();
36
37    let spec_tests = root.join("tests/spec_testsuite");
38    add_tests(
39        &mut tests,
40        &spec_tests,
41        &FindConfig::Infer(spec_test_config),
42    )
43    .context("Do you need to `git submodule update --init`?")
44    .with_context(|| format!("failed to add tests from `{}`", spec_tests.display()))?;
45
46    let misc_tests = root.join("tests/misc_testsuite");
47    add_tests(&mut tests, &misc_tests, &FindConfig::InTest)
48        .with_context(|| format!("failed to add tests from `{}`", misc_tests.display()))?;
49
50    let cm_tests = root.join("tests/component-model/test");
51    add_tests(
52        &mut tests,
53        &cm_tests,
54        &FindConfig::Infer(component_test_config),
55    )
56    .context("Do you need to `git submodule update --init`?")
57    .with_context(|| format!("failed to add tests from `{}`", cm_tests.display()))?;
58
59    // Temporarily work around upstream tests that fail in unexpected ways (e.g.
60    // panics, loops, etc).
61    {
62        let skip_list = &[
63            // .. empty currently ..
64        ];
65        tests.retain(|test| {
66            test.path
67                .file_name()
68                .and_then(|name| name.to_str())
69                .map(|name| !skip_list.contains(&name))
70                .unwrap_or(true)
71        });
72    }
73
74    Ok(tests)
75}
76
77enum FindConfig {
78    InTest,
79    Infer(fn(&Path) -> TestConfig),
80}
81
82fn add_tests(tests: &mut Vec<WastTest>, path: &Path, config: &FindConfig) -> Result<()> {
83    for entry in path.read_dir().context("failed to read directory")? {
84        let entry = entry.context("failed to read directory entry")?;
85        let path = entry.path();
86        if entry
87            .file_type()
88            .context("failed to get file type")?
89            .is_dir()
90        {
91            add_tests(tests, &path, config).context("failed to read sub-directory")?;
92            continue;
93        }
94
95        if path.extension().and_then(|s| s.to_str()) != Some("wast") {
96            continue;
97        }
98
99        // These tests use `*.wast` directives not yet supported by Wasmtime, so
100        // wait for a `wasm-tools` update to ungate these.
101        if path.ends_with("spec_testsuite/custom/custom_annot.wast")
102            || path.ends_with("spec_testsuite/custom/branch_hint.wast")
103            || path.ends_with("spec_testsuite/custom/name_annot.wast")
104        {
105            continue;
106        }
107
108        let contents =
109            fs::read_to_string(&path).with_context(|| format!("failed to read test: {path:?}"))?;
110        let config = match config {
111            FindConfig::InTest => parse_test_config(&contents, ";;!")
112                .with_context(|| format!("failed to parse test configuration: {path:?}"))?,
113            FindConfig::Infer(f) => f(&path),
114        };
115        tests.push(WastTest {
116            path,
117            contents,
118            config,
119        })
120    }
121    Ok(())
122}
123
124fn spec_test_config(test: &Path) -> TestConfig {
125    let mut ret = TestConfig::default();
126    ret.spec_test = Some(true);
127    ret.bulk_memory = Some(true);
128    match spec_proposal_from_path(test) {
129        Some("wide-arithmetic") => {
130            ret.wide_arithmetic = Some(true);
131        }
132        Some("threads") => {
133            ret.threads = Some(true);
134            ret.reference_types = Some(false);
135        }
136        Some("custom-page-sizes") => {
137            ret.custom_page_sizes = Some(true);
138            ret.multi_memory = Some(true);
139            ret.memory64 = Some(true);
140            ret.reference_types = Some(true);
141
142            // See commentary below in `wasm-3.0` case for why these "hog
143            // memory"
144            if test.ends_with("memory_max.wast") || test.ends_with("memory_max_i64.wast") {
145                ret.hogs_memory = Some(true);
146            }
147        }
148        Some("custom-descriptors") => {
149            ret.custom_descriptors = Some(true);
150        }
151        Some("compact-import-section") => {
152            ret.compact_imports = Some(true);
153            ret.reference_types = Some(true);
154            ret.multi_memory = Some(true);
155            ret.exceptions = Some(true);
156        }
157        Some("extended-name-section") => {
158            ret.gc = Some(true);
159            ret.exceptions = Some(true);
160        }
161        Some(proposal) => panic!("unsupported proposal {proposal:?}"),
162
163        // The rough goal here is to enable a minimal set of features for the
164        // spec test in question. The fewer features required the more
165        // configurations this can run on. For example Winch doesn't currently
166        // support GC but it can still run all the simd tests which don't use
167        // GC. In general the upstream spec tests are not silo'd by feature
168        // meaning that one test probably requires a whole bunch of proposals.
169        // The tests are also quite large meaning the entire test may not need
170        // a particular feature, but there's not much we can do about that.
171        //
172        // What follows is a coarse algorithm of guessing, based on a test's
173        // name, what features are needed. The upstream spec test suite is a
174        // frozen commit in a git submodule within this repo meaning that this
175        // should work but will need updates when new tests are added or
176        // preexisting tests are changed.
177        None => {
178            let test_name = test.file_name().unwrap().to_str().unwrap();
179            ret.reference_types = Some(true);
180            ret.multi_memory = Some(true);
181            if test_name.contains("simd") {
182                ret.simd = Some(true);
183            }
184            if test_name.contains("relaxed") {
185                ret.relaxed_simd = Some(true);
186            }
187            if test_name.contains("64") || test_name.contains("mixed") {
188                ret.memory64 = Some(true);
189            }
190            if test_name.contains("ref")
191                || test_name.contains("array")
192                || test_name.contains("struct")
193                || test_name.contains("table")
194                || test_name.contains("type")
195                || test_name.contains("tag")
196                || test_name.contains("extern")
197                || test_name.contains("br_on_")
198                || test_name.contains("linking")
199                || test_name.contains("instance")
200                || test_name.contains("local_init")
201                || test_name.contains("elem")
202                || test_name.contains("global")
203                || test_name.contains("i31")
204                || test_name.contains("unreached-valid")
205                || test_name.contains("select")
206                || test_name.contains("data")
207                || test_name.contains("cast")
208            {
209                ret.gc = Some(true);
210            }
211            if test_name == "throw_ref.wast" {
212                // This test only uses exception references, which do not
213                // require enabling the GC proposal.
214                ret.gc = Some(false);
215            }
216            if test_name.contains("return_") || test_name.contains("try_table") {
217                ret.tail_call = Some(true);
218            }
219            if test_name == "return_call.wast" || test_name == "return_call_indirect.wast" {
220                ret.function_references = Some(true);
221            }
222            if test_name.contains("tag")
223                || test_name.contains("try_table")
224                || test_name.contains("throw")
225                || test_name.contains("ref")
226                || test_name.contains("instance")
227                || test_name.contains("imports")
228                || test_name.contains("cast")
229            {
230                ret.exceptions = Some(true);
231            }
232            if test_name.contains("global")
233                || test_name.contains("elem")
234                || test_name.contains("data")
235            {
236                ret.extended_const = Some(true);
237            }
238
239            if test.parent().unwrap().ends_with("legacy") {
240                ret.legacy_exceptions = Some(true);
241            }
242
243            // These tests technically don't actually hog any memory but they
244            // do have a module definition with a table/memory that is the
245            // maximum size. These modules fail to compile in the pooling
246            // allocator which has limits on the minimum size of
247            // memories/tables by default.
248            //
249            // Pretend that these hog memory to avoid running the tests in the
250            // pooling allocator.
251            if test.ends_with("memory.wast")
252                || test.ends_with("table.wast")
253                || test.ends_with("memory64.wast")
254                || test.ends_with("table64.wast")
255            {
256                ret.hogs_memory = Some(true);
257            }
258        }
259    }
260
261    ret
262}
263
264fn component_test_config(test: &Path) -> TestConfig {
265    let mut ret = TestConfig::default();
266    ret.spec_test = Some(true);
267    ret.reference_types = Some(true);
268    ret.multi_memory = Some(true);
269    ret.component_model_implements = Some(true);
270    ret.component_model_canonical_names = Some(true);
271    ret.component_model_accessors = Some(true);
272    ret.bulk_memory = Some(true);
273    ret.component_model_async = Some(true);
274    ret.component_model_more_async_builtins = Some(true);
275    ret.component_model_async_stackful = Some(true);
276    ret.component_model_threading = Some(true);
277    ret.gc = Some(true);
278    ret.exceptions = Some(true);
279    ret.component_model_map = Some(true);
280    ret.component_model_fixed_length_lists = Some(true);
281
282    if test.ends_with("memory64.wast") {
283        ret.component_model_memory64 = Some(true);
284    }
285
286    if let Some(parent) = test.parent() {
287        if parent.ends_with("wasm-tools") {
288            ret.memory64 = Some(true);
289            ret.threads = Some(true);
290            ret.exceptions = Some(true);
291            ret.gc = Some(true);
292        }
293        if parent.ends_with("wasmtime") {
294            ret.exceptions = Some(true);
295            ret.gc = Some(true);
296        }
297    }
298
299    ret
300}
301
302/// Parse test configuration from the specified test, comments starting with
303/// `;;!`.
304pub fn parse_test_config<T>(wat: &str, comment: &'static str) -> Result<T>
305where
306    T: DeserializeOwned,
307{
308    // The test config source is the leading lines of the WAT file that are
309    // prefixed with `;;!`.
310    let config_lines: Vec<_> = wat
311        .lines()
312        .take_while(|l| l.starts_with(comment))
313        .map(|l| &l[comment.len()..])
314        .collect();
315    let config_text = config_lines.join("\n");
316
317    toml::from_str(&config_text).context("failed to parse the test configuration")
318}
319
320/// A `*.wast` test with its path, contents, and configuration.
321#[derive(Clone)]
322pub struct WastTest {
323    pub path: PathBuf,
324    pub contents: String,
325    pub config: TestConfig,
326}
327
328impl fmt::Debug for WastTest {
329    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
330        f.debug_struct("WastTest")
331            .field("path", &self.path)
332            .field("contents", &"...")
333            .field("config", &self.config)
334            .finish()
335    }
336}
337
338macro_rules! foreach_config_option {
339    ($m:ident) => {
340        $m! {
341            bulk_memory
342            memory64
343            custom_page_sizes
344            multi_memory
345            threads
346            shared_everything_threads
347            gc
348            function_references
349            relaxed_simd
350            reference_types
351            tail_call
352            extended_const
353            wide_arithmetic
354            branch_hinting
355            compact_imports
356            hogs_memory
357            nan_canonicalization
358            component_model_async
359            component_model_more_async_builtins
360            component_model_async_stackful
361            component_model_threading
362            component_model_error_context
363            component_model_gc
364            component_model_map
365            component_model_memory64
366            component_model_fixed_length_lists
367            component_model_implements
368            component_model_canonical_names
369            component_model_accessors
370            simd
371            gc_types
372            exceptions
373            legacy_exceptions
374            stack_switching
375            spec_test
376            custom_descriptors
377        }
378    };
379}
380
381macro_rules! define_test_config {
382    ($($option:ident)*) => {
383        /// Per-test configuration which is written down in the test file itself for
384        /// `misc_testsuite/**/*.wast` or in `spec_test_config` above for spec tests.
385        #[derive(Debug, PartialEq, Default, Deserialize, Clone)]
386        #[serde(deny_unknown_fields)]
387        pub struct TestConfig {
388            $(pub $option: Option<bool>,)*
389        }
390
391        impl TestConfig {
392            $(
393                pub fn $option(&self) -> bool {
394                    self.$option.unwrap_or(false)
395                }
396            )*
397        }
398    }
399}
400
401foreach_config_option!(define_test_config);
402
403impl TestConfig {
404    /// Returns an iterator over each option.
405    pub fn options_mut(&mut self) -> impl Iterator<Item = (&'static str, &mut Option<bool>)> {
406        macro_rules! mk {
407            ($($option:ident)*) => {
408                [
409                    $((stringify!($option), &mut self.$option),)*
410                ].into_iter()
411            }
412        }
413        foreach_config_option!(mk)
414    }
415}
416
417/// Configuration that spec tests can run under.
418#[derive(Debug)]
419pub struct WastConfig {
420    /// Compiler chosen to run this test.
421    pub compiler: Compiler,
422    /// Whether or not the pooling allocator is enabled.
423    pub pooling: bool,
424    /// What garbage collector is being used.
425    pub collector: Collector,
426}
427
428/// Different compilers that can be tested in Wasmtime.
429#[derive(PartialEq, Debug, Copy, Clone)]
430pub enum Compiler {
431    /// Cranelift backend.
432    ///
433    /// This tests the Cranelift code generator for native platforms. This
434    /// notably excludes Pulley since that's listed separately below even though
435    /// Pulley is a backend of Cranelift. This is only used for native code
436    /// generation such as x86_64.
437    CraneliftNative,
438
439    /// Winch backend.
440    ///
441    /// This tests the Winch backend for native platforms. Currently Winch
442    /// primarily supports x86_64.
443    Winch,
444
445    /// Pulley interpreter.
446    ///
447    /// This tests the Cranelift pulley backend plus the pulley execution
448    /// environment of the output bytecode. Note that this is separate from
449    /// `Cranelift` above to be able to test both on platforms where Cranelift
450    /// has native codegen support.
451    CraneliftPulley,
452}
453
454impl Compiler {
455    /// Returns whether this compiler is known to fail for the provided
456    /// `TestConfig`.
457    ///
458    /// This function will determine if the configuration of the test provided
459    /// is known to guarantee fail. This effectively tracks the proposal support
460    /// for each compiler backend/runtime and tests whether `config` enables or
461    /// disables features that aren't supported.
462    ///
463    /// Note that this is closely aligned with
464    /// `Config::compiler_panicking_wasm_features`.
465    pub fn should_fail(&self, config: &TestConfig) -> bool {
466        match self {
467            Compiler::CraneliftNative => {
468                if config.legacy_exceptions() {
469                    return true;
470                }
471
472                // Stack-switching is only implemented on x86_64 for unix
473                // platforms right now.
474                if config.stack_switching() && !(cfg!(target_arch = "x86_64") && cfg!(unix)) {
475                    return true;
476                }
477
478                false
479            }
480
481            Compiler::Winch => {
482                if config.gc()
483                    || config.function_references()
484                    || config.relaxed_simd()
485                    || config.legacy_exceptions()
486                    || config.stack_switching()
487                {
488                    return true;
489                }
490
491                if cfg!(target_arch = "aarch64") {
492                    return config.threads();
493                }
494
495                !cfg!(target_arch = "x86_64")
496            }
497
498            Compiler::CraneliftPulley => {
499                config.threads() || config.legacy_exceptions() || config.stack_switching()
500            }
501        }
502    }
503
504    /// Returns whether this compiler configuration supports the current host
505    /// architecture.
506    pub fn supports_host(&self) -> bool {
507        match self {
508            Compiler::CraneliftNative => {
509                cfg!(target_arch = "x86_64")
510                    || cfg!(target_arch = "aarch64")
511                    || cfg!(target_arch = "riscv64")
512                    || cfg!(target_arch = "s390x")
513            }
514            Compiler::Winch => cfg!(target_arch = "x86_64") || cfg!(target_arch = "aarch64"),
515            Compiler::CraneliftPulley => true,
516        }
517    }
518}
519
520#[derive(PartialEq, Debug, Copy, Clone)]
521pub enum Collector {
522    Auto,
523    Null,
524    DeferredReferenceCounting,
525    Copying,
526}
527
528impl WastTest {
529    /// Returns whether this test exercises the GC types and might want to use
530    /// multiple different garbage collectors.
531    pub fn test_uses_gc_types(&self) -> bool {
532        self.config.gc() || self.config.function_references()
533    }
534
535    /// Returns the optional spec proposal that this test is associated with.
536    pub fn spec_proposal(&self) -> Option<&str> {
537        spec_proposal_from_path(&self.path)
538    }
539
540    /// Returns whether this test should fail under the specified extra
541    /// configuration.
542    pub fn should_fail(&self, config: &WastConfig) -> bool {
543        if !config.compiler.supports_host() {
544            return true;
545        }
546
547        // Some tests are known to fail with the pooling allocator
548        if config.pooling {
549            // allocates too much memory for the pooling configuration here
550            if self.config.hogs_memory() {
551                return true;
552            }
553            let unsupported = [
554                // shared memories + pooling allocator aren't supported yet
555                "misc_testsuite/memory-combos.wast",
556                "misc_testsuite/threads/atomics-end-of-memory.wast",
557                "misc_testsuite/threads/atomic_wait_endianness.wast",
558                "misc_testsuite/threads/LB.wast",
559                "misc_testsuite/threads/LB_atomic.wast",
560                "misc_testsuite/threads/MP.wast",
561                "misc_testsuite/threads/MP_atomic.wast",
562                "misc_testsuite/threads/MP_wait.wast",
563                "misc_testsuite/threads/SB.wast",
564                "misc_testsuite/threads/SB_atomic.wast",
565                "misc_testsuite/threads/atomics_notify.wast",
566                "misc_testsuite/threads/atomics_wait_address.wast",
567                "misc_testsuite/threads/wait_notify.wast",
568                "spec_testsuite/proposals/threads/atomic.wast",
569                "spec_testsuite/proposals/threads/exports.wast",
570                "spec_testsuite/proposals/threads/memory.wast",
571                "misc_testsuite/memory64/threads.wast",
572                "misc_testsuite/winch/rmw32_cmpxchg_u_wrap.wast",
573            ];
574
575            if unsupported.iter().any(|part| self.path.ends_with(part)) {
576                return true;
577            }
578        }
579
580        if config.compiler.should_fail(&self.config) {
581            return true;
582        }
583
584        // Wasmtime doesn't implement `assert_{invalid,malformed}_custom`
585        let unsupported = [
586            "spec_testsuite/proposals/extended-name-section/custom/name_annot.wast",
587            "spec_testsuite/proposals/extended-name-section/custom/name.wast",
588        ];
589        if unsupported.iter().any(|part| self.path.ends_with(part)) {
590            return true;
591        }
592
593        // Disable spec tests per target for proposals that Winch does not implement yet.
594        if config.compiler == Compiler::Winch {
595            // Common list for tests that fail in all targets supported by Winch.
596            let unsupported = [
597                "extended-const/elem.wast",
598                "extended-const/global.wast",
599                "misc_testsuite/externref-segments.wast",
600                "misc_testsuite/externref-table-dropped-segment-issue-8281.wast",
601                "misc_testsuite/many_table_gets_lead_to_gc.wast",
602                "misc_testsuite/no-panic.wast",
603            ];
604
605            if unsupported.iter().any(|part| self.path.ends_with(part)) {
606                return true;
607            }
608
609            #[cfg(target_arch = "x86_64")]
610            {
611                // SIMD on Winch requires AVX instructions.
612                #[cfg(target_arch = "x86_64")]
613                if !(std::is_x86_feature_detected!("avx") && std::is_x86_feature_detected!("avx2"))
614                {
615                    let unsupported = [
616                        "annotations/simd_lane.wast",
617                        "memory64/simd.wast",
618                        "misc_testsuite/int-to-float-splat.wast",
619                        "misc_testsuite/issue6562.wast",
620                        "misc_testsuite/simd/almost-extmul.wast",
621                        "misc_testsuite/simd/canonicalize-nan.wast",
622                        "misc_testsuite/simd/cvt-from-uint.wast",
623                        "misc_testsuite/simd/edge-of-memory.wast",
624                        "misc_testsuite/simd/issue_3327_bnot_lowering.wast",
625                        "misc_testsuite/simd/issue6725-no-egraph-panic.wast",
626                        "misc_testsuite/simd/replace-lane-preserve.wast",
627                        "misc_testsuite/simd/spillslot-size-fuzzbug.wast",
628                        "misc_testsuite/simd/sse-cannot-fold-unaligned-loads.wast",
629                        "misc_testsuite/winch/issue-10331.wast",
630                        "misc_testsuite/int-to-float-splat.wast",
631                        "misc_testsuite/simd/cvt-from-uint.wast",
632                        "misc_testsuite/winch/replace_lane.wast",
633                        "misc_testsuite/simd/riscv64-replicated-imm5-works.wast",
634                        "misc_testsuite/simd/v128-equal.wast",
635                        "misc_testsuite/winch/issue-10331.wast",
636                        "misc_testsuite/int-to-float-splat.wast",
637                        "misc_testsuite/simd/cvt-from-uint.wast",
638                        "spec_testsuite/simd_align.wast",
639                        "spec_testsuite/simd_boolean.wast",
640                        "spec_testsuite/simd_conversions.wast",
641                        "spec_testsuite/simd_f32x4.wast",
642                        "spec_testsuite/simd_f32x4_arith.wast",
643                        "spec_testsuite/simd_f32x4_cmp.wast",
644                        "spec_testsuite/simd_f32x4_pmin_pmax.wast",
645                        "spec_testsuite/simd_f32x4_rounding.wast",
646                        "spec_testsuite/simd_f64x2.wast",
647                        "spec_testsuite/simd_f64x2_arith.wast",
648                        "spec_testsuite/simd_f64x2_cmp.wast",
649                        "spec_testsuite/simd_f64x2_pmin_pmax.wast",
650                        "spec_testsuite/simd_f64x2_rounding.wast",
651                        "spec_testsuite/simd_i16x8_cmp.wast",
652                        "spec_testsuite/simd_i32x4_cmp.wast",
653                        "spec_testsuite/simd_i64x2_arith2.wast",
654                        "spec_testsuite/simd_i64x2_cmp.wast",
655                        "spec_testsuite/simd_i8x16_arith2.wast",
656                        "spec_testsuite/simd_i8x16_cmp.wast",
657                        "spec_testsuite/simd_int_to_int_extend.wast",
658                        "spec_testsuite/simd_load.wast",
659                        "spec_testsuite/simd_load_extend.wast",
660                        "spec_testsuite/simd_load_splat.wast",
661                        "spec_testsuite/simd_load_zero.wast",
662                        "spec_testsuite/simd_splat.wast",
663                        "spec_testsuite/simd_store16_lane.wast",
664                        "spec_testsuite/simd_store32_lane.wast",
665                        "spec_testsuite/simd_store64_lane.wast",
666                        "spec_testsuite/simd_store8_lane.wast",
667                        "spec_testsuite/simd_load16_lane.wast",
668                        "spec_testsuite/simd_load32_lane.wast",
669                        "spec_testsuite/simd_load64_lane.wast",
670                        "spec_testsuite/simd_load8_lane.wast",
671                        "spec_testsuite/simd_bitwise.wast",
672                        "misc_testsuite/simd/load_splat_out_of_bounds.wast",
673                        "misc_testsuite/simd/unaligned-load.wast",
674                        "misc_testsuite/simd/riscv64-replicated-imm5-works.wast",
675                        "misc_testsuite/simd/issue6725-no-egraph-panic.wast",
676                        "misc_testsuite/winch/replace_lane.wast",
677                        "misc_testsuite/simd/v128-equal.wast",
678                        "misc_testsuite/winch/issue-10331.wast",
679                        "misc_testsuite/int-to-float-splat.wast",
680                        "misc_testsuite/simd/cvt-from-uint.wast",
681                        "spec_testsuite/simd_memory-multi.wast",
682                        "misc_testsuite/simd/issue4807.wast",
683                        "spec_testsuite/simd_const.wast",
684                        "spec_testsuite/simd_i8x16_sat_arith.wast",
685                        "spec_testsuite/simd_i64x2_arith.wast",
686                        "spec_testsuite/simd_i16x8_arith.wast",
687                        "spec_testsuite/simd_i16x8_arith2.wast",
688                        "spec_testsuite/simd_i16x8_q15mulr_sat_s.wast",
689                        "spec_testsuite/simd_i16x8_sat_arith.wast",
690                        "spec_testsuite/simd_i32x4_arith.wast",
691                        "spec_testsuite/simd_i32x4_dot_i16x8.wast",
692                        "spec_testsuite/simd_i32x4_trunc_sat_f32x4.wast",
693                        "spec_testsuite/simd_i32x4_trunc_sat_f64x2.wast",
694                        "spec_testsuite/simd_i8x16_arith.wast",
695                        "spec_testsuite/simd_bit_shift.wast",
696                        "spec_testsuite/simd_lane.wast",
697                        "spec_testsuite/simd_i16x8_extmul_i8x16.wast",
698                        "spec_testsuite/simd_i32x4_extmul_i16x8.wast",
699                        "spec_testsuite/simd_i64x2_extmul_i32x4.wast",
700                        "spec_testsuite/simd_i16x8_extadd_pairwise_i8x16.wast",
701                        "spec_testsuite/simd_i32x4_extadd_pairwise_i16x8.wast",
702                        "spec_testsuite/simd_i32x4_arith2.wast",
703                    ];
704
705                    if unsupported.iter().any(|part| self.path.ends_with(part)) {
706                        return true;
707                    }
708                }
709            }
710        }
711
712        // Not implemented in Wasmtime anywhere yet.
713        if self.config.custom_descriptors() {
714            let happens_to_work =
715                ["spec_testsuite/proposals/custom-descriptors/binary-leb128.wast"];
716
717            if happens_to_work.iter().any(|part| self.path.ends_with(part)) {
718                return false;
719            }
720            return true;
721        }
722
723        // `stream.forward` not yet implemented:
724        let uses_stream_forward = [
725            "component-model/test/values/post-return.wast",
726            "component-model/test/async/big-interleaving-test.wast",
727            "component-model/test/async/forward.wast",
728        ];
729
730        if uses_stream_forward
731            .iter()
732            .any(|part| self.path.ends_with(part))
733        {
734            return true;
735        }
736
737        // Obsolete test to be removed in https://github.com/WebAssembly/component-model/pull/726
738        if self
739            .path
740            .ends_with("component-model/test/async/cancel-instance-wide-resume.wast")
741        {
742            return true;
743        }
744
745        false
746    }
747}
748
749fn spec_proposal_from_path(path: &Path) -> Option<&str> {
750    let mut iter = path.iter();
751    loop {
752        match iter.next()?.to_str()? {
753            "proposals" => break,
754            _ => {}
755        }
756    }
757    Some(iter.next()?.to_str()?)
758}