Skip to main content

miden_assembly/
testing.rs

1use alloc::{boxed::Box, sync::Arc, vec::Vec};
2
3#[cfg(any(test, feature = "testing"))]
4pub use miden_assembly_syntax::parser;
5use miden_assembly_syntax::{
6    Library, Parse, ParseOptions, Path, Word,
7    ast::{Module, ModuleKind},
8    debuginfo::{DefaultSourceManager, SourceManager},
9    diagnostics::{
10        Report,
11        reporting::{ReportHandlerOpts, set_hook},
12    },
13};
14pub use miden_assembly_syntax::{
15    assert_diagnostic, assert_diagnostic_lines, parse_module, regex, source_file, testing::Pattern,
16};
17#[cfg(feature = "testing")]
18use miden_assembly_syntax::{ast::Form, debuginfo::SourceFile};
19use miden_core::program::Program;
20
21use crate::assembler::Assembler;
22#[cfg(feature = "std")]
23use crate::diagnostics::reporting::set_panic_hook;
24
25/// A [TestContext] provides common functionality for all tests which interact with an [Assembler].
26///
27/// It is used by constructing it with `TestContext::default()`, which will initialize the
28/// diagnostic reporting infrastructure, and construct a default [Assembler] instance for you. You
29/// can then optionally customize the context, or start invoking any of its test helpers.
30///
31/// Some of the assertion macros defined above require a [TestContext], so be aware of that.
32pub struct TestContext {
33    source_manager: Arc<dyn SourceManager>,
34    assembler: Assembler,
35}
36
37impl Default for TestContext {
38    fn default() -> Self {
39        Self::new()
40    }
41}
42
43impl TestContext {
44    pub fn new() -> Self {
45        #[cfg(feature = "logging")]
46        {
47            // Enable debug tracing to stderr via the MIDEN_LOG environment variable, if present
48            let _ = env_logger::Builder::from_env("MIDEN_LOG").format_timestamp(None).try_init();
49        }
50
51        #[cfg(feature = "std")]
52        {
53            let result = set_hook(Box::new(|_| Box::new(ReportHandlerOpts::new().build())));
54            #[cfg(feature = "std")]
55            if result.is_ok() {
56                set_panic_hook();
57            }
58        }
59
60        #[cfg(not(feature = "std"))]
61        {
62            let _ = set_hook(Box::new(|_| Box::new(ReportHandlerOpts::new().build())));
63        }
64        let source_manager = Arc::new(DefaultSourceManager::default());
65        let assembler = Assembler::new(source_manager.clone()).with_warnings_as_errors(true);
66        Self { source_manager, assembler }
67    }
68
69    #[inline]
70    fn assembler(&self) -> Assembler {
71        self.assembler.clone()
72    }
73
74    #[inline(always)]
75    pub fn source_manager(&self) -> Arc<dyn SourceManager> {
76        self.source_manager.clone()
77    }
78
79    /// Parse the given source file into a vector of top-level [Form]s.
80    ///
81    /// This does not run semantic analysis, or construct a [Module] from the parsed
82    /// forms, and is largely intended for low-level testing of the parser.
83    #[cfg(feature = "testing")]
84    #[track_caller]
85    pub fn parse_forms(&self, source: Arc<SourceFile>) -> Result<Vec<Form>, Report> {
86        parser::parse_forms(source.clone()).map_err(|err| Report::new(err).with_source_code(source))
87    }
88
89    /// Parse the given source file into an executable [Module].
90    ///
91    /// This runs semantic analysis, and the returned module is guaranteed to be syntactically
92    /// valid.
93    #[track_caller]
94    pub fn parse_program(&self, source: impl Parse) -> Result<Box<Module>, Report> {
95        source.parse_with_options(
96            self.source_manager.clone(),
97            ParseOptions {
98                warnings_as_errors: self.assembler.warnings_as_errors(),
99                ..Default::default()
100            },
101        )
102    }
103
104    /// Parse the given source file into a kernel [Module].
105    ///
106    /// This runs semantic analysis, and the returned module is guaranteed to be syntactically
107    /// valid.
108    #[track_caller]
109    pub fn parse_kernel(&self, source: impl Parse) -> Result<Box<Module>, Report> {
110        source.parse_with_options(
111            self.source_manager.clone(),
112            ParseOptions {
113                warnings_as_errors: self.assembler.warnings_as_errors(),
114                ..ParseOptions::for_kernel()
115            },
116        )
117    }
118
119    /// Parse the given source file into an anonymous library [Module].
120    ///
121    /// This runs semantic analysis, and the returned module is guaranteed to be syntactically
122    /// valid.
123    #[track_caller]
124    pub fn parse_module(&self, source: impl Parse) -> Result<Box<Module>, Report> {
125        source.parse_with_options(
126            self.source_manager.clone(),
127            ParseOptions {
128                warnings_as_errors: self.assembler.warnings_as_errors(),
129                ..ParseOptions::for_library()
130            },
131        )
132    }
133
134    /// Parse the given source file into a library [Module] with the given fully-qualified path.
135    #[track_caller]
136    pub fn parse_module_with_path(
137        &self,
138        path: impl AsRef<Path>,
139        source: impl Parse,
140    ) -> Result<Box<Module>, Report> {
141        source.parse_with_options(
142            self.source_manager.clone(),
143            ParseOptions {
144                warnings_as_errors: self.assembler.warnings_as_errors(),
145                ..ParseOptions::new(ModuleKind::Library, path.as_ref().to_absolute())
146            },
147        )
148    }
149
150    /// Add `module` to the [Assembler] constructed by this context, making it available to
151    /// other modules.
152    #[track_caller]
153    pub fn add_module(&mut self, module: impl Parse) -> Result<(), Report> {
154        self.assembler.compile_and_statically_link(module).map(|_| ())
155    }
156
157    /// Add a module to the [Assembler] constructed by this context, with the fully-qualified
158    /// name `path`, by parsing it from the provided source file.
159    ///
160    /// This will fail if the module cannot be parsed, fails semantic analysis, or conflicts
161    /// with a previously added module within the assembler.
162    #[track_caller]
163    pub fn add_module_from_source(
164        &mut self,
165        path: impl AsRef<Path>,
166        source: impl Parse,
167    ) -> Result<(), Report> {
168        let module = source.parse_with_options(
169            self.source_manager.clone(),
170            ParseOptions {
171                path: Some(path.as_ref().to_absolute().into_owned().into()),
172                ..ParseOptions::for_library()
173            },
174        )?;
175        self.assembler.compile_and_statically_link(module).map(|_| ())
176    }
177
178    /// Add the modules of `library` to the [Assembler] constructed by this context.
179    #[track_caller]
180    pub fn add_library(&mut self, library: impl AsRef<Library>) -> Result<(), Report> {
181        self.assembler.link_dynamic_library(library)
182    }
183
184    /// Compile a [Program] from `source` using the [Assembler] constructed by this context.
185    ///
186    /// NOTE: Any modules added by, e.g. `add_module`, will be available to the executable
187    /// module represented in `source`.
188    #[track_caller]
189    pub fn assemble(&self, source: impl Parse) -> Result<Program, Report> {
190        self.assembler().assemble_program(source)
191    }
192
193    /// Compile a [Library] from `modules` using the [Assembler] constructed by this
194    /// context.
195    ///
196    /// NOTE: Any modules added by, e.g. `add_module`, will be available to the library
197    #[track_caller]
198    pub fn assemble_library(
199        &self,
200        modules: impl IntoIterator<Item = Box<Module>>,
201    ) -> Result<Library, Report> {
202        self.assembler().assemble_library(modules)
203    }
204
205    /// Compile a module from `source`, with the fully-qualified name `path`, to MAST, returning
206    /// the MAST roots of all the exported procedures of that module.
207    #[track_caller]
208    pub fn assemble_module(
209        &self,
210        _path: impl AsRef<Path>,
211        _module: impl Parse,
212    ) -> Result<Vec<Word>, Report> {
213        // This API will change after we implement `Assembler::add_library()`
214        unimplemented!()
215    }
216}