problemreductions/testing/
mod.rs

1//! Testing utilities and macros for problem implementations.
2//!
3//! This module provides macros and helpers to reduce test boilerplate by ~90%
4//! when implementing new problems. Instead of writing 300+ lines of tests per
5//! problem, you can use these macros to generate comprehensive test suites.
6//!
7//! # Macros
8//!
9//! ## `graph_problem_tests!`
10//!
11//! Generates a complete test suite for graph problems:
12//!
13//! ```rust,ignore
14//! use problemreductions::graph_problem_tests;
15//!
16//! graph_problem_tests! {
17//!     problem_type: IndependentSetT<i32>,
18//!     constraint_type: IndependentSetConstraint,
19//!     test_cases: [
20//!         // (name, num_vertices, edges, valid_solution, expected_size, is_maximization)
21//!         (triangle, 3, [(0, 1), (1, 2), (0, 2)], [1, 0, 0], 1, true),
22//!         (path, 3, [(0, 1), (1, 2)], [1, 0, 1], 2, true),
23//!     ]
24//! }
25//! ```
26//!
27//! This generates tests for:
28//! - Problem creation and metadata
29//! - Solution validity and size computation
30//! - Energy mode (maximization vs minimization)
31//! - CSP interface (constraints, objectives)
32//! - Brute force solving (for small instances)
33//!
34//! ## `complement_test!`
35//!
36//! Tests that two problems are complements (e.g., IS + VC = n):
37//!
38//! ```rust,ignore
39//! use problemreductions::complement_test;
40//!
41//! complement_test! {
42//!     name: test_is_vc_complement,
43//!     problem_a: IndependentSetT<i32>,
44//!     problem_b: VertexCoverT<i32>,
45//!     test_graphs: [
46//!         (3, [(0, 1), (1, 2)]),
47//!         (4, [(0, 1), (1, 2), (2, 3)]),
48//!     ]
49//! }
50//! ```
51//!
52//! ## `quick_problem_test!`
53//!
54//! Quick single-instance validation:
55//!
56//! ```rust,ignore
57//! use problemreductions::quick_problem_test;
58//!
59//! quick_problem_test!(
60//!     IndependentSetT<i32>,
61//!     new(3, vec![(0, 1)]),
62//!     solution: [0, 0, 1],
63//!     expected_size: 1,
64//!     is_valid: true
65//! );
66//! ```
67//!
68//! # Test Case Types
69//!
70//! - [`GraphTestCase`] - Structured test case for graph problems
71//! - [`SatTestCase`] - Structured test case for SAT problems
72
73#[macro_use]
74mod macros;
75
76/// A test case for a graph problem.
77#[derive(Debug, Clone)]
78pub struct GraphTestCase<W> {
79    /// Number of vertices.
80    pub num_vertices: usize,
81    /// Edge list.
82    pub edges: Vec<(usize, usize)>,
83    /// Vertex weights (if any).
84    pub weights: Option<Vec<W>>,
85    /// A known valid solution.
86    pub valid_solution: Vec<usize>,
87    /// The expected objective value for the valid solution.
88    pub expected_size: W,
89    /// The optimal objective value (for brute force testing).
90    pub optimal_size: Option<W>,
91}
92
93impl<W: Clone> GraphTestCase<W> {
94    /// Create a new test case with unit weights.
95    pub fn new(
96        num_vertices: usize,
97        edges: Vec<(usize, usize)>,
98        valid_solution: Vec<usize>,
99        expected_size: W,
100    ) -> Self {
101        Self {
102            num_vertices,
103            edges,
104            weights: None,
105            valid_solution,
106            expected_size,
107            optimal_size: None,
108        }
109    }
110
111    /// Create a new test case with custom weights.
112    pub fn with_weights(
113        num_vertices: usize,
114        edges: Vec<(usize, usize)>,
115        weights: Vec<W>,
116        valid_solution: Vec<usize>,
117        expected_size: W,
118    ) -> Self {
119        Self {
120            num_vertices,
121            edges,
122            weights: Some(weights),
123            valid_solution,
124            expected_size,
125            optimal_size: None,
126        }
127    }
128
129    /// Set the optimal objective value.
130    pub fn with_optimal(mut self, optimal: W) -> Self {
131        self.optimal_size = Some(optimal);
132        self
133    }
134}
135
136/// A test case for a SAT problem.
137#[derive(Debug, Clone)]
138pub struct SatTestCase {
139    /// Number of variables.
140    pub num_vars: usize,
141    /// Clauses as lists of literals (positive = true, negative = negated).
142    pub clauses: Vec<Vec<i32>>,
143    /// A known satisfying assignment (if satisfiable).
144    pub satisfying_assignment: Option<Vec<usize>>,
145    /// Whether the formula is satisfiable.
146    pub is_satisfiable: bool,
147}
148
149impl SatTestCase {
150    /// Create a satisfiable test case.
151    pub fn satisfiable(
152        num_vars: usize,
153        clauses: Vec<Vec<i32>>,
154        satisfying_assignment: Vec<usize>,
155    ) -> Self {
156        Self {
157            num_vars,
158            clauses,
159            satisfying_assignment: Some(satisfying_assignment),
160            is_satisfiable: true,
161        }
162    }
163
164    /// Create an unsatisfiable test case.
165    pub fn unsatisfiable(num_vars: usize, clauses: Vec<Vec<i32>>) -> Self {
166        Self {
167            num_vars,
168            clauses,
169            satisfying_assignment: None,
170            is_satisfiable: false,
171        }
172    }
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_graph_test_case() {
181        let case = GraphTestCase::new(3, vec![(0, 1), (1, 2)], vec![1, 0, 1], 2);
182        assert_eq!(case.num_vertices, 3);
183        assert_eq!(case.edges.len(), 2);
184        assert!(case.weights.is_none());
185        assert!(case.optimal_size.is_none());
186    }
187
188    #[test]
189    fn test_graph_test_case_with_weights() {
190        let case = GraphTestCase::with_weights(
191            3,
192            vec![(0, 1)],
193            vec![1, 2, 3],
194            vec![0, 0, 1],
195            3,
196        );
197        assert!(case.weights.is_some());
198        assert_eq!(case.weights.as_ref().unwrap(), &vec![1, 2, 3]);
199    }
200
201    #[test]
202    fn test_graph_test_case_with_optimal() {
203        let case = GraphTestCase::new(3, vec![(0, 1)], vec![0, 0, 1], 1).with_optimal(2);
204        assert_eq!(case.optimal_size, Some(2));
205    }
206
207    #[test]
208    fn test_sat_test_case_satisfiable() {
209        let case = SatTestCase::satisfiable(2, vec![vec![1, 2], vec![-1]], vec![0, 1]);
210        assert!(case.is_satisfiable);
211        assert!(case.satisfying_assignment.is_some());
212    }
213
214    #[test]
215    fn test_sat_test_case_unsatisfiable() {
216        let case = SatTestCase::unsatisfiable(1, vec![vec![1], vec![-1]]);
217        assert!(!case.is_satisfiable);
218        assert!(case.satisfying_assignment.is_none());
219    }
220}