Skip to main content

wasm_testsuite/data/
mod.rs

1use include_dir::{Dir, include_dir};
2use std::{
3    fmt::{Debug, Display},
4    str::FromStr,
5};
6
7static DATA: Dir<'_> = include_dir!("$CARGO_MANIFEST_DIR/data");
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10#[non_exhaustive]
11pub enum Proposal {
12    Annotations,
13    BulkMemoryOperations,
14    CustomPageSizes,
15    CustomDescriptors,
16    ExceptionHandling,
17    ExtendedConst,
18    FunctionReferences,
19    GC,
20    Memory64,
21    MultiMemory,
22    MultiValue,
23    MutableGlobal,
24    NontrappingFloatToIntConversions,
25    ReferenceTypes,
26    RelaxedSimd,
27    SignExtensionOps,
28    Simd,
29    TailCall,
30    Threads,
31    WideArithmetic,
32}
33
34impl Proposal {
35    pub fn all() -> &'static [Proposal] {
36        &[
37            Proposal::Annotations,
38            Proposal::BulkMemoryOperations,
39            Proposal::CustomPageSizes,
40            Proposal::CustomDescriptors,
41            Proposal::ExceptionHandling,
42            Proposal::ExtendedConst,
43            Proposal::FunctionReferences,
44            Proposal::GC,
45            Proposal::Memory64,
46            Proposal::MultiMemory,
47            Proposal::MultiValue,
48            Proposal::MutableGlobal,
49            Proposal::NontrappingFloatToIntConversions,
50            Proposal::ReferenceTypes,
51            Proposal::RelaxedSimd,
52            Proposal::SignExtensionOps,
53            Proposal::Simd,
54            Proposal::TailCall,
55            Proposal::Threads,
56            Proposal::WideArithmetic,
57        ]
58    }
59}
60
61impl Display for Proposal {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        f.write_str((*self).into())
64    }
65}
66
67impl From<Proposal> for &'static str {
68    fn from(proposal: Proposal) -> &'static str {
69        match proposal {
70            Proposal::Annotations => "annotations",
71            Proposal::CustomPageSizes => "custom-page-sizes",
72            Proposal::CustomDescriptors => "custom-descriptors",
73            Proposal::ExceptionHandling => "exceptions",
74            Proposal::ExtendedConst => "extended-const",
75            Proposal::FunctionReferences => "function-references",
76            Proposal::GC => "gc",
77            Proposal::Memory64 => "memory64",
78            Proposal::MultiMemory => "multi-memory",
79            Proposal::Simd => "simd",
80            Proposal::RelaxedSimd => "relaxed-simd",
81            Proposal::TailCall => "tail-call",
82            Proposal::Threads => "threads",
83            Proposal::WideArithmetic => "wide-arithmetic",
84            Proposal::BulkMemoryOperations => "bulk-memory",
85            Proposal::MultiValue => "multi-value",
86            Proposal::MutableGlobal => "mutable-global",
87            Proposal::NontrappingFloatToIntConversions => "nontrapping-float-to-int-conversions",
88            Proposal::ReferenceTypes => "reference-types",
89            Proposal::SignExtensionOps => "sign-extension-ops",
90        }
91    }
92}
93
94impl From<&Proposal> for Proposal {
95    fn from(val: &Proposal) -> Self {
96        *val
97    }
98}
99
100impl FromStr for Proposal {
101    type Err = ();
102
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        Ok(match s {
105            "annotations" => Proposal::Annotations,
106            "custom-page-sizes" => Proposal::CustomPageSizes,
107            "custom-descriptors" => Proposal::CustomDescriptors,
108            "exception-handling" | "exceptions" => Proposal::ExceptionHandling,
109            "extended-const" => Proposal::ExtendedConst,
110            "function-references" => Proposal::FunctionReferences,
111            "gc" => Proposal::GC,
112            "memory64" => Proposal::Memory64,
113            "multi-memory" => Proposal::MultiMemory,
114            "simd" => Proposal::Simd,
115            "relaxed-simd" => Proposal::RelaxedSimd,
116            "tail-call" => Proposal::TailCall,
117            "threads" => Proposal::Threads,
118            "wide-arithmetic" => Proposal::WideArithmetic,
119            "bulk-memory-operations" | "bulk-memory" => Proposal::BulkMemoryOperations,
120            "multi-value" => Proposal::MultiValue,
121            "mutable-global" => Proposal::MutableGlobal,
122            "nontrapping-float-to-int-conversions" => Proposal::NontrappingFloatToIntConversions,
123            "reference-types" => Proposal::ReferenceTypes,
124            "sign-extension-ops" => Proposal::SignExtensionOps,
125            _ => return Err(()),
126        })
127    }
128}
129
130#[derive(Debug, Clone, Copy)]
131#[non_exhaustive]
132pub enum SpecVersion {
133    V1,
134    V2,
135    V3,
136    Latest,
137}
138
139impl From<&SpecVersion> for SpecVersion {
140    fn from(val: &SpecVersion) -> Self {
141        *val
142    }
143}
144
145impl SpecVersion {
146    fn name(self) -> &'static str {
147        match self {
148            Self::V1 => "wasm-v1",
149            Self::V2 => "wasm-v2",
150            Self::V3 => "wasm-v3",
151            Self::Latest => "wasm-latest",
152        }
153    }
154
155    pub fn all() -> &'static [SpecVersion] {
156        &[SpecVersion::V1, SpecVersion::V2, SpecVersion::V3, SpecVersion::Latest]
157    }
158}
159
160/// Get all test files associated with a proposal
161pub fn proposal(name: impl Into<Proposal>) -> impl Iterator<Item = TestFile<'static>> {
162    let name: &'static str = name.into().into();
163    let tests = DATA.get_dir(format!("proposals/{name}")).expect("spec dir should always exist");
164
165    tests.files().map(|file| TestFile {
166        parent: name.to_string(),
167        name: file.path().file_name().unwrap_or_default().to_string_lossy().to_string(),
168        contents: file.contents_utf8().expect("file should be utf8"),
169    })
170}
171
172/// Get all test files associated with a spec version
173pub fn spec(version: impl Into<SpecVersion>) -> impl Iterator<Item = TestFile<'static>> {
174    let name = version.into().name();
175    let tests = DATA.get_dir(name).expect("spec dir should always exist");
176
177    tests.files().map(|file| TestFile {
178        parent: name.to_string(),
179        name: file.path().file_name().unwrap_or_default().to_string_lossy().to_string(),
180        contents: file.contents_utf8().expect("file should be utf8"),
181    })
182}
183
184/// A test file
185#[derive(Debug)]
186pub struct TestFile<'a> {
187    pub parent: String,
188    pub name: String,
189    pub contents: &'a str,
190}
191
192impl<'a> TestFile<'a> {
193    /// Get the name of the test file
194    pub fn name(&self) -> &str {
195        &self.name
196    }
197
198    /// Get the parent of the test file (either a proposal or a spec version)
199    pub fn parent(&self) -> &str {
200        &self.parent
201    }
202
203    /// Get the raw contents of the test file
204    pub fn raw(&self) -> &'a str {
205        self.contents
206    }
207
208    #[cfg(feature = "wast")]
209    /// Parse the contents of the test file
210    pub fn wast(&self) -> wast::parser::Result<WastBuffer<'a>> {
211        let mut lexer = wast::lexer::Lexer::new(self.contents);
212        lexer.allow_confusing_unicode(true);
213        let parse_buffer = wast::parser::ParseBuffer::new_with_lexer(lexer)?;
214
215        Ok(WastBuffer { buffer: parse_buffer })
216    }
217}
218
219/// A wrapper around [`wast::parser::ParseBuffer`]
220pub struct WastBuffer<'a> {
221    // this wrapper struct is necessary to not use self-referential structs as WastDirective
222    // contains a reference to the buffer which contains a reference to the original file data.
223    buffer: wast::parser::ParseBuffer<'a>,
224}
225
226impl Debug for WastBuffer<'_> {
227    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
228        f.debug_struct("WastBuffer").finish()
229    }
230}
231
232impl<'a> WastBuffer<'a> {
233    // Get the wast directives from the buffer
234    pub fn directives(&'a self) -> wast::parser::Result<Vec<wast::WastDirective<'a>>> {
235        Ok(wast::parser::parse::<wast::Wast<'a>>(&self.buffer)?.directives)
236    }
237}
238
239#[cfg(test)]
240mod tests {
241    use super::*;
242
243    #[test]
244    fn test_enum() {
245        for p in Proposal::all() {
246            let name = p.to_string();
247            let parsed = Proposal::from_str(&name).expect("Failed to parse proposal");
248            assert_eq!(*p, parsed);
249        }
250    }
251
252    #[test]
253    fn test_proposals() {
254        for p in Proposal::all() {
255            for test in proposal(p) {
256                if let Err(e) = test.wast().expect("Failed to lex wast").directives() {
257                    panic!("Failed to parse wast for {}/{}: {e:?}", test.parent, test.name);
258                }
259            }
260        }
261    }
262
263    #[test]
264    fn test_spec_versions() {
265        for v in SpecVersion::all() {
266            for test in spec(v) {
267                if let Err(e) = test.wast().expect("Failed to lex wast").directives() {
268                    panic!("Failed to parse wast: {e:?}, {test:?}");
269                }
270            }
271        }
272    }
273}