1use super::parser::ParsedSpec;
6use super::template::{FalsificationTemplate, TestSeverity, TestTemplate};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11pub enum TargetLanguage {
12 Rust,
13 Python,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct GeneratedTest {
19 pub id: String,
21 pub name: String,
23 pub category: String,
25 pub points: u32,
27 pub severity: TestSeverity,
29 pub code: String,
31}
32
33#[derive(Debug)]
35pub struct FalsifyGenerator {
36 module_placeholder: String,
38 function_placeholder: String,
40 type_placeholder: String,
42}
43
44impl FalsifyGenerator {
45 pub fn new() -> Self {
47 Self {
48 module_placeholder: "{{module}}".to_string(),
49 function_placeholder: "{{function}}".to_string(),
50 type_placeholder: "{{type}}".to_string(),
51 }
52 }
53
54 pub fn generate(
56 &self,
57 spec: &ParsedSpec,
58 template: &FalsificationTemplate,
59 language: TargetLanguage,
60 ) -> anyhow::Result<Vec<GeneratedTest>> {
61 let mut tests = Vec::new();
62
63 for category in &template.categories {
64 for test_template in &category.tests {
65 let code = self.generate_test_code(spec, test_template, language)?;
66
67 tests.push(GeneratedTest {
68 id: test_template.id.clone(),
69 name: test_template.name.clone(),
70 category: category.name.clone(),
71 points: test_template.points,
72 severity: test_template.severity,
73 code,
74 });
75 }
76 }
77
78 Ok(tests)
79 }
80
81 fn generate_test_code(
83 &self,
84 spec: &ParsedSpec,
85 template: &TestTemplate,
86 language: TargetLanguage,
87 ) -> anyhow::Result<String> {
88 let code_template = match language {
89 TargetLanguage::Rust => template.rust_template.as_deref().unwrap_or(DEFAULT_RUST),
90 TargetLanguage::Python => template.python_template.as_deref().unwrap_or(DEFAULT_PYTHON),
91 };
92
93 let code = self.substitute_placeholders(code_template, spec, template);
94 Ok(code)
95 }
96
97 fn substitute_placeholders(
99 &self,
100 template: &str,
101 spec: &ParsedSpec,
102 test_template: &TestTemplate,
103 ) -> String {
104 let mut code = template.to_string();
105
106 code = code.replace(&self.module_placeholder, &spec.module);
108 code = code.replace("{{module}}", &spec.module);
109
110 let function = spec.functions.first().map(|s| s.as_str()).unwrap_or("function");
112 code = code.replace(&self.function_placeholder, function);
113 code = code.replace("{{function}}", function);
114
115 let type_name = spec.types.first().map(|s| s.as_str()).unwrap_or("T");
117 code = code.replace(&self.type_placeholder, type_name);
118 code = code.replace("{{type}}", type_name);
119
120 let id_lower = test_template.id.to_lowercase().replace('-', "_");
122 code = code.replace("{{id_lower}}", &id_lower);
123 code = code.replace("{{id}}", &test_template.id);
124
125 code = code.replace("{{max_size}}", "1_000_000");
127
128 let strategy = match type_name {
130 "String" | "str" => "text",
131 "Vec" | "list" => "lists(integers())",
132 "f32" | "f64" | "float" => "floats(-1e6, 1e6)",
133 "i32" | "i64" | "int" => "integers(-1000000, 1000000)",
134 _ => "builds(lambda: None)",
135 };
136 code = code.replace("{{strategy}}", strategy);
137
138 code
139 }
140}
141
142impl Default for FalsifyGenerator {
143 fn default() -> Self {
144 Self::new()
145 }
146}
147
148const DEFAULT_RUST: &str = r#"#[test]
149#[ignore = "Stub: implement falsification for {{id}}"]
150fn falsify_{{id_lower}}_default() {
151 // Placeholder for {{id}} - replace with actual falsification logic
152 assert!(false, "Not yet implemented: falsification test for {{id}}");
153}"#;
154
155const DEFAULT_PYTHON: &str = r" # STUB: Test placeholder for {{id}} - replace with actual falsification
156 pass
157";
158
159#[cfg(test)]
160mod tests {
161 use super::super::parser::SpecParser;
162 use super::*;
163 use std::path::Path;
164
165 #[test]
166 fn test_generator_creation() {
167 let gen = FalsifyGenerator::new();
168 assert!(!gen.module_placeholder.is_empty());
169 }
170
171 #[test]
172 fn test_generate_from_spec() {
173 let parser = SpecParser::new();
174 let content = r#"
175# Test Spec
176module: test_module
177
178## Functions
179fn test_function(input: &[u8]) -> Result<Vec<u8>, Error>
180"#;
181 let spec = parser.parse(content, Path::new("test.md")).expect("unexpected failure");
182 let template = FalsificationTemplate::default();
183 let gen = FalsifyGenerator::new();
184
185 let tests =
186 gen.generate(&spec, &template, TargetLanguage::Rust).expect("unexpected failure");
187 assert!(!tests.is_empty());
188
189 for test in &tests {
191 assert!(!test.id.is_empty(), "Test ID should not be empty");
193 }
194 }
195
196 #[test]
197 fn test_placeholder_substitution() {
198 let gen = FalsifyGenerator::new();
199 let template = "fn test_{{module}}_{{function}}()";
200
201 let spec = ParsedSpec {
202 name: "test".to_string(),
203 module: "my_module".to_string(),
204 requirements: vec![],
205 types: vec!["MyType".to_string()],
206 functions: vec!["my_func".to_string()],
207 tolerances: None,
208 };
209
210 let test_template = super::super::template::TestTemplate {
211 id: "TEST-001".to_string(),
212 name: "Test".to_string(),
213 description: "Test".to_string(),
214 severity: TestSeverity::Medium,
215 points: 5,
216 rust_template: Some(template.to_string()),
217 python_template: None,
218 };
219
220 let result = gen.substitute_placeholders(template, &spec, &test_template);
221 assert!(result.contains("my_module"));
222 assert!(result.contains("my_func"));
223 }
224
225 #[test]
226 fn test_generator_default() {
227 let gen = FalsifyGenerator::default();
228 assert_eq!(gen.module_placeholder, "{{module}}");
229 }
230
231 #[test]
232 fn test_target_language_variants() {
233 assert_ne!(TargetLanguage::Rust, TargetLanguage::Python);
234 assert_eq!(TargetLanguage::Rust, TargetLanguage::Rust);
235 }
236
237 #[test]
238 fn test_generated_test_fields() {
239 let test = GeneratedTest {
240 id: "BC-001".to_string(),
241 name: "Boundary Test".to_string(),
242 category: "boundary".to_string(),
243 points: 4,
244 severity: TestSeverity::High,
245 code: "#[test]\nfn test_boundary() {}".to_string(),
246 };
247 assert_eq!(test.id, "BC-001");
248 assert_eq!(test.points, 4);
249 assert_eq!(test.severity, TestSeverity::High);
250 }
251
252 #[test]
253 fn test_generate_python() {
254 let parser = SpecParser::new();
255 let content = "module: test\n- MUST work";
256 let spec = parser.parse(content, Path::new("test.md")).expect("unexpected failure");
257 let template = FalsificationTemplate::default();
258 let gen = FalsifyGenerator::new();
259
260 let tests =
261 gen.generate(&spec, &template, TargetLanguage::Python).expect("unexpected failure");
262 assert!(!tests.is_empty());
263 }
264
265 #[test]
266 fn test_placeholder_id_substitution() {
267 let gen = FalsifyGenerator::new();
268 let template = "test_{{id_lower}} {{id}}";
269
270 let spec = ParsedSpec {
271 name: "test".to_string(),
272 module: "mod".to_string(),
273 requirements: vec![],
274 types: vec![],
275 functions: vec![],
276 tolerances: None,
277 };
278
279 let test_template = super::super::template::TestTemplate {
280 id: "BC-001".to_string(),
281 name: "Test".to_string(),
282 description: "Test".to_string(),
283 severity: TestSeverity::Medium,
284 points: 5,
285 rust_template: Some(template.to_string()),
286 python_template: None,
287 };
288
289 let result = gen.substitute_placeholders(template, &spec, &test_template);
290 assert!(result.contains("bc_001"));
291 assert!(result.contains("BC-001"));
292 }
293
294 #[test]
295 fn test_placeholder_type_substitution() {
296 let gen = FalsifyGenerator::new();
297 let template = "type: {{type}}";
298
299 let spec = ParsedSpec {
300 name: "test".to_string(),
301 module: "mod".to_string(),
302 requirements: vec![],
303 types: vec!["MyStruct".to_string()],
304 functions: vec![],
305 tolerances: None,
306 };
307
308 let test_template = super::super::template::TestTemplate {
309 id: "TEST-001".to_string(),
310 name: "Test".to_string(),
311 description: "Test".to_string(),
312 severity: TestSeverity::Low,
313 points: 2,
314 rust_template: Some(template.to_string()),
315 python_template: None,
316 };
317
318 let result = gen.substitute_placeholders(template, &spec, &test_template);
319 assert!(result.contains("MyStruct"));
320 }
321
322 #[test]
323 fn test_placeholder_max_size_substitution() {
324 let gen = FalsifyGenerator::new();
325 let template = "size: {{max_size}}";
326
327 let spec = ParsedSpec {
328 name: "test".to_string(),
329 module: "mod".to_string(),
330 requirements: vec![],
331 types: vec![],
332 functions: vec![],
333 tolerances: None,
334 };
335
336 let test_template = super::super::template::TestTemplate {
337 id: "TEST-001".to_string(),
338 name: "Test".to_string(),
339 description: "Test".to_string(),
340 severity: TestSeverity::Medium,
341 points: 5,
342 rust_template: Some(template.to_string()),
343 python_template: None,
344 };
345
346 let result = gen.substitute_placeholders(template, &spec, &test_template);
347 assert!(result.contains("1_000_000"));
348 }
349
350 #[test]
351 fn test_strategy_substitution_string() {
352 let gen = FalsifyGenerator::new();
353 let template = "{{strategy}}";
354
355 let spec = ParsedSpec {
356 name: "test".to_string(),
357 module: "mod".to_string(),
358 requirements: vec![],
359 types: vec!["String".to_string()],
360 functions: vec![],
361 tolerances: None,
362 };
363
364 let test_template = super::super::template::TestTemplate {
365 id: "TEST-001".to_string(),
366 name: "Test".to_string(),
367 description: "Test".to_string(),
368 severity: TestSeverity::Medium,
369 points: 5,
370 rust_template: Some(template.to_string()),
371 python_template: None,
372 };
373
374 let result = gen.substitute_placeholders(template, &spec, &test_template);
375 assert!(result.contains("text"));
376 }
377
378 #[test]
379 fn test_strategy_substitution_float() {
380 let gen = FalsifyGenerator::new();
381 let template = "{{strategy}}";
382
383 let spec = ParsedSpec {
384 name: "test".to_string(),
385 module: "mod".to_string(),
386 requirements: vec![],
387 types: vec!["f64".to_string()],
388 functions: vec![],
389 tolerances: None,
390 };
391
392 let test_template = super::super::template::TestTemplate {
393 id: "TEST-001".to_string(),
394 name: "Test".to_string(),
395 description: "Test".to_string(),
396 severity: TestSeverity::Medium,
397 points: 5,
398 rust_template: Some(template.to_string()),
399 python_template: None,
400 };
401
402 let result = gen.substitute_placeholders(template, &spec, &test_template);
403 assert!(result.contains("floats"));
404 }
405
406 #[test]
407 fn test_fallback_defaults() {
408 let gen = FalsifyGenerator::new();
409 let template = "{{function}} {{type}}";
410
411 let spec = ParsedSpec {
412 name: "test".to_string(),
413 module: "mod".to_string(),
414 requirements: vec![],
415 types: vec![],
416 functions: vec![],
417 tolerances: None,
418 };
419
420 let test_template = super::super::template::TestTemplate {
421 id: "TEST-001".to_string(),
422 name: "Test".to_string(),
423 description: "Test".to_string(),
424 severity: TestSeverity::Medium,
425 points: 5,
426 rust_template: Some(template.to_string()),
427 python_template: None,
428 };
429
430 let result = gen.substitute_placeholders(template, &spec, &test_template);
431 assert!(result.contains("function"));
432 assert!(result.contains('T'));
433 }
434}