1pub mod example_pack;
14pub use example_pack::{DeriveExamplePackSpec, Example, PackRunReport};
15
16use std::collections::BTreeMap;
17use std::path::{Path, PathBuf};
18use tatara_rust_ast::{AstError, CompileToCrate, CrateScaffold};
19use tatara_rust_derive::ProcDeriveSpec;
20use thiserror::Error;
21
22#[derive(Debug, Error)]
23pub enum TestError {
24 #[error("ast: {0}")]
25 Ast(#[from] AstError),
26 #[error("io: {0}")]
27 Io(#[from] std::io::Error),
28}
29
30pub struct TestLayout {
32 pub root: PathBuf,
33 pub derive_crate_name: String,
34 pub consumer_crate_name: String,
35}
36
37impl TestLayout {
38 pub fn write(
39 spec: &ProcDeriveSpec,
40 derive_crate_name: &str,
41 consumer_crate_name: &str,
42 root: &Path,
43 ) -> Result<Self, TestError> {
44 let derive_root = root.join(derive_crate_name);
45 spec.compile_to_crate(derive_crate_name)?.write_to(&derive_root)?;
46
47 let consumer = consumer_scaffold(spec, derive_crate_name, consumer_crate_name);
48 let consumer_root = root.join(consumer_crate_name);
49 consumer.write_to(&consumer_root)?;
50
51 Ok(Self {
52 root: root.to_path_buf(),
53 derive_crate_name: derive_crate_name.into(),
54 consumer_crate_name: consumer_crate_name.into(),
55 })
56 }
57
58 #[must_use]
61 pub fn as_files(
62 spec: &ProcDeriveSpec,
63 derive_crate_name: &str,
64 consumer_crate_name: &str,
65 ) -> BTreeMap<String, String> {
66 let mut out: BTreeMap<String, String> = BTreeMap::new();
67 if let Ok(d) = spec.compile_to_crate(derive_crate_name) {
68 for f in d.files {
69 out.insert(format!("{derive_crate_name}/{}", f.path), f.contents);
70 }
71 }
72 let c = consumer_scaffold(spec, derive_crate_name, consumer_crate_name);
73 for f in c.files {
74 out.insert(format!("{consumer_crate_name}/{}", f.path), f.contents);
75 }
76 out
77 }
78}
79
80fn consumer_scaffold(
81 spec: &ProcDeriveSpec,
82 derive_crate_name: &str,
83 consumer_crate_name: &str,
84) -> CrateScaffold {
85 let trait_id = &spec.trait_name.0;
86 let derive_under = derive_crate_name.replace('-', "_");
87 let consumer_under = consumer_crate_name.replace('-', "_");
88
89 let mut s = CrateScaffold::new(consumer_crate_name, "0.1.0");
90
91 s.add_file(
92 "Cargo.toml",
93 format!(
94 r#"[package]
95name = "{consumer_crate_name}"
96version = "0.1.0"
97edition = "2024"
98
99[dependencies]
100{derive_crate_name} = {{ path = "../{derive_crate_name}" }}
101
102[lib]
103path = "src/lib.rs"
104"#
105 ),
106 );
107
108 s.add_file(
109 "src/lib.rs",
110 format!(
111 r#"// Consumer crate — exercises the generated derive macro end-to-end.
112use {derive_under}::{trait_id};
113
114#[derive({trait_id})]
115pub struct Thing;
116
117#[cfg(test)]
118mod tests {{
119 use super::*;
120 #[test]
121 fn smoke() {{
122 // Compilation of the derive IS the assertion.
123 let _t = Thing;
124 }}
125}}
126"#
127 ),
128 );
129 let _ = consumer_under; s
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use tatara_rust_ast::{
137 Block, Expr, Fn as RsFn, FnSig, Generics, Ident, RefKind, Stmt, TypeRef,
138 };
139
140 fn spec() -> ProcDeriveSpec {
141 ProcDeriveSpec::new(
142 "Probe",
143 vec![RsFn {
144 sig: FnSig {
145 name: Ident::new("probe"),
146 generics: Generics::default(),
147 params: vec![],
148 return_type: Some(TypeRef {
149 ident: Ident::new("str"),
150 generics: vec![],
151 reference: Some(RefKind::shared_lifetime("static")),
152 }),
153 },
154 body: Block {
155 stmts: vec![Stmt::Tail {
156 expr: Expr::Literal {
157 value: "\"ok\"".into(),
158 },
159 }],
160 },
161 }],
162 )
163 }
164
165 #[test]
166 fn as_files_contains_both_crates() {
167 let files = TestLayout::as_files(&spec(), "probe-derive", "probe-consumer");
168 assert!(files.contains_key("probe-derive/Cargo.toml"));
169 assert!(files.contains_key("probe-derive/src/lib.rs"));
170 assert!(files.contains_key("probe-consumer/Cargo.toml"));
171 assert!(files.contains_key("probe-consumer/src/lib.rs"));
172 }
173
174 #[test]
175 fn consumer_uses_derive_path_dep() {
176 let files = TestLayout::as_files(&spec(), "probe-derive", "probe-consumer");
177 let toml = files.get("probe-consumer/Cargo.toml").unwrap();
178 assert!(toml.contains(r#"probe-derive = { path = "../probe-derive" }"#));
179 }
180}