pattern_core/test_utils/equivalence.rs
1//! Equivalence checking utilities for comparing pattern-rs and gram-hs implementations
2//!
3//! # Using gramref CLI for Reference Outputs
4//!
5//! The `gramref` CLI tool can be used to get reference outputs for comparison:
6//!
7//! ```bash
8//! # Get reference output (value only, canonical format)
9//! echo '(node1)' | gramref parse --format json --value-only --canonical
10//! ```
11//!
12//! See [gramref CLI Testing Guide](../../../../docs/gramref-cli-testing-guide.md) for
13//! comprehensive usage examples and integration patterns.
14
15use serde::{Deserialize, Serialize};
16use std::fmt::Debug;
17
18/// Result of an equivalence check between pattern-rs and gram-hs implementations
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct EquivalenceResult {
21 /// Whether the outputs are equivalent
22 pub equivalent: bool,
23 /// List of differences found (if not equivalent)
24 pub differences: Vec<Difference>,
25 /// Method used for comparison
26 pub comparison_method: ComparisonMethod,
27}
28
29/// A single difference found during equivalence checking
30#[derive(Debug, Clone, PartialEq, Eq)]
31pub struct Difference {
32 /// Path to the field that differs
33 pub path: Vec<String>,
34 /// Expected value (from gram-hs)
35 pub expected: String,
36 /// Actual value (from pattern-rs)
37 pub actual: String,
38 /// Description of the difference
39 pub description: String,
40}
41
42/// Method used for equivalence comparison
43#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum ComparisonMethod {
45 /// Direct comparison using PartialEq
46 Direct,
47 /// JSON serialization comparison
48 Json,
49 /// Test data comparison
50 TestData,
51}
52
53/// Options for equivalence checking
54#[derive(Debug, Clone)]
55pub struct EquivalenceOptions {
56 /// Whether to use approximate equality for floating-point values
57 pub approximate_float_equality: bool,
58 /// Tolerance for floating-point comparisons
59 pub float_tolerance: f64,
60 /// Fields to ignore during comparison
61 pub ignore_fields: Vec<String>,
62 /// Comparison method to use
63 pub comparison_method: ComparisonMethod,
64}
65
66impl Default for EquivalenceOptions {
67 fn default() -> Self {
68 Self {
69 approximate_float_equality: false,
70 float_tolerance: 1e-6,
71 ignore_fields: Vec::new(),
72 comparison_method: ComparisonMethod::Json,
73 }
74 }
75}
76
77/// Check equivalence between pattern-rs and gram-hs outputs
78///
79/// # Arguments
80///
81/// * `gram_rs_output` - Output from pattern-rs implementation
82/// * `gram_hs_output` - Output from gram-hs implementation (or test data)
83/// * `options` - Comparison options
84///
85/// # Returns
86///
87/// `EquivalenceResult` containing comparison results
88pub fn check_equivalence<T>(
89 gram_rs_output: &T,
90 gram_hs_output: &T,
91 options: &EquivalenceOptions,
92) -> EquivalenceResult
93where
94 T: Serialize + PartialEq + Debug,
95{
96 match options.comparison_method {
97 ComparisonMethod::Direct => {
98 let equivalent = gram_rs_output == gram_hs_output;
99 EquivalenceResult {
100 equivalent,
101 differences: if equivalent {
102 Vec::new()
103 } else {
104 vec![Difference {
105 path: vec!["root".to_string()],
106 expected: format!("{:?}", gram_hs_output),
107 actual: format!("{:?}", gram_rs_output),
108 description: "Direct comparison: outputs differ".to_string(),
109 }]
110 },
111 comparison_method: ComparisonMethod::Direct,
112 }
113 }
114 ComparisonMethod::Json => {
115 // Use JSON serialization for detailed comparison
116 let rs_json = serde_json::to_string(gram_rs_output)
117 .unwrap_or_else(|_| format!("{:?}", gram_rs_output));
118 let hs_json = serde_json::to_string(gram_hs_output)
119 .unwrap_or_else(|_| format!("{:?}", gram_hs_output));
120
121 let equivalent = rs_json == hs_json;
122 EquivalenceResult {
123 equivalent,
124 differences: if equivalent {
125 Vec::new()
126 } else {
127 vec![Difference {
128 path: vec!["root".to_string()],
129 expected: hs_json,
130 actual: rs_json,
131 description: "JSON comparison: outputs differ".to_string(),
132 }]
133 },
134 comparison_method: ComparisonMethod::Json,
135 }
136 }
137 ComparisonMethod::TestData => {
138 // For test data comparison, use direct comparison
139 // Full test data comparison will be implemented when test case structure is finalized
140 check_equivalence(
141 gram_rs_output,
142 gram_hs_output,
143 &EquivalenceOptions {
144 comparison_method: ComparisonMethod::Direct,
145 ..options.clone()
146 },
147 )
148 }
149 }
150}
151
152/// Check equivalence using extracted test data from gram-hs
153///
154/// # Arguments
155///
156/// * `test_case` - Test case from extracted gram-hs data
157/// * `gram_rs_impl` - Function that executes pattern-rs implementation
158/// * `options` - Comparison options
159///
160/// # Returns
161///
162/// `EquivalenceResult` containing comparison results
163pub fn check_equivalence_from_test_data<T, F>(
164 _test_case: &TestCase,
165 _gram_rs_impl: F,
166 _options: &EquivalenceOptions,
167) -> EquivalenceResult
168where
169 T: Serialize + PartialEq + Debug,
170 F: FnOnce(&TestCaseInput) -> T,
171{
172 // Placeholder implementation
173 // Will be fully implemented when test case structure is defined
174 EquivalenceResult {
175 equivalent: false,
176 differences: vec![Difference {
177 path: vec!["test_case".to_string()],
178 expected: "test_case.expected".to_string(),
179 actual: "test_case.actual".to_string(),
180 description: "Test case comparison not yet implemented".to_string(),
181 }],
182 comparison_method: ComparisonMethod::TestData,
183 }
184}
185
186/// Test case structure (placeholder - will match test-sync-format.md from feature 002)
187#[derive(Debug, Clone, Serialize, Deserialize)]
188pub struct TestCase {
189 pub name: String,
190 pub description: String,
191 pub input: TestCaseInput,
192 pub expected: TestCaseOutput,
193}
194
195/// Test case input (placeholder)
196#[derive(Debug, Clone, Serialize, Deserialize)]
197pub struct TestCaseInput {
198 pub r#type: String,
199 pub value: serde_json::Value,
200}
201
202/// Test case output (placeholder)
203#[derive(Debug, Clone, Serialize, Deserialize)]
204pub struct TestCaseOutput {
205 pub r#type: String,
206 pub value: serde_json::Value,
207}