Skip to main content

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}