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}