use std::path::PathBuf;
use rstest::{fixture, rstest};
use ryo_app::{AnalysisContext, BlueprintExecutor, Goal, ParallelBlueprint, Planner};
use ryo_test::harness::{source_diff, sources_equal};
#[fixture]
fn pattern_source() -> &'static str {
include_str!("fixtures/patterns.rs")
}
#[fixture]
fn pattern_ctx(pattern_source: &'static str) -> PatternTestContext {
PatternTestContext::new(pattern_source)
}
pub struct PatternTestContext {
_temp_dir: tempfile::TempDir,
workspace_root: PathBuf,
source: String,
}
impl PatternTestContext {
pub fn new(source: &str) -> Self {
let temp_dir = tempfile::tempdir().expect("Failed to create temp dir");
let src_dir = temp_dir.path().join("src");
std::fs::create_dir_all(&src_dir).expect("Failed to create src dir");
let lib_rs = src_dir.join("lib.rs");
std::fs::write(&lib_rs, source).expect("Failed to write lib.rs");
let cargo_toml = temp_dir.path().join("Cargo.toml");
std::fs::write(
&cargo_toml,
r#"[package]
name = "test_crate"
version = "0.1.0"
edition = "2021"
"#,
)
.expect("Failed to write Cargo.toml");
Self {
workspace_root: temp_dir.path().to_path_buf(),
_temp_dir: temp_dir,
source: source.to_string(),
}
}
pub fn execute(&self, goal: &Goal) -> Result<String, String> {
let mut ctx = AnalysisContext::from_workspace_root(&self.workspace_root)
.map_err(|e| format!("Context creation error: {e}"))?;
let specs = Planner::plan(goal, Some(&ctx.registry))
.map_err(|e| format!("Planning failed: {}", e))?;
if specs.is_empty() {
return Ok(self.source.clone());
}
let blueprint = ParallelBlueprint::from_mutations(specs);
if blueprint.needs_escalation() {
return Err("Blueprint has conflicts".to_string());
}
let executor = BlueprintExecutor::default();
let result = executor.execute_v2(&blueprint, &mut ctx);
if !result.success {
return Err(result.error.unwrap_or_else(|| "Unknown error".to_string()));
}
BlueprintExecutor::sync_files_and_rebuild(&result, &mut ctx).map_err(|e| e.to_string())?;
ctx.files()
.iter()
.next()
.map(|(_, file)| file.to_source().map_err(|e| e.to_string()))
.ok_or_else(|| "No files".to_string())?
}
pub fn assert_output(&self, goal: &Goal, expected: &str) {
let result = self.execute(goal).expect("Transformation failed");
if !sources_equal(expected, &result) {
let diff = source_diff(expected, &result);
eprintln!("=== ACTUAL OUTPUT ===\n{}\n=== END ===", result);
panic!("Mismatch!\n\nDiff:\n{}", diff);
}
}
}
#[rstest]
fn test_add_default_derive(pattern_ctx: PatternTestContext) {
let goal: Goal = serde_json::from_str(
r#"{
"intents": [{
"type": "AddDerive",
"target_name": "Config",
"derives": ["Default"]
}]
}"#,
)
.unwrap();
let expected = include_str!("snapshots/add-default-derive.snap");
pattern_ctx.assert_output(&goal, expected);
}
#[rstest]
fn test_add_eq_derive(pattern_ctx: PatternTestContext) {
let goal: Goal = serde_json::from_str(
r#"{
"intents": [{
"type": "AddDerive",
"target_name": "Token",
"derives": ["PartialEq", "Eq"]
}]
}"#,
)
.unwrap();
let expected = include_str!("snapshots/add-eq-derive.snap");
pattern_ctx.assert_output(&goal, expected);
}
#[rstest]
fn test_add_hash_derive(pattern_ctx: PatternTestContext) {
let goal: Goal = serde_json::from_str(
r#"{
"intents": [{
"type": "AddDerive",
"target_name": "CacheKey",
"derives": ["Hash"]
}]
}"#,
)
.unwrap();
let expected = include_str!("snapshots/add-hash-derive.snap");
pattern_ctx.assert_output(&goal, expected);
}
#[rstest]
fn test_add_constructor(pattern_ctx: PatternTestContext) {
let goal: Goal = serde_json::from_str(
r#"{
"intents": [{
"type": "AddMethod",
"target_type": "Counter",
"method_name": "new",
"params": [["max", "i32"]],
"return_type": "Self",
"body": "Self { value: 0, max }",
"is_pub": true
}]
}"#,
)
.unwrap();
let result = pattern_ctx.execute(&goal).expect("Transformation failed");
assert!(result.contains("pub fn new"));
}
#[rstest]
fn test_add_from_impl(pattern_ctx: PatternTestContext) {
let goal: Goal = serde_json::from_str(
r#"{
"intents": [{
"type": "AddItem",
"symbol_path": "test_crate",
"item_kind": "Impl",
"content": "impl From<u64> for UserId { fn from(id: u64) -> Self { UserId(id) } }"
}]
}"#,
)
.unwrap();
let expected = include_str!("snapshots/add-from-impl.snap");
pattern_ctx.assert_output(&goal, expected);
}