tatara_rust_test/
example_pack.rs1use std::path::Path;
16use std::process::Command;
17use tatara_rust_ast::{AstError, CompileToCrate};
18
19#[derive(Clone, Debug)]
22pub struct Example {
23 pub name: String,
25 pub consumer_item: String,
28 pub assertion_body: String,
31}
32
33pub struct DeriveExamplePackSpec<'a, T: CompileToCrate + ?Sized> {
35 pub derive_crate_name: String,
36 pub trait_name: String,
38 pub spec: &'a T,
40 pub extra_consumer_imports: Vec<String>,
44 pub auxiliary_trait_crates: Vec<(String, String)>,
47 pub examples: Vec<Example>,
49}
50
51#[derive(Debug)]
53pub struct PackRunReport {
54 pub temp_root: std::path::PathBuf,
55 pub cargo_test_succeeded: bool,
56}
57
58impl<'a, T: CompileToCrate + ?Sized> DeriveExamplePackSpec<'a, T> {
59 pub fn run_under(&self, root: &Path) -> Result<PackRunReport, AstError> {
63 std::fs::create_dir_all(root)?;
64
65 let derive_root = root.join(&self.derive_crate_name);
67 self.spec
68 .compile_to_crate(&self.derive_crate_name)?
69 .write_to(&derive_root)?;
70
71 for (name, lib_rs) in &self.auxiliary_trait_crates {
73 let dir = root.join(name).join("src");
74 std::fs::create_dir_all(&dir)?;
75 std::fs::write(
76 root.join(name).join("Cargo.toml"),
77 format!(
78 r#"[package]
79name = "{name}"
80version = "0.1.0"
81edition = "2024"
82
83[lib]
84path = "src/lib.rs"
85"#
86 ),
87 )?;
88 std::fs::write(dir.join("lib.rs"), lib_rs)?;
89 }
90
91 let consumer = root.join("consumer");
93 std::fs::create_dir_all(consumer.join("src"))?;
94 std::fs::write(consumer.join("Cargo.toml"), self.render_consumer_cargo())?;
95 std::fs::write(consumer.join("src/lib.rs"), self.render_consumer_lib())?;
96
97 let status = Command::new("cargo")
99 .arg("test")
100 .current_dir(&consumer)
101 .status()?;
102 Ok(PackRunReport {
103 temp_root: root.to_path_buf(),
104 cargo_test_succeeded: status.success(),
105 })
106 }
107
108 fn render_consumer_cargo(&self) -> String {
109 let derive_under = self.derive_crate_name.replace('-', "_");
110 let derive_dep = format!(
111 r#"{derive_crate} = {{ path = "../{derive_crate}" }}"#,
112 derive_crate = self.derive_crate_name
113 );
114 let aux_deps = self
115 .auxiliary_trait_crates
116 .iter()
117 .map(|(n, _)| format!(r#"{n} = {{ path = "../{n}" }}"#))
118 .collect::<Vec<_>>()
119 .join("\n");
120 let _ = derive_under;
121 format!(
122 r#"[package]
123name = "consumer"
124version = "0.1.0"
125edition = "2024"
126
127[dependencies]
128{derive_dep}
129{aux_deps}
130
131[lib]
132path = "src/lib.rs"
133"#
134 )
135 }
136
137 fn render_consumer_lib(&self) -> String {
138 let derive_under = self.derive_crate_name.replace('-', "_");
139 let extra_imports = self.extra_consumer_imports.join("\n");
140 let mut items = String::new();
141 let mut tests = String::new();
142 for ex in &self.examples {
143 let ex_under = ex.name.replace('-', "_");
144 items.push_str(&format!(
145 "#[derive({trait_name})]\n{src}\n\n",
146 trait_name = self.trait_name,
147 src = ex.consumer_item
148 ));
149 tests.push_str(&format!(
150 " #[test] fn ex_{ex_under}() {{\n{body}\n }}\n",
151 body = ex.assertion_body
152 ));
153 }
154 format!(
155 r#"use {derive_under}::{trait_name};
156{extra_imports}
157
158{items}
159
160#[cfg(test)]
161mod tests {{
162 use super::*;
163{tests}
164}}
165"#,
166 trait_name = self.trait_name
167 )
168 }
169}
170
171#[cfg(test)]
172mod tests {
173 use super::*;
174 use tatara_rust_derive::{PerFieldDeriveSpec, PerFieldTarget};
175 use tatara_rust_ast::Ident;
176
177 fn getter_spec() -> PerFieldDeriveSpec {
178 PerFieldDeriveSpec {
179 trait_name: Ident::new("GetterPack"),
180 target: PerFieldTarget::NamedStruct,
181 trait_ref: None,
182 per_field_template:
183 "pub fn #field_name(&self) -> &#field_ty { &self.#field_name }".into(),
184 method_name_template: None,
185 impl_prelude: None,
186 skip_fields: vec![],
187 field_attribute: None,
188 }
189 }
190
191 #[test]
192 fn pack_renders_consumer_with_derives_and_assertions() {
193 let spec = getter_spec();
194 let pack = DeriveExamplePackSpec {
195 derive_crate_name: "getter-pack-derive".into(),
196 trait_name: "GetterPack".into(),
197 spec: &spec,
198 extra_consumer_imports: vec![],
199 auxiliary_trait_crates: vec![],
200 examples: vec![Example {
201 name: "two-fields".into(),
202 consumer_item: "pub struct TwoFields { pub a: i32, pub b: String }".into(),
203 assertion_body: r#"
204 let t = TwoFields { a: 1, b: "x".into() };
205 assert_eq!(*t.a(), 1);
206 assert_eq!(t.b(), "x");"#
207 .into(),
208 }],
209 };
210 let lib = pack.render_consumer_lib();
211 assert!(lib.contains("use getter_pack_derive::GetterPack;"));
212 assert!(lib.contains("#[derive(GetterPack)]"));
213 assert!(lib.contains("pub struct TwoFields"));
214 assert!(lib.contains("fn ex_two_fields"));
215 }
216
217 #[test]
218 fn pack_cargo_lists_aux_trait_path_deps() {
219 let spec = getter_spec();
220 let pack = DeriveExamplePackSpec {
221 derive_crate_name: "x-derive".into(),
222 trait_name: "X".into(),
223 spec: &spec,
224 extra_consumer_imports: vec!["use x_trait::X;".into()],
225 auxiliary_trait_crates: vec![("x-trait".into(), "pub trait X {}".into())],
226 examples: vec![],
227 };
228 let cargo = pack.render_consumer_cargo();
229 assert!(cargo.contains(r#"x-derive = { path = "../x-derive" }"#));
230 assert!(cargo.contains(r#"x-trait = { path = "../x-trait" }"#));
231 }
232}