|
1 |
| -use std::path::Path; |
| 1 | +use std::{ |
| 2 | + env, |
| 3 | + fs::File, |
| 4 | + io::Write, |
| 5 | + path::{Path, PathBuf}, |
| 6 | +}; |
2 | 7 |
|
| 8 | +use askama::Template; |
3 | 9 | use syn::visit::Visit;
|
| 10 | +use thiserror::Error; |
4 | 11 |
|
5 |
| -use crate::{expand, ffi_items::FfiItems, Result}; |
| 12 | +use crate::{ |
| 13 | + expand, |
| 14 | + ffi_items::FfiItems, |
| 15 | + template::{CTestTemplate, RustTestTemplate}, |
| 16 | +}; |
| 17 | + |
| 18 | +#[derive(Debug, Error)] |
| 19 | +pub enum GenerationError { |
| 20 | + #[error("unable to expand crate {0}: {1}")] |
| 21 | + MacroExpansion(PathBuf, String), |
| 22 | + #[error("unable to parse expanded crate {0}: {1}")] |
| 23 | + RustSyntax(String, String), |
| 24 | + #[error("unable to render {0} template: {1}")] |
| 25 | + TemplateRender(String, String), |
| 26 | + #[error("unable to create or write template file: {0}")] |
| 27 | + OsError(std::io::Error), |
| 28 | +} |
6 | 29 |
|
7 | 30 | /// A builder used to generate a test suite.
|
8 |
| -#[non_exhaustive] |
9 | 31 | #[derive(Default, Debug, Clone)]
|
10 |
| -pub struct TestGenerator {} |
| 32 | +pub struct TestGenerator { |
| 33 | + headers: Vec<String>, |
| 34 | + pub(crate) target: Option<String>, |
| 35 | + pub(crate) includes: Vec<PathBuf>, |
| 36 | + out_dir: Option<PathBuf>, |
| 37 | +} |
11 | 38 |
|
12 | 39 | impl TestGenerator {
|
13 | 40 | /// Creates a new blank test generator.
|
14 | 41 | pub fn new() -> Self {
|
15 | 42 | Self::default()
|
16 | 43 | }
|
17 | 44 |
|
18 |
| - /// Generate all tests for the given crate and output the Rust side to a file. |
19 |
| - pub fn generate<P: AsRef<Path>>(&mut self, crate_path: P, _output_file_path: P) -> Result<()> { |
20 |
| - let expanded = expand(crate_path)?; |
21 |
| - let ast = syn::parse_file(&expanded)?; |
| 45 | + /// Add a header to be included as part of the generated C file. |
| 46 | + /// |
| 47 | + /// The generate C test will be compiled by a C compiler, and this can be |
| 48 | + /// used to ensure that all the necessary header files are included to test |
| 49 | + /// all FFI definitions. |
| 50 | + pub fn header(&mut self, header: &str) -> &mut Self { |
| 51 | + self.headers.push(header.to_string()); |
| 52 | + self |
| 53 | + } |
| 54 | + |
| 55 | + /// Configures the target to compile C code for. |
| 56 | + /// |
| 57 | + /// Note that for Cargo builds this defaults to `$TARGET` and it's not |
| 58 | + /// necessary to call. |
| 59 | + pub fn target(&mut self, target: &str) -> &mut Self { |
| 60 | + self.target = Some(target.to_string()); |
| 61 | + self |
| 62 | + } |
| 63 | + |
| 64 | + /// Add a path to the C compiler header lookup path. |
| 65 | + /// |
| 66 | + /// This is useful for if the C library is installed to a nonstandard |
| 67 | + /// location to ensure that compiling the C file succeeds. |
| 68 | + pub fn include<P: AsRef<Path>>(&mut self, p: P) -> &mut Self { |
| 69 | + self.includes.push(p.as_ref().to_owned()); |
| 70 | + self |
| 71 | + } |
| 72 | + |
| 73 | + /// Configures the output directory of the generated Rust and C code. |
| 74 | + pub fn out_dir<P: AsRef<Path>>(&mut self, p: P) -> &mut Self { |
| 75 | + self.out_dir = Some(p.as_ref().to_owned()); |
| 76 | + self |
| 77 | + } |
| 78 | + |
| 79 | + /// Generate the Rust and C testing files. |
| 80 | + /// |
| 81 | + /// Returns the path to t generated file. |
| 82 | + pub fn generate_files( |
| 83 | + &mut self, |
| 84 | + crate_path: impl AsRef<Path>, |
| 85 | + output_file_path: impl AsRef<Path>, |
| 86 | + ) -> Result<PathBuf, GenerationError> { |
| 87 | + let expanded = expand(&crate_path).map_err(|e| { |
| 88 | + GenerationError::MacroExpansion(crate_path.as_ref().to_path_buf(), e.to_string()) |
| 89 | + })?; |
| 90 | + let ast = syn::parse_file(&expanded) |
| 91 | + .map_err(|e| GenerationError::RustSyntax(expanded, e.to_string()))?; |
22 | 92 |
|
23 | 93 | let mut ffi_items = FfiItems::new();
|
24 | 94 | ffi_items.visit_file(&ast);
|
25 | 95 |
|
26 |
| - Ok(()) |
| 96 | + let output_directory = self |
| 97 | + .out_dir |
| 98 | + .clone() |
| 99 | + .unwrap_or_else(|| env::var("OUT_DIR").unwrap().into()); |
| 100 | + let output_file_path = output_directory.join(output_file_path); |
| 101 | + |
| 102 | + // Generate the Rust side of the tests. |
| 103 | + File::create(output_file_path.with_extension("rs")) |
| 104 | + .map_err(GenerationError::OsError)? |
| 105 | + .write_all( |
| 106 | + RustTestTemplate::new(&ffi_items) |
| 107 | + .render() |
| 108 | + .map_err(|e| { |
| 109 | + GenerationError::TemplateRender("Rust".to_string(), e.to_string()) |
| 110 | + })? |
| 111 | + .as_bytes(), |
| 112 | + ) |
| 113 | + .map_err(GenerationError::OsError)?; |
| 114 | + |
| 115 | + // Generate the C side of the tests. |
| 116 | + // FIXME(ctest): Cpp not supported yet. |
| 117 | + let c_output_path = output_file_path.with_extension("c"); |
| 118 | + let headers = self.headers.iter().map(|h| h.as_str()).collect(); |
| 119 | + File::create(&c_output_path) |
| 120 | + .map_err(GenerationError::OsError)? |
| 121 | + .write_all( |
| 122 | + CTestTemplate::new(headers, &ffi_items) |
| 123 | + .render() |
| 124 | + .map_err(|e| GenerationError::TemplateRender("C".to_string(), e.to_string()))? |
| 125 | + .as_bytes(), |
| 126 | + ) |
| 127 | + .map_err(GenerationError::OsError)?; |
| 128 | + |
| 129 | + Ok(output_file_path) |
27 | 130 | }
|
28 | 131 | }
|
0 commit comments