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        &[
157            SpecVersion::V1,
158            SpecVersion::V2,
159            SpecVersion::V3,
160            SpecVersion::Latest,
161        ]
162    }
163}
164
165/// Get all test files associated with a proposal
166pub fn proposal(name: impl Into<Proposal>) -> impl Iterator<Item = TestFile<'static>> {
167    let name: &'static str = name.into().into();
168    let tests = DATA
169        .get_dir(format!("proposals/{name}"))
170        .expect("spec dir should always exist");
171
172    tests.files().map(|file| TestFile {
173        parent: name.to_string(),
174        name: file
175            .path()
176            .file_name()
177            .unwrap_or_default()
178            .to_string_lossy()
179            .to_string(),
180        contents: file.contents_utf8().expect("file should be utf8"),
181    })
182}
183
184/// Get all test files associated with a spec version
185pub fn spec(version: impl Into<SpecVersion>) -> impl Iterator<Item = TestFile<'static>> {
186    let name = version.into().name();
187    let tests = DATA.get_dir(name).expect("spec dir should always exist");
188
189    tests.files().map(|file| TestFile {
190        parent: name.to_string(),
191        name: file
192            .path()
193            .file_name()
194            .unwrap_or_default()
195            .to_string_lossy()
196            .to_string(),
197        contents: file.contents_utf8().expect("file should be utf8"),
198    })
199}
200
201/// A test file
202#[derive(Debug)]
203pub struct TestFile<'a> {
204    pub parent: String,
205    pub name: String,
206    pub contents: &'a str,
207}
208
209impl<'a> TestFile<'a> {
210    /// Get the name of the test file
211    pub fn name(&self) -> &str {
212        &self.name
213    }
214
215    /// Get the parent of the test file (either a proposal or a spec version)
216    pub fn parent(&self) -> &str {
217        &self.parent
218    }
219
220    /// Get the raw contents of the test file
221    pub fn raw(&self) -> &'a str {
222        self.contents
223    }
224
225    #[cfg(feature = "wast")]
226    /// Parse the contents of the test file
227    pub fn wast(&self) -> wast::parser::Result<WastBuffer<'a>> {
228        let mut lexer = wast::lexer::Lexer::new(self.contents);
229        lexer.allow_confusing_unicode(true);
230        let parse_buffer = wast::parser::ParseBuffer::new_with_lexer(lexer)?;
231
232        Ok(WastBuffer {
233            buffer: parse_buffer,
234        })
235    }
236}
237
238/// A wrapper around [`wast::parser::ParseBuffer`]
239pub struct WastBuffer<'a> {
240    // this wrapper struct is necessary to not use self-referential structs as WastDirective
241    // contains a reference to the buffer which contains a reference to the original file data.
242    buffer: wast::parser::ParseBuffer<'a>,
243}
244
245impl Debug for WastBuffer<'_> {
246    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247        f.debug_struct("WastBuffer").finish()
248    }
249}
250
251impl<'a> WastBuffer<'a> {
252    // Get the wast directives from the buffer
253    pub fn directives(&'a self) -> wast::parser::Result<Vec<wast::WastDirective<'a>>> {
254        Ok(wast::parser::parse::<wast::Wast<'a>>(&self.buffer)?.directives)
255    }
256}
257
258#[cfg(test)]
259mod tests {
260    use super::*;
261
262    #[test]
263    fn test_enum() {
264        for p in Proposal::all() {
265            let name = p.to_string();
266            let parsed = Proposal::from_str(&name).expect("Failed to parse proposal");
267            assert_eq!(*p, parsed);
268        }
269    }
270
271    #[test]
272    fn test_proposals() {
273        for p in Proposal::all() {
274            for test in proposal(p) {
275                if let Err(e) = test.wast().expect("Failed to lex wast").directives() {
276                    panic!(
277                        "Failed to parse wast for {}/{}: {e:?}",
278                        test.parent, test.name
279                    );
280                }
281            }
282        }
283    }
284
285    #[test]
286    fn test_spec_versions() {
287        for v in SpecVersion::all() {
288            for test in spec(v) {
289                if let Err(e) = test.wast().expect("Failed to lex wast").directives() {
290                    panic!("Failed to parse wast: {e:?}, {test:?}");
291                }
292            }
293        }
294    }
295}