diff --git a/rust/src/layers.rs b/rust/src/layers.rs index 5d809c7c..87549c02 100644 --- a/rust/src/layers.rs +++ b/rust/src/layers.rs @@ -6,9 +6,9 @@ use std::collections::HashSet; use std::time::Instant; /// A group of layers at the same level in the layering. -/// These layers should be independent. #[derive(PartialEq, Eq, Hash, Debug)] pub struct Level<'a> { + pub independent: bool, pub layers: Vec<&'a str>, } @@ -69,13 +69,18 @@ fn _generate_module_permutations<'a>( continue; } - // Build the layers that mustn't import this higher layer. That - // includes lower layers and siblings. + // Build the layers that mustn't import this higher layer. + // That includes: + // * lower layers. + // * sibling layers, if the layer is independent. let mut layers_forbidden_to_import_higher_layer: Vec<&str> = vec![]; - for potential_sibling_layer in &higher_level.layers { - if potential_sibling_layer != higher_layer { - // It's a sibling layer. - layers_forbidden_to_import_higher_layer.push(potential_sibling_layer); + + if higher_level.independent { + for potential_sibling_layer in &higher_level.layers { + if potential_sibling_layer != higher_layer { + // It's a sibling layer. + layers_forbidden_to_import_higher_layer.push(potential_sibling_layer); + } } } @@ -333,12 +338,15 @@ mod tests { ])); let levels = vec![ Level { + independent: true, layers: vec!["high"], }, Level { + independent: true, layers: vec!["mid_a", "mid_b", "mid_c"], }, Level { + independent: true, layers: vec!["low"], }, ]; @@ -405,9 +413,11 @@ mod tests { ])); let levels = vec![ Level { + independent: true, layers: vec!["high"], }, Level { + independent: true, layers: vec!["low"], }, ]; @@ -460,12 +470,15 @@ mod tests { ])); let levels = vec![ Level { + independent: true, layers: vec!["high"], }, Level { + independent: true, layers: vec!["mid_a", "mid_b", "mid_c"], }, Level { + independent: true, layers: vec!["low"], }, ]; @@ -553,16 +566,109 @@ mod tests { ); } + #[test] + fn test_generate_module_permutations_sibling_layer_not_independent() { + let graph = ImportGraph::new(HashMap::from([ + ("mypackage.low", HashSet::new()), + ("mypackage.low.blue", HashSet::from(["mypackage.utils"])), + ("mypackage.low.green", HashSet::new()), + ( + "mypackage.low.green.alpha", + HashSet::from(["mypackage.high.yellow"]), + ), + ("mypackage.mid_a", HashSet::new()), + ("mypackage.mid_a.foo", HashSet::new()), + ("mypackage.mid_b", HashSet::new()), + ("mypackage.mid_b.foo", HashSet::new()), + ("mypackage.mid_c", HashSet::new()), + ("mypackage.mid_c.foo", HashSet::new()), + ("mypackage.high", HashSet::from(["mypackage.low.blue"])), + ("mypackage.high.yellow", HashSet::new()), + ("mypackage.high.red", HashSet::new()), + ("mypackage.high.red.beta", HashSet::new()), + ("mypackage.utils", HashSet::from(["mypackage.high.red"])), + ])); + let levels = vec![ + Level { + independent: true, + layers: vec!["high"], + }, + Level { + independent: false, + layers: vec!["mid_a", "mid_b", "mid_c"], + }, + Level { + independent: true, + layers: vec!["low"], + }, + ]; + let containers = HashSet::from(["mypackage"]); + + let perms = _generate_module_permutations(&graph, &levels, &containers); + + let result: HashSet<(String, String, Option)> = HashSet::from_iter(perms); + let (high, mid_a, mid_b, mid_c, low) = ( + "mypackage.high", + "mypackage.mid_a", + "mypackage.mid_b", + "mypackage.mid_c", + "mypackage.low", + ); + assert_eq!( + result, + HashSet::from_iter([ + ( + high.to_string(), + mid_a.to_string(), + Some("mypackage".to_string()) + ), + ( + high.to_string(), + mid_b.to_string(), + Some("mypackage".to_string()) + ), + ( + high.to_string(), + mid_c.to_string(), + Some("mypackage".to_string()) + ), + ( + high.to_string(), + low.to_string(), + Some("mypackage".to_string()) + ), + ( + mid_a.to_string(), + low.to_string(), + Some("mypackage".to_string()) + ), + ( + mid_b.to_string(), + low.to_string(), + Some("mypackage".to_string()) + ), + ( + mid_c.to_string(), + low.to_string(), + Some("mypackage".to_string()) + ), + ]) + ); + } + #[test] fn test_layers_from_levels() { let levels = vec![ Level { + independent: true, layers: vec!["high"], }, Level { + independent: true, layers: vec!["medium_a", "medium_b", "medium_c"], }, Level { + independent: true, layers: vec!["low"], }, ]; diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 433bb066..b6a9de36 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -35,7 +35,7 @@ pub fn find_illegal_dependencies<'a>( info!("Using Rust to find illegal dependencies."); let graph = ImportGraph::new(importeds_by_importer.extract()?); - let levels_rust = rustify_levels(levels); + let levels_rust = rustify_levels(levels)?; let containers_rust: HashSet<&str> = containers.extract()?; if let Err(err) = check_containers_exist(&graph, &containers_rust) { @@ -47,15 +47,18 @@ pub fn find_illegal_dependencies<'a>( convert_dependencies_to_python(py, dependencies, &graph) } -fn rustify_levels(levels_python: &PyTuple) -> Vec { +fn rustify_levels(levels_python: &PyTuple) -> PyResult> { let mut rust_levels: Vec = vec![]; for level_python in levels_python.into_iter() { - let layers: HashSet<&str> = level_python.extract().unwrap(); + let level_dict = level_python.downcast::()?; + let independent: bool = level_dict.get_item("independent").unwrap().extract()?; + let layers: HashSet<&str> = level_dict.get_item("layers").unwrap().extract()?; rust_levels.push(Level { + independent, layers: layers.into_iter().collect(), }); } - rust_levels + Ok(rust_levels) } fn convert_dependencies_to_python<'a>( @@ -104,31 +107,56 @@ fn convert_dependencies_to_python<'a>( #[cfg(test)] mod tests { use super::*; - use pyo3::types::{PySet, PyTuple}; + + // Macro to easily define a python dict. + // Adapted from the hash_map! macro in https://github.com/jofas/map_macro. + macro_rules! pydict { + ($py: ident, {$($k: expr => $v: expr),*, $(,)?}) => { + { + let dict = PyDict::new($py); + $( + dict.set_item($k, $v)?; + )* + dict + } + }; + } #[test] fn test_rustify_levels_no_sibling_layers() { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| -> PyResult<()> { - let elements: Vec<&PySet> = vec![ - PySet::new(py, &vec!["high"]).unwrap(), - PySet::new(py, &vec!["medium"]).unwrap(), - PySet::new(py, &vec!["low"]).unwrap(), + let elements: Vec<&PyDict> = vec![ + pydict! (py, { + "independent" => true, + "layers" => HashSet::from(["high"]), + }), + pydict! (py, { + "independent" => true, + "layers" => HashSet::from(["medium"]), + }), + pydict! (py, { + "independent" => true, + "layers" => HashSet::from(["low"]), + }) ]; let python_levels: &PyTuple = PyTuple::new(py, elements); - let result = rustify_levels(python_levels); + let result = rustify_levels(python_levels)?; assert_eq!( result, vec![ Level { + independent: true, layers: vec!["high"] }, Level { + independent: true, layers: vec!["medium"] }, Level { + independent: true, layers: vec!["low"] } ] @@ -143,14 +171,27 @@ mod tests { fn test_rustify_levels_sibling_layers() { pyo3::prepare_freethreaded_python(); Python::with_gil(|py| -> PyResult<()> { - let elements: Vec<&PySet> = vec![ - PySet::new(py, &vec!["high"]).unwrap(), - PySet::new(py, &vec!["blue", "green", "orange"]).unwrap(), - PySet::new(py, &vec!["low"]).unwrap(), + let elements: Vec<&PyDict> = vec![ + pydict! (py, { + "independent" => true, + "layers" => HashSet::from(["high"]), + }), + pydict! (py, { + "independent" => true, + "layers" => HashSet::from(["blue", "green", "orange"]), + }), + pydict! (py, { + "independent" => false, + "layers" => HashSet::from(["red", "yellow"]), + }), + pydict! (py, { + "independent" => true, + "layers" => HashSet::from(["low"]), + }) ]; let python_levels: &PyTuple = PyTuple::new(py, elements); - let mut result = rustify_levels(python_levels); + let mut result = rustify_levels(python_levels)?; for level in &mut result { level.layers.sort(); @@ -159,12 +200,19 @@ mod tests { result, vec![ Level { + independent: true, layers: vec!["high"] }, Level { + independent: true, layers: vec!["blue", "green", "orange"] }, Level { + independent: false, + layers: vec!["red", "yellow"] + }, + Level { + independent: true, layers: vec!["low"] } ] diff --git a/rust/tests/large.rs b/rust/tests/large.rs index d24bfbc6..fc030da0 100644 --- a/rust/tests/large.rs +++ b/rust/tests/large.rs @@ -21,18 +21,23 @@ fn test_large_graph() { let levels = vec![ Level { + independent: true, layers: vec!["plugins"], }, Level { + independent: true, layers: vec!["interfaces"], }, Level { + independent: true, layers: vec!["application"], }, Level { + independent: true, layers: vec!["domain"], }, Level { + independent: true, layers: vec!["data"], }, ];