Skip to content

Commit e22297d

Browse files
isPANNclaudeGiggleLiu
authored
Restructure tests: split test and source code (#27)
* Restructure tests: extract inline tests to src/tests_unit/, consolidate integration tests - Extract #[cfg(test)] mod tests blocks from 73 src/ files into src/tests_unit/ using #[path] - Consolidate 6 integration test binaries into 1 (tests/main.rs) - Move internal tests (property, reduction graph, trait consistency, unitdiskmapping algorithms) from tests/ to src/tests_unit/ - Keep only user-facing tests in tests/: integration.rs and reductions.rs - All 1582 tests preserved, 0 failures, clippy clean Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Rename src/tests_unit/ to src/unit_tests/ and fix coverage gap - Rename tests_unit → unit_tests for clearer distinction from src/testing/ - Fix test_brute_force_satisfiable: use valid_only(false) so the loop body actually executes on the UNSAT instance, covering the 2 missing lines Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * Fix review feedback: portable temp paths, consistent ConfigIterator, accurate comment - Replace hardcoded /tmp/ paths in IO tests with std::env::temp_dir() + unique timestamps - Fix ConfigIterator::total() to return 0 when num_variables == 0, matching iteration behavior - Fix factoring test comment to accurately describe the assertion (at least one, not both) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: GiggleLiu <cacate0129@gmail.com>
1 parent 90573dc commit e22297d

169 files changed

Lines changed: 15009 additions & 15708 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.claude/CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ make test clippy export-graph # Must pass before PR
2626
- `src/traits.rs` - `Problem`, `ConstraintSatisfactionProblem` traits
2727
- `src/rules/traits.rs` - `ReduceTo<T>`, `ReductionResult` traits
2828
- `src/registry/` - Compile-time reduction metadata collection
29+
- `src/unit_tests/` - Unit test files (extracted from inline `mod tests` blocks via `#[path]`)
30+
- `tests/main.rs` - User-facing integration tests only (modules in `tests/suites/`)
2931

3032
### Trait Hierarchy
3133

.claude/rules/testing.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,23 @@ make clippy # No warnings
4848
make coverage # >95% for new code
4949
```
5050

51+
## Test File Organization
52+
53+
Unit tests live in `src/unit_tests/`, mirroring `src/` structure. Source files reference them via `#[path]`:
54+
55+
```rust
56+
// In src/rules/foo_bar.rs:
57+
#[cfg(test)]
58+
#[path = "../unit_tests/rules/foo_bar.rs"]
59+
mod tests;
60+
```
61+
62+
The `#[path]` is relative to the source file's directory. `use super::*` in the test file resolves to the parent module (same as inline tests).
63+
64+
Integration tests are consolidated into a single binary at `tests/main.rs`, with test modules in `tests/suites/`.
65+
5166
## Anti-patterns
5267
- Don't skip closed-loop tests for reductions
5368
- Don't test only happy paths - include edge cases
5469
- Don't ignore clippy warnings
70+
- Don't add inline `mod tests` blocks in `src/` — use `src/unit_tests/` with `#[path]`

src/config.rs

Lines changed: 8 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,12 @@ pub struct ConfigIterator {
1515
impl ConfigIterator {
1616
/// Create a new configuration iterator.
1717
pub fn new(num_variables: usize, num_flavors: usize) -> Self {
18-
let total_configs = num_flavors.pow(num_variables as u32);
19-
let current = if num_variables == 0 || num_flavors == 0 {
18+
let total_configs = if num_variables == 0 || num_flavors == 0 {
19+
0
20+
} else {
21+
num_flavors.pow(num_variables as u32)
22+
};
23+
let current = if total_configs == 0 {
2024
None
2125
} else {
2226
Some(vec![0; num_variables])
@@ -108,109 +112,5 @@ pub fn bits_to_config(bits: &[bool]) -> Vec<usize> {
108112
}
109113

110114
#[cfg(test)]
111-
mod tests {
112-
use super::*;
113-
114-
#[test]
115-
fn test_config_iterator_binary() {
116-
let iter = ConfigIterator::new(3, 2);
117-
assert_eq!(iter.total(), 8);
118-
119-
let configs: Vec<_> = iter.collect();
120-
assert_eq!(configs.len(), 8);
121-
assert_eq!(configs[0], vec![0, 0, 0]);
122-
assert_eq!(configs[1], vec![0, 0, 1]);
123-
assert_eq!(configs[2], vec![0, 1, 0]);
124-
assert_eq!(configs[3], vec![0, 1, 1]);
125-
assert_eq!(configs[4], vec![1, 0, 0]);
126-
assert_eq!(configs[5], vec![1, 0, 1]);
127-
assert_eq!(configs[6], vec![1, 1, 0]);
128-
assert_eq!(configs[7], vec![1, 1, 1]);
129-
}
130-
131-
#[test]
132-
fn test_config_iterator_ternary() {
133-
let iter = ConfigIterator::new(2, 3);
134-
assert_eq!(iter.total(), 9);
135-
136-
let configs: Vec<_> = iter.collect();
137-
assert_eq!(configs.len(), 9);
138-
assert_eq!(configs[0], vec![0, 0]);
139-
assert_eq!(configs[1], vec![0, 1]);
140-
assert_eq!(configs[2], vec![0, 2]);
141-
assert_eq!(configs[3], vec![1, 0]);
142-
assert_eq!(configs[8], vec![2, 2]);
143-
}
144-
145-
#[test]
146-
fn test_config_iterator_empty() {
147-
let iter = ConfigIterator::new(0, 2);
148-
assert_eq!(iter.total(), 1);
149-
let configs: Vec<_> = iter.collect();
150-
assert_eq!(configs.len(), 0); // Empty because num_variables is 0
151-
}
152-
153-
#[test]
154-
fn test_config_iterator_single_variable() {
155-
let iter = ConfigIterator::new(1, 4);
156-
assert_eq!(iter.total(), 4);
157-
158-
let configs: Vec<_> = iter.collect();
159-
assert_eq!(configs, vec![vec![0], vec![1], vec![2], vec![3]]);
160-
}
161-
162-
#[test]
163-
fn test_index_to_config() {
164-
assert_eq!(index_to_config(0, 3, 2), vec![0, 0, 0]);
165-
assert_eq!(index_to_config(1, 3, 2), vec![0, 0, 1]);
166-
assert_eq!(index_to_config(7, 3, 2), vec![1, 1, 1]);
167-
assert_eq!(index_to_config(5, 3, 2), vec![1, 0, 1]);
168-
}
169-
170-
#[test]
171-
fn test_config_to_index() {
172-
assert_eq!(config_to_index(&[0, 0, 0], 2), 0);
173-
assert_eq!(config_to_index(&[0, 0, 1], 2), 1);
174-
assert_eq!(config_to_index(&[1, 1, 1], 2), 7);
175-
assert_eq!(config_to_index(&[1, 0, 1], 2), 5);
176-
}
177-
178-
#[test]
179-
fn test_index_config_roundtrip() {
180-
for i in 0..27 {
181-
let config = index_to_config(i, 3, 3);
182-
let back = config_to_index(&config, 3);
183-
assert_eq!(i, back);
184-
}
185-
}
186-
187-
#[test]
188-
fn test_config_to_bits() {
189-
assert_eq!(
190-
config_to_bits(&[0, 1, 0, 1]),
191-
vec![false, true, false, true]
192-
);
193-
assert_eq!(config_to_bits(&[0, 0, 0]), vec![false, false, false]);
194-
assert_eq!(config_to_bits(&[1, 1, 1]), vec![true, true, true]);
195-
}
196-
197-
#[test]
198-
fn test_bits_to_config() {
199-
assert_eq!(
200-
bits_to_config(&[false, true, false, true]),
201-
vec![0, 1, 0, 1]
202-
);
203-
assert_eq!(bits_to_config(&[true, true, true]), vec![1, 1, 1]);
204-
}
205-
206-
#[test]
207-
fn test_exact_size_iterator() {
208-
let mut iter = ConfigIterator::new(3, 2);
209-
assert_eq!(iter.len(), 8);
210-
iter.next();
211-
assert_eq!(iter.len(), 7);
212-
iter.next();
213-
iter.next();
214-
assert_eq!(iter.len(), 5);
215-
}
216-
}
115+
#[path = "unit_tests/config.rs"]
116+
mod tests;

src/graph_types.rs

Lines changed: 2 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -67,75 +67,5 @@ declare_graph_subtype!(PlanarGraph => SimpleGraph);
6767
declare_graph_subtype!(BipartiteGraph => SimpleGraph);
6868

6969
#[cfg(test)]
70-
mod tests {
71-
use super::*;
72-
73-
#[test]
74-
fn test_reflexive_subtype() {
75-
fn assert_subtype<A: GraphSubtype<B>, B: GraphMarker>() {}
76-
77-
// Every type is a subtype of itself
78-
assert_subtype::<SimpleGraph, SimpleGraph>();
79-
assert_subtype::<PlanarGraph, PlanarGraph>();
80-
assert_subtype::<UnitDiskGraph, UnitDiskGraph>();
81-
}
82-
83-
#[test]
84-
fn test_subtype_entries_registered() {
85-
let entries: Vec<_> = inventory::iter::<GraphSubtypeEntry>().collect();
86-
87-
// Should have at least 4 entries
88-
assert!(entries.len() >= 4);
89-
90-
// Check specific relationships
91-
assert!(entries
92-
.iter()
93-
.any(|e| e.subtype == "UnitDiskGraph" && e.supertype == "SimpleGraph"));
94-
assert!(entries
95-
.iter()
96-
.any(|e| e.subtype == "PlanarGraph" && e.supertype == "SimpleGraph"));
97-
}
98-
99-
#[test]
100-
fn test_declared_subtypes() {
101-
fn assert_subtype<A: GraphSubtype<B>, B: GraphMarker>() {}
102-
103-
// Declared relationships
104-
assert_subtype::<UnitDiskGraph, PlanarGraph>();
105-
assert_subtype::<UnitDiskGraph, SimpleGraph>();
106-
assert_subtype::<PlanarGraph, SimpleGraph>();
107-
assert_subtype::<BipartiteGraph, SimpleGraph>();
108-
}
109-
110-
#[test]
111-
fn test_graph_type_traits() {
112-
// Test Default
113-
let _: SimpleGraph = Default::default();
114-
let _: PlanarGraph = Default::default();
115-
let _: UnitDiskGraph = Default::default();
116-
let _: BipartiteGraph = Default::default();
117-
118-
// Test Copy (SimpleGraph implements Copy, so no need to clone)
119-
let g = SimpleGraph;
120-
let _g2 = g; // Copy
121-
let g = SimpleGraph;
122-
let _g2 = g;
123-
let _g3 = g; // still usable
124-
}
125-
126-
#[test]
127-
fn test_bipartite_entry_registered() {
128-
let entries: Vec<_> = inventory::iter::<GraphSubtypeEntry>().collect();
129-
assert!(entries
130-
.iter()
131-
.any(|e| e.subtype == "BipartiteGraph" && e.supertype == "SimpleGraph"));
132-
}
133-
134-
#[test]
135-
fn test_unit_disk_to_planar_registered() {
136-
let entries: Vec<_> = inventory::iter::<GraphSubtypeEntry>().collect();
137-
assert!(entries
138-
.iter()
139-
.any(|e| e.subtype == "UnitDiskGraph" && e.supertype == "PlanarGraph"));
140-
}
141-
}
70+
#[path = "unit_tests/graph_types.rs"]
71+
mod tests;

src/io.rs

Lines changed: 2 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -129,86 +129,5 @@ pub fn write_file<P: AsRef<Path>>(path: P, contents: &str) -> Result<()> {
129129
}
130130

131131
#[cfg(test)]
132-
mod tests {
133-
use super::*;
134-
use crate::models::graph::IndependentSet;
135-
use crate::topology::SimpleGraph;
136-
use std::fs;
137-
138-
#[test]
139-
fn test_to_json() {
140-
let problem = IndependentSet::<SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2)]);
141-
let json = to_json(&problem);
142-
assert!(json.is_ok());
143-
let json = json.unwrap();
144-
assert!(json.contains("graph"));
145-
}
146-
147-
#[test]
148-
fn test_from_json() {
149-
let problem = IndependentSet::<SimpleGraph, i32>::new(3, vec![(0, 1), (1, 2)]);
150-
let json = to_json(&problem).unwrap();
151-
let restored: IndependentSet<SimpleGraph, i32> = from_json(&json).unwrap();
152-
assert_eq!(restored.num_vertices(), 3);
153-
assert_eq!(restored.num_edges(), 2);
154-
}
155-
156-
#[test]
157-
fn test_json_compact() {
158-
let problem = IndependentSet::<SimpleGraph, i32>::new(3, vec![(0, 1)]);
159-
let compact = to_json_compact(&problem).unwrap();
160-
let pretty = to_json(&problem).unwrap();
161-
// Compact should be shorter
162-
assert!(compact.len() < pretty.len());
163-
}
164-
165-
#[test]
166-
fn test_file_roundtrip() {
167-
let problem = IndependentSet::<SimpleGraph, i32>::new(4, vec![(0, 1), (1, 2), (2, 3)]);
168-
let path = "/tmp/test_problem.json";
169-
170-
// Write
171-
write_problem(&problem, path, FileFormat::Json).unwrap();
172-
173-
// Read back
174-
let restored: IndependentSet<SimpleGraph, i32> = read_problem(path, FileFormat::Json).unwrap();
175-
assert_eq!(restored.num_vertices(), 4);
176-
assert_eq!(restored.num_edges(), 3);
177-
178-
// Cleanup
179-
fs::remove_file(path).ok();
180-
}
181-
182-
#[test]
183-
fn test_file_format_from_extension() {
184-
assert_eq!(
185-
FileFormat::from_extension(Path::new("test.json")),
186-
Some(FileFormat::Json)
187-
);
188-
assert_eq!(
189-
FileFormat::from_extension(Path::new("test.JSON")),
190-
Some(FileFormat::Json)
191-
);
192-
assert_eq!(FileFormat::from_extension(Path::new("test.txt")), None);
193-
assert_eq!(FileFormat::from_extension(Path::new("noext")), None);
194-
}
195-
196-
#[test]
197-
fn test_read_write_file() {
198-
let path = "/tmp/test_io.txt";
199-
let contents = "Hello, World!";
200-
201-
write_file(path, contents).unwrap();
202-
let read_back = read_file(path).unwrap();
203-
204-
assert_eq!(read_back, contents);
205-
206-
fs::remove_file(path).ok();
207-
}
208-
209-
#[test]
210-
fn test_invalid_json() {
211-
let result: Result<IndependentSet<SimpleGraph, i32>> = from_json("not valid json");
212-
assert!(result.is_err());
213-
}
214-
}
132+
#[path = "unit_tests/io.rs"]
133+
mod tests;

src/lib.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,19 @@ pub use types::{EnergyMode, LocalConstraint, LocalSolutionSize, ProblemSize, Sol
113113

114114
// Re-export proc macro for reduction registration
115115
pub use problemreductions_macros::reduction;
116+
117+
#[cfg(test)]
118+
#[path = "unit_tests/graph_models.rs"]
119+
mod test_graph_models;
120+
#[cfg(test)]
121+
#[path = "unit_tests/property.rs"]
122+
mod test_property;
123+
#[cfg(test)]
124+
#[path = "unit_tests/reduction_graph.rs"]
125+
mod test_reduction_graph;
126+
#[cfg(test)]
127+
#[path = "unit_tests/trait_consistency.rs"]
128+
mod test_trait_consistency;
129+
#[cfg(test)]
130+
#[path = "unit_tests/unitdiskmapping_algorithms/mod.rs"]
131+
mod test_unitdiskmapping_algorithms;

0 commit comments

Comments
 (0)