Skip to content

Commit

Permalink
Adapt rust find_illegal_dependencies to accept tuple of level dicts
Browse files Browse the repository at this point in the history
Rather than a tuple of strings accept a tuple of dicts.
This dict will contain the level layers as well as an
boolean indicating whether the layers within the level
should be independent of each other.
  • Loading branch information
Peter554 committed Nov 28, 2023
1 parent a91b84d commit 8554474
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 22 deletions.
120 changes: 113 additions & 7 deletions rust/src/layers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
}

Expand Down Expand Up @@ -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);
}
}
}

Expand Down Expand Up @@ -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"],
},
];
Expand Down Expand Up @@ -405,9 +413,11 @@ mod tests {
]));
let levels = vec![
Level {
independent: true,
layers: vec!["high"],
},
Level {
independent: true,
layers: vec!["low"],
},
];
Expand Down Expand Up @@ -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"],
},
];
Expand Down Expand Up @@ -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<String>)> = 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"],
},
];
Expand Down
78 changes: 63 additions & 15 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -47,15 +47,18 @@ pub fn find_illegal_dependencies<'a>(
convert_dependencies_to_python(py, dependencies, &graph)
}

fn rustify_levels(levels_python: &PyTuple) -> Vec<Level> {
fn rustify_levels(levels_python: &PyTuple) -> PyResult<Vec<Level>> {
let mut rust_levels: Vec<Level> = vec![];
for level_python in levels_python.into_iter() {
let layers: HashSet<&str> = level_python.extract().unwrap();
let level_dict = level_python.downcast::<PyDict>()?;
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>(
Expand Down Expand Up @@ -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"]
}
]
Expand All @@ -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();
Expand All @@ -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"]
}
]
Expand Down
5 changes: 5 additions & 0 deletions rust/tests/large.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
},
];
Expand Down

0 comments on commit 8554474

Please sign in to comment.