diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 00000000..7b035da8 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,22 @@ +version: '3' + +tasks: + test: + cmds: + - task: test.rust + - task: test.python + + test.python: + cmds: + - maturin develop + - pytest --benchmark-skip + + test.rust: + dir: rust + cmds: + - cargo test --no-default-features {{.F}} + + bench: + cmds: + - maturin develop --release + - pytest --benchmark-only --benchmark-autosave --benchmark-compare --benchmark-group-by=func --benchmark-columns mean \ No newline at end of file diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 9773fef5..d32dd381 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1,12 +1,15 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "_rustgrimp" version = "0.1.0" dependencies = [ + "anyhow", "bimap", + "derive-new", + "getset", "log", "petgraph", "pyo3", @@ -14,8 +17,15 @@ dependencies = [ "rayon", "rustc-hash", "serde_json", + "slotmap", ] +[[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + [[package]] name = "arc-swap" version = "1.7.1" @@ -65,6 +75,17 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "derive-new" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "either" version = "1.13.0" @@ -83,6 +104,18 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +[[package]] +name = "getset" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f636605b743120a8d32ed92fc27b6cde1a769f8f936c065151eb66f88ded513c" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -166,6 +199,28 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "280dc24453071f1b63954171985a0b0d30058d287960968b9b2aca264c8d4ee6" +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.93" @@ -322,6 +377,15 @@ dependencies = [ "serde", ] +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "version_check", +] + [[package]] name = "syn" version = "2.0.96" @@ -350,3 +414,9 @@ name = "unindent" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 00f48bc5..c920939c 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -15,6 +15,10 @@ rayon = "1.10" petgraph = "0.6.5" bimap = "0.6.3" rustc-hash = "2.1.0" +slotmap = "1.0.7" +getset = "0.1.3" +anyhow = "1.0.95" +derive-new = "0.7.0" [dependencies.pyo3] version = "0.23.4" diff --git a/rust/src/graph.rs b/rust/src/graph.rs index 248e84d6..b447b7f3 100644 --- a/rust/src/graph.rs +++ b/rust/src/graph.rs @@ -1,39 +1,10 @@ -use bimap::BiMap; -use log::info; -use petgraph::algo::astar; -use petgraph::graph::EdgeIndex; -use petgraph::stable_graph::{NodeIndex, StableGraph}; -use petgraph::visit::{Bfs, Walker}; -use petgraph::Direction; -use rayon::prelude::*; -use rustc_hash::{FxHashMap, FxHashSet}; -use std::fmt; -use std::time::Instant; - -/// A group of layers at the same level in the layering. -#[derive(PartialEq, Eq, Hash, Debug)] -pub struct Level { - pub layers: Vec, - pub independent: bool, -} - -// Delimiter for Python modules. -const DELIMITER: char = '.'; - -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct Module { - pub name: String, -} - -impl fmt::Display for Module { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.name) - } -} +use crate::hierarchy::ModuleHierarchy; +use crate::imports::{ImportDetails, ModuleImports}; +use rustc_hash::FxHashSet; #[derive(Debug, Clone, PartialEq)] -pub struct ModuleNotPresent { - pub module: Module, +pub struct ModuleNotPresent<'a> { + pub module: &'a str, } #[derive(Debug, Clone, PartialEq)] @@ -41,2927 +12,154 @@ pub struct NoSuchContainer { pub container: String, } -pub struct ModulesHaveSharedDescendants {} - -impl fmt::Display for ModuleNotPresent { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "\"{}\" not present in the graph", self.module.name) - } -} - -impl Module { - pub fn new(name: String) -> Module { - Module { name } - } - - // Returns whether the module is a root-level package. - pub fn is_root(&self) -> bool { - !self.name.contains(DELIMITER) - } - - // Create a Module that is the parent of the passed Module. - // - // Panics if the child is a root Module. - pub fn new_parent(child: &Module) -> Module { - let parent_name = match child.name.rsplit_once(DELIMITER) { - Some((base, _)) => base.to_string(), - None => panic!("{} is a root level package", child.name), - }; - - Module::new(parent_name) - } - - // Return whether this module is a descendant of the supplied one, based on the name. - pub fn is_descendant_of(&self, module: &Module) -> bool { - let candidate = format!("{}{}", module.name, DELIMITER); - self.name.starts_with(&candidate) - } -} - #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DetailedImport { - pub importer: Module, - pub imported: Module, +pub struct DetailedImport<'a> { + pub importer: &'a str, + pub imported: &'a str, pub line_number: usize, - pub line_contents: String, + pub line_contents: &'a str, } #[derive(Default, Clone)] pub struct Graph { - // Bidirectional lookup between Module and NodeIndex. - hierarchy_module_indices: BiMap, - hierarchy: StableGraph, - imports_module_indices: BiMap, - imports: StableGraph, - squashed_modules: FxHashSet, - // Invisible modules exist in the hierarchy but haven't been explicitly added to the graph. - invisible_modules: FxHashSet, - detailed_imports_map: FxHashMap<(Module, Module), FxHashSet>, -} - -#[derive(PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] -pub struct Route { - pub heads: Vec, - pub middle: Vec, - pub tails: Vec, -} - -#[derive(PartialEq, Eq, Hash, Debug, PartialOrd, Ord)] -pub struct PackageDependency { - pub importer: Module, - pub imported: Module, - pub routes: Vec, -} - -fn _module_from_layer(layer: &str, container: &Option) -> Module { - let module_name = match container { - Some(container) => format!("{}{}{}", container.name, DELIMITER, layer), - None => layer.to_string(), - }; - Module::new(module_name) -} - -fn _log_illegal_route_count(dependency_or_none: &Option, duration_in_s: u64) { - let route_count = match dependency_or_none { - Some(dependency) => dependency.routes.len(), - None => 0, - }; - let pluralized = if route_count == 1 { "" } else { "s" }; - info!( - "Found {} illegal route{} in {}s.", - route_count, pluralized, duration_in_s - ); + module_hierarchy: ModuleHierarchy, + module_imports: ModuleImports, } impl Graph { - pub fn pretty_str(&self) -> String { - let mut hierarchy: Vec = vec![]; - let mut imports: Vec = vec![]; - - let hierarchy_module_indices: Vec<_> = self.hierarchy_module_indices.iter().collect(); - - for (parent_module, parent_index) in hierarchy_module_indices { - for child_index in self.hierarchy.neighbors(*parent_index) { - let child_module = self - .hierarchy_module_indices - .get_by_right(&child_index) - .unwrap(); - let parent_module_str = match self.invisible_modules.contains(&parent_module) { - true => format!("({})", parent_module.name), - false => parent_module.name.to_string(), - }; - let child_module_str = match self.invisible_modules.contains(&child_module) { - true => format!("({})", child_module.name), - false => child_module.name.to_string(), - }; - hierarchy.push(format!(" {} -> {}", parent_module_str, child_module_str)); - } - } - - let imports_module_indices: Vec<_> = self.imports_module_indices.iter().collect(); - - for (from_module, from_index) in imports_module_indices { - for to_index in self.imports.neighbors(*from_index) { - let to_module = self.imports_module_indices.get_by_right(&to_index).unwrap(); - imports.push(format!(" {} -> {}", from_module.name, to_module.name)); - } - } - // Assemble String. - let mut pretty = String::new(); - pretty.push_str("hierarchy:\n"); - hierarchy.sort(); - pretty.push_str(&hierarchy.join("\n")); - pretty.push_str("\nimports:\n"); - imports.sort(); - pretty.push_str(&imports.join("\n")); - pretty.push('\n'); - pretty + pub fn add_module(&mut self, module: &str) { + self.module_hierarchy.add_module(module).unwrap(); } - pub fn add_module(&mut self, module: Module) { - // If this module is already in the graph, but invisible, just make it visible. - if self.invisible_modules.contains(&module) { - self.invisible_modules.remove(&module); - return; - } - // If this module is already in the graph, don't do anything. - if self.hierarchy_module_indices.get_by_left(&module).is_some() { - return; - } - - let module_index = self.hierarchy.add_node(module.clone()); - self.hierarchy_module_indices - .insert(module.clone(), module_index); - - // Add this module to the hierarchy. - if !module.is_root() { - let parent = Module::new_parent(&module); + pub fn add_squashed_module(&mut self, module: &str) { + let module = self.module_hierarchy.add_module(module).unwrap(); + self.module_hierarchy.mark_squashed(module).unwrap(); + } - let parent_index = match self.hierarchy_module_indices.get_by_left(&parent) { - Some(index) => index, - None => { - // If the parent isn't already in the graph, add it, but as an invisible module. - self.add_module(parent.clone()); - self.invisible_modules.insert(parent.clone()); - self.hierarchy_module_indices.get_by_left(&parent).unwrap() - } - }; - self.hierarchy.add_edge(*parent_index, module_index, ()); + pub fn remove_module(&mut self, module: &str) { + if let Some(module) = self.module_hierarchy.get_by_name(module) { + self.module_hierarchy.remove_module(module.token()).unwrap() + // TODO(peter) Remove imports } } - pub fn add_squashed_module(&mut self, module: Module) { - self.add_module(module.clone()); - self.squashed_modules.insert(module); + pub fn get_modules(&self) -> FxHashSet<&str> { + todo!() } - pub fn remove_module(&mut self, module: &Module) { - // Remove imports by module. - let imported_modules: Vec = self - .find_modules_directly_imported_by(module) - .iter() - .map(|m| (*m).clone()) - .collect(); - for imported_module in imported_modules { - self.remove_import(&module, &imported_module); - } - - // Remove imports of module. - let importer_modules: Vec = self - .find_modules_that_directly_import(module) - .iter() - .map(|m| (*m).clone()) - .collect(); - for importer_module in importer_modules { - self.remove_import(&importer_module, &module); - } - - // Remove module from hierarchy. - if let Some(hierarchy_index) = self.hierarchy_module_indices.get_by_left(module) { - // TODO should we check for children before removing? - // Maybe should just make invisible instead? - self.hierarchy.remove_node(*hierarchy_index); - self.hierarchy_module_indices.remove_by_left(module); - }; + pub fn count_imports(&self) -> usize { + todo!() } - pub fn get_modules(&self) -> FxHashSet<&Module> { - self.hierarchy_module_indices - .left_values() - .filter(|module| !self.invisible_modules.contains(module)) - .collect() + pub fn get_import_details(&self, importer: &str, imported: &str) -> FxHashSet { + todo!() } - pub fn count_imports(&self) -> usize { - self.imports.edge_count() + pub fn find_children(&self, module: &str) -> FxHashSet<&str> { + todo!() } - pub fn get_import_details( - &self, - importer: &Module, - imported: &Module, - ) -> FxHashSet { - let key = (importer.clone(), imported.clone()); - match self.detailed_imports_map.get(&key) { - Some(import_details) => import_details.clone(), - None => FxHashSet::default(), - } + pub fn find_descendants(&self, module: &str) -> Result, ModuleNotPresent> { + todo!() } - pub fn find_children(&self, module: &Module) -> FxHashSet<&Module> { - if self.invisible_modules.contains(module) { - return FxHashSet::default(); - } - let module_index = match self.hierarchy_module_indices.get_by_left(module) { - Some(index) => index, - // Module does not exist. - // TODO: should this return a result, to handle if module is not in graph? - None => return FxHashSet::default(), + pub fn add_import(&mut self, importer: &str, imported: &str) { + let importer = match self.module_hierarchy.get_by_name(importer) { + Some(importer) => importer.token(), + None => self.module_hierarchy.add_module(importer).unwrap(), }; - self.hierarchy - .neighbors(*module_index) - .map(|index| self.hierarchy_module_indices.get_by_right(&index).unwrap()) - .filter(|module| !self.invisible_modules.contains(module)) - .collect() - } - - pub fn find_descendants( - &self, - module: &Module, - ) -> Result, ModuleNotPresent> { - let module_index = match self.hierarchy_module_indices.get_by_left(module) { - Some(index) => index, - None => { - return Err(ModuleNotPresent { - module: module.clone(), - }) - } + let imported = match self.module_hierarchy.get_by_name(imported) { + Some(imported) => imported.token(), + None => self.module_hierarchy.add_module(imported).unwrap(), }; - Ok(Bfs::new(&self.hierarchy, *module_index) - .iter(&self.hierarchy) - .filter(|index| index != module_index) // Don't include the supplied module. - .map(|index| self.hierarchy_module_indices.get_by_right(&index).unwrap()) // This panics sometimes. - .filter(|module| !self.invisible_modules.contains(module)) - .collect()) - } - pub fn add_import(&mut self, importer: &Module, imported: &Module) { - // Don't bother doing anything if it's already in the graph. - if self.direct_import_exists(&importer, &imported, false) { + if self.module_imports.direct_import_exists(importer, imported) { return; } - self.add_module_if_not_in_hierarchy(importer); - self.add_module_if_not_in_hierarchy(imported); - - let importer_index: NodeIndex = match self.imports_module_indices.get_by_left(importer) { - Some(index) => *index, - None => { - let index = self.imports.add_node(importer.clone()); - self.imports_module_indices.insert(importer.clone(), index); - index - } - }; - let imported_index: NodeIndex = match self.imports_module_indices.get_by_left(imported) { - Some(index) => *index, - None => { - let index = self.imports.add_node(imported.clone()); - self.imports_module_indices.insert(imported.clone(), index); - index - } - }; - - self.imports.add_edge(importer_index, imported_index, ()); - // println!( - // "Added {:?} {:?} -> {:?} {:?}, edge count now {:?}", - // importer, - // importer_index, - // imported, - // imported_index, - // self.imports.edge_count() - // ); + self.module_imports + .add_import(importer, imported, None) + .unwrap(); } pub fn add_detailed_import(&mut self, import: &DetailedImport) { - let key = (import.importer.clone(), import.imported.clone()); - self.detailed_imports_map - .entry(key) - .or_insert_with(FxHashSet::default) - .insert(import.clone()); - self.add_import(&import.importer, &import.imported); - } - - pub fn remove_import(&mut self, importer: &Module, imported: &Module) { - let importer_index: NodeIndex = match self.imports_module_indices.get_by_left(importer) { - Some(index) => *index, - None => return, - }; - let imported_index: NodeIndex = match self.imports_module_indices.get_by_left(imported) { - Some(index) => *index, - None => return, + let importer = match self.module_hierarchy.get_by_name(import.importer) { + Some(importer) => importer.token(), + None => self.module_hierarchy.add_module(import.importer).unwrap(), }; - let edge_index: EdgeIndex = match self.imports.find_edge(importer_index, imported_index) { - Some(index) => index, - None => return, + let imported = match self.module_hierarchy.get_by_name(import.imported) { + Some(imported) => imported.token(), + None => self.module_hierarchy.add_module(import.imported).unwrap(), }; - self.imports.remove_edge(edge_index); - - // There might be other imports to / from the modules, so don't - // remove from the indices. (TODO: does it matter if we don't clean these up - // if there are no more imports?) - // self.imports_module_indices.remove_by_left(importer); - // self.imports_module_indices.remove_by_left(importer); + if self.module_imports.direct_import_exists(importer, imported) { + return; + } - let key = (importer.clone(), imported.clone()); + self.module_imports + .add_import( + importer, + imported, + Some(ImportDetails::new(import.line_number, import.line_contents)), + ) + .unwrap(); + } - self.detailed_imports_map.remove(&key); - self.imports.remove_edge(edge_index); + pub fn remove_import(&mut self, importer: &str, imported: &str) { + todo!() } // Note: this will panic if importer and imported are in the same package. - pub fn direct_import_exists( - &self, - importer: &Module, - imported: &Module, - as_packages: bool, - ) -> bool { - let graph_to_use: &Graph; - let mut graph_copy: Graph; - - if as_packages { - graph_copy = self.clone(); - graph_copy.squash_module(importer); - graph_copy.squash_module(imported); - graph_to_use = &graph_copy; - } else { - graph_to_use = self; - } - - // The modules may appear in the hierarchy, but have no imports, so we - // return false unless they're both in there. - let importer_index = match graph_to_use.imports_module_indices.get_by_left(importer) { - Some(importer_index) => *importer_index, - None => return false, - }; - let imported_index = match graph_to_use.imports_module_indices.get_by_left(imported) { - Some(imported_index) => *imported_index, - None => return false, - }; - - graph_to_use - .imports - .contains_edge(importer_index, imported_index) + pub fn direct_import_exists(&self, importer: &str, imported: &str, as_packages: bool) -> bool { + todo!() } - pub fn find_modules_that_directly_import(&self, imported: &Module) -> FxHashSet<&Module> { - let imported_index = match self.imports_module_indices.get_by_left(imported) { - Some(imported_index) => *imported_index, - None => return FxHashSet::default(), - }; - let importer_indices: FxHashSet = self - .imports - .neighbors_directed(imported_index, Direction::Incoming) - .collect(); - - let importers: FxHashSet<&Module> = importer_indices - .iter() - .map(|importer_index| { - self.imports_module_indices - .get_by_right(importer_index) - .unwrap() - }) - .collect(); - importers + pub fn find_modules_that_directly_import(&self, imported: &str) -> FxHashSet<&str> { + todo!() } - pub fn find_modules_directly_imported_by(&self, importer: &Module) -> FxHashSet<&Module> { - let importer_index = match self.imports_module_indices.get_by_left(importer) { - Some(importer_index) => *importer_index, - None => return FxHashSet::default(), - }; - let imported_indices: FxHashSet = self - .imports - .neighbors_directed(importer_index, Direction::Outgoing) - .collect(); - - let importeds: FxHashSet<&Module> = imported_indices - .iter() - .map(|imported_index| { - self.imports_module_indices - .get_by_right(imported_index) - .unwrap() - }) - .collect(); - importeds + pub fn find_modules_directly_imported_by(&self, importer: &str) -> FxHashSet<&str> { + todo!() } - pub fn find_upstream_modules(&self, module: &Module, as_package: bool) -> FxHashSet<&Module> { - let mut upstream_modules = FxHashSet::default(); - - let mut modules_to_check: FxHashSet<&Module> = FxHashSet::from_iter([module]); - if as_package { - let descendants = self - .find_descendants(&module) - .unwrap_or(FxHashSet::default()); - modules_to_check.extend(descendants.into_iter()); - }; - - for module_to_check in modules_to_check.iter() { - let module_index = match self.imports_module_indices.get_by_left(module_to_check) { - Some(index) => *index, - None => continue, - }; - upstream_modules.extend( - Bfs::new(&self.imports, module_index) - .iter(&self.imports) - .map(|index| self.imports_module_indices.get_by_right(&index).unwrap()) - // Exclude any modules that we are checking. - .filter(|downstream_module| !modules_to_check.contains(downstream_module)), - ); - } - - upstream_modules + pub fn find_upstream_modules(&self, module: &str, as_package: bool) -> FxHashSet<&str> { + todo!() } - pub fn find_downstream_modules(&self, module: &Module, as_package: bool) -> FxHashSet<&Module> { - let mut downstream_modules = FxHashSet::default(); - - let mut modules_to_check: FxHashSet<&Module> = FxHashSet::from_iter([module]); - if as_package { - let descendants = self - .find_descendants(&module) - .unwrap_or(FxHashSet::default()); - modules_to_check.extend(descendants.into_iter()); - }; - - for module_to_check in modules_to_check.iter() { - let module_index = match self.imports_module_indices.get_by_left(module_to_check) { - Some(index) => *index, - None => continue, - }; - - // Reverse all the edges in the graph and then do what we do in find_upstream_modules. - // Is there a way of doing this without the clone? - let mut reversed_graph = self.imports.clone(); - reversed_graph.reverse(); - - downstream_modules.extend( - Bfs::new(&reversed_graph, module_index) - .iter(&reversed_graph) - .map(|index| self.imports_module_indices.get_by_right(&index).unwrap()) - // Exclude any modules that we are checking. - .filter(|downstream_module| !modules_to_check.contains(downstream_module)), - ) - } - - downstream_modules + pub fn find_downstream_modules(&self, module: &str, as_package: bool) -> FxHashSet<&str> { + todo!() } - pub fn find_shortest_chain( - &self, - importer: &Module, - imported: &Module, - ) -> Option> { - let importer_index = match self.imports_module_indices.get_by_left(importer) { - Some(index) => *index, - None => return None, // Importer has no imports to or from. - }; - let imported_index = match self.imports_module_indices.get_by_left(imported) { - Some(index) => *index, - None => return None, // Imported has no imports to or from. - }; - let path_to_imported = match astar( - &self.imports, - importer_index, - |finish| finish == imported_index, - |_e| 1, - |_| 0, - ) { - Some(path_tuple) => path_tuple.1, - None => return None, // No chain to the imported. - }; - - let mut chain: Vec<&Module> = vec![]; - for link_index in path_to_imported { - let module = self - .imports_module_indices - .get_by_right(&link_index) - .unwrap(); - chain.push(module); - } - Some(chain) + pub fn find_shortest_chain(&self, importer: &str, imported: &str) -> Option> { + todo!() } pub fn find_shortest_chains( &self, - importer: &Module, - imported: &Module, + importer: &str, + imported: &str, as_packages: bool, - ) -> Result>, String> { - let mut chains = FxHashSet::default(); - let mut temp_graph = self.clone(); - - let mut downstream_modules: FxHashSet = FxHashSet::from_iter([importer.clone()]); - let mut upstream_modules: FxHashSet = FxHashSet::from_iter([imported.clone()]); - - // TODO don't do this if module is squashed? - if as_packages { - for descendant in self.find_descendants(importer).unwrap() { - downstream_modules.insert(descendant.clone()); - } - for descendant in self.find_descendants(imported).unwrap() { - upstream_modules.insert(descendant.clone()); - } - if upstream_modules - .intersection(&downstream_modules) - .next() - .is_some() - { - return Err("Modules have shared descendants.".to_string()); - } - } - - // Remove imports within the packages. - let mut imports_to_remove: Vec<(Module, Module)> = vec![]; - for upstream_module in &upstream_modules { - for imported_module in temp_graph.find_modules_directly_imported_by(&upstream_module) { - if upstream_modules.contains(&imported_module) { - imports_to_remove.push((upstream_module.clone(), imported_module.clone())); - } - } - } - for downstream_module in &downstream_modules { - for imported_module in temp_graph.find_modules_directly_imported_by(&downstream_module) - { - if downstream_modules.contains(&imported_module) { - imports_to_remove.push((downstream_module.clone(), imported_module.clone())); - } - } - } - for (importer_to_remove, imported_to_remove) in imports_to_remove { - temp_graph.remove_import(&importer_to_remove, &imported_to_remove); - } - - // Keep track of imports into/out of upstream/downstream packages, and remove them. - let mut map_of_imports: FxHashMap> = - FxHashMap::default(); - for module in upstream_modules.union(&downstream_modules) { - let mut imports_to_or_from_module = FxHashSet::default(); - for imported_module in temp_graph.find_modules_directly_imported_by(&module) { - imports_to_or_from_module.insert((module.clone(), imported_module.clone())); - } - for importer_module in temp_graph.find_modules_that_directly_import(&module) { - imports_to_or_from_module.insert((importer_module.clone(), module.clone())); - } - map_of_imports.insert(module.clone(), imports_to_or_from_module); - } - for imports in map_of_imports.values() { - for (importer_to_remove, imported_to_remove) in imports { - temp_graph.remove_import(&importer_to_remove, &imported_to_remove); - } - } - - for importer_module in &downstream_modules { - // Reveal imports to/from importer module. - for (importer_to_add, imported_to_add) in &map_of_imports[&importer_module] { - temp_graph.add_import(&importer_to_add, &imported_to_add); - } - for imported_module in &upstream_modules { - // Reveal imports to/from imported module. - for (importer_to_add, imported_to_add) in &map_of_imports[&imported_module] { - temp_graph.add_import(&importer_to_add, &imported_to_add); - } - if let Some(chain) = - temp_graph.find_shortest_chain(importer_module, imported_module) - { - chains.insert(chain.iter().cloned().map(|module| module.clone()).collect()); - } - // Remove imports relating to imported module again. - for (importer_to_remove, imported_to_remove) in &map_of_imports[&imported_module] { - temp_graph.remove_import(&importer_to_remove, &imported_to_remove); - } - } - // Remove imports relating to importer module again. - for (importer_to_remove, imported_to_remove) in &map_of_imports[&importer_module] { - temp_graph.remove_import(&importer_to_remove, &imported_to_remove); - } - } - Ok(chains) - } - - pub fn chain_exists(&self, importer: &Module, imported: &Module, as_packages: bool) -> bool { - // TODO should this return a Result, so we can handle the situation the importer / imported - // having shared descendants when as_packages=true? - let mut temp_graph; - let graph = match as_packages { - true => { - temp_graph = self.clone(); - temp_graph.squash_module(importer); - temp_graph.squash_module(imported); - &temp_graph - } - false => self, - }; - graph.find_shortest_chain(importer, imported).is_some() - } - - pub fn find_illegal_dependencies_for_layers( - &self, - levels: Vec, - containers: FxHashSet, - ) -> Result, NoSuchContainer> { - // Check that containers exist. - let modules = self.get_modules(); - for container in containers.iter() { - let container_module = Module::new(container.clone()); - if !modules.contains(&container_module) { - return Err(NoSuchContainer { - container: container.clone(), - }); - } - } - - let all_layers: Vec = levels - .iter() - .flat_map(|level| level.layers.iter()) - .map(|module_name| module_name.to_string()) - .collect(); - - let mut dependencies: Vec = self - ._generate_module_permutations(&levels, &containers) - //.into_iter() - .into_par_iter() - .filter_map(|(higher_layer_package, lower_layer_package, container)| { - // TODO: it's inefficient to do this for sibling layers, as we don't need - // to clone and trim the graph for identical pairs. - info!( - "Searching for import chains from {} to {}...", - lower_layer_package, higher_layer_package - ); - let now = Instant::now(); - let dependency_or_none = self._search_for_package_dependency( - &higher_layer_package, - &lower_layer_package, - &all_layers, - &container, - ); - _log_illegal_route_count(&dependency_or_none, now.elapsed().as_secs()); - dependency_or_none - }) - .collect(); - - dependencies.sort(); - - Ok(dependencies) - } - - // Return every permutation of modules that exist in the graph - /// in which the second should not import the first. - /// The third item in the tuple is the relevant container, if used. - fn _generate_module_permutations( - &self, - levels: &Vec, - containers: &FxHashSet, - ) -> Vec<(Module, Module, Option)> { - let mut permutations: Vec<(Module, Module, Option)> = vec![]; - - let quasi_containers: Vec> = if containers.is_empty() { - vec![None] - } else { - containers - .iter() - .map(|i| Some(Module::new(i.to_string()))) - .collect() - }; - let all_modules = self.get_modules(); - - for quasi_container in quasi_containers { - for (index, higher_level) in levels.iter().enumerate() { - for higher_layer in &higher_level.layers { - let higher_layer_module = _module_from_layer(&higher_layer, &quasi_container); - if !all_modules.contains(&higher_layer_module) { - continue; - } - - // 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 = vec![]; - - // Independence - if higher_level.independent { - for potential_sibling_layer in &higher_level.layers { - let sibling_module = - _module_from_layer(&potential_sibling_layer, &quasi_container); - if sibling_module != higher_layer_module - && all_modules.contains(&sibling_module) - { - layers_forbidden_to_import_higher_layer.push(sibling_module); - } - } - } - - for lower_level in &levels[index + 1..] { - for lower_layer in &lower_level.layers { - let lower_layer_module = - _module_from_layer(&lower_layer, &quasi_container); - if all_modules.contains(&lower_layer_module) { - layers_forbidden_to_import_higher_layer.push(lower_layer_module); - } - } - } - - // Add to permutations. - for forbidden in layers_forbidden_to_import_higher_layer { - permutations.push(( - higher_layer_module.clone(), - forbidden.clone(), - quasi_container.clone(), - )); - } - } - } - } - - permutations - } - - fn _search_for_package_dependency( - &self, - higher_layer_package: &Module, - lower_layer_package: &Module, - layers: &Vec, - container: &Option, - ) -> Option { - let mut temp_graph = self.clone(); - - // Remove other layers. - let mut modules_to_remove: Vec = vec![]; - for layer in layers { - let layer_module = _module_from_layer(&layer, &container); - if layer_module != *higher_layer_package && layer_module != *lower_layer_package { - // Remove this subpackage. - match temp_graph.find_descendants(&layer_module) { - Ok(descendants) => { - for descendant in descendants { - modules_to_remove.push(descendant.clone()) - } - } - Err(_) => (), // ModuleNotPresent. - } - modules_to_remove.push(layer_module.clone()); - } - } - for module_to_remove in modules_to_remove.clone() { - temp_graph.remove_module(&module_to_remove); - } - - let mut routes: Vec = vec![]; - - // Direct routes. - // TODO: do we need to pop the imports? - // The indirect routes should cope without removing them? - let direct_links = - temp_graph._pop_direct_imports(lower_layer_package, higher_layer_package); - for (importer, imported) in direct_links { - routes.push(Route { - heads: vec![importer], - middle: vec![], - tails: vec![imported], - }); - } - - // Indirect routes. - for indirect_route in - temp_graph._find_indirect_routes(lower_layer_package, higher_layer_package) - { - routes.push(indirect_route); - } - - if routes.is_empty() { - None - } else { - Some(PackageDependency { - importer: lower_layer_package.clone(), - imported: higher_layer_package.clone(), - routes, - }) - } - } - - fn _find_indirect_routes( - &self, - importer_package: &Module, - imported_package: &Module, - ) -> Vec { - let mut routes = vec![]; - - let mut temp_graph = self.clone(); - temp_graph.squash_module(importer_package); - temp_graph.squash_module(imported_package); - - // Find middles. - let mut middles: Vec> = vec![]; - for chain in temp_graph._pop_shortest_chains(importer_package, imported_package) { - // Remove first and last element. - let mut middle: Vec = vec![]; - let chain_length = chain.len(); - for (index, module) in chain.iter().enumerate() { - if index != 0 && index != chain_length - 1 { - middle.push(module.clone()); - } - } - middles.push(middle); - } - - // Set up importer/imported package contents. - let mut importer_modules: FxHashSet<&Module> = FxHashSet::from_iter([importer_package]); - importer_modules.extend(self.find_descendants(&importer_package).unwrap()); - let mut imported_modules: FxHashSet<&Module> = FxHashSet::from_iter([imported_package]); - imported_modules.extend(self.find_descendants(&imported_package).unwrap()); - - // Build routes from middles. - for middle in middles { - // Construct heads. - let mut heads: Vec = vec![]; - let first_imported_module = &middle[0]; - for candidate_head in self.find_modules_that_directly_import(&first_imported_module) { - if importer_modules.contains(candidate_head) { - heads.push(candidate_head.clone()); - } - } - - // Construct tails. - let mut tails: Vec = vec![]; - let last_importer_module = &middle[middle.len() - 1]; - for candidate_tail in self.find_modules_directly_imported_by(&last_importer_module) { - if imported_modules.contains(candidate_tail) { - tails.push(candidate_tail.clone()); - } - } - - routes.push(Route { - heads, - middle, - tails, - }) - } - - routes - } - - fn _pop_shortest_chains(&mut self, importer: &Module, imported: &Module) -> Vec> { - let mut chains = vec![]; - - loop { - // TODO - defend against infinite loops somehow. - - let found_chain: Vec; - { - let chain = self.find_shortest_chain(importer, imported); - - if chain.is_none() { - break; - } - - found_chain = chain.unwrap().into_iter().cloned().collect(); - } - // Remove chain. - for i in 0..found_chain.len() - 1 { - self.remove_import(&found_chain[i], &found_chain[i + 1]); - } - chains.push(found_chain); - } - chains - } - - /// Remove the direct imports, returning them as (importer, imported) tuples. - fn _pop_direct_imports( - &mut self, - lower_layer_module: &Module, - higher_layer_module: &Module, - ) -> FxHashSet<(Module, Module)> { - let mut imports = FxHashSet::default(); - - let mut lower_layer_modules = FxHashSet::from_iter([lower_layer_module.clone()]); - for descendant in self - .find_descendants(lower_layer_module) - .unwrap() - .iter() - .cloned() - { - lower_layer_modules.insert(descendant.clone()); - } - - let mut higher_layer_modules = FxHashSet::from_iter([higher_layer_module.clone()]); - for descendant in self - .find_descendants(higher_layer_module) - .unwrap() - .iter() - .cloned() - { - higher_layer_modules.insert(descendant.clone()); - } - - for lower_layer_module in lower_layer_modules { - for imported_module in self.find_modules_directly_imported_by(&lower_layer_module) { - if higher_layer_modules.contains(imported_module) { - imports.insert((lower_layer_module.clone(), imported_module.clone())); - } - } - } - - // Remove imports. - for (importer, imported) in &imports { - self.remove_import(&importer, &imported) - } - - imports - } - - pub fn squash_module(&mut self, module: &Module) { - // Get descendants and their imports. - let descendants: Vec = self - .find_descendants(module) - .unwrap() - .into_iter() - .cloned() - .collect(); - let modules_imported_by_descendants: Vec = descendants - .iter() - .flat_map(|descendant| { - self.find_modules_directly_imported_by(descendant) - .into_iter() - .cloned() - }) - .collect(); - let modules_that_import_descendants: Vec = descendants - .iter() - .flat_map(|descendant| { - self.find_modules_that_directly_import(descendant) - .into_iter() - .cloned() - }) - .collect(); - - // Remove any descendants. - for descendant in descendants { - self.remove_module(&descendant); - } - - // Add descendants and imports to parent module. - for imported in modules_imported_by_descendants { - self.add_import(module, &imported); - } - - for importer in modules_that_import_descendants { - self.add_import(&importer, module); - } - - self.squashed_modules.insert(module.clone()); - } - - pub fn is_module_squashed(&self, module: &Module) -> bool { - self.squashed_modules.contains(module) - } - - /// Return the squashed module that is the nearest ancestor of the supplied module, - /// if such an ancestor exists. - pub fn find_ancestor_squashed_module(&self, module: &Module) -> Option { - if module.is_root() { - return None; - } - let parent = Module::new_parent(&module); - if self.is_module_squashed(&parent) { - return Some(parent); - } - self.find_ancestor_squashed_module(&parent) - } - - fn add_module_if_not_in_hierarchy(&mut self, module: &Module) { - if self.hierarchy_module_indices.get_by_left(module).is_none() { - self.add_module(module.clone()); - }; - if self.invisible_modules.contains(&module) { - self.invisible_modules.remove(&module); - }; - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn modules_when_empty() { - let graph = Graph::default(); - - assert_eq!(graph.get_modules(), FxHashSet::default()); - } - - #[test] - fn module_is_value_object() { - assert_eq!( - Module::new("mypackage".to_string()), - Module::new("mypackage".to_string()) - ); - } - - #[test] - fn add_module() { - let mypackage = Module::new("mypackage".to_string()); - let mut graph = Graph::default(); - graph.add_module(mypackage.clone()); - - let result = graph.get_modules(); - - assert_eq!(result, FxHashSet::from_iter([&mypackage])); - } - - #[test] - fn add_module_doesnt_add_parent() { - let mypackage = Module::new("mypackage.foo".to_string()); - let mut graph = Graph::default(); - graph.add_module(mypackage.clone()); - - let result = graph.get_modules(); - - assert_eq!(result, FxHashSet::from_iter([&mypackage])); - } - - #[test] - fn add_modules() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - - let result = graph.get_modules(); - - assert_eq!(result, FxHashSet::from_iter([&mypackage, &mypackage_foo])); - assert_eq!( - graph.pretty_str(), - " -hierarchy: - mypackage -> mypackage.foo -imports: - -" - .trim_start() - ); - } - - #[test] - fn remove_nonexistent_module() { - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mut graph = Graph::default(); - // Add mypackage but not mypackage.foo. - graph.add_module(mypackage.clone()); - - graph.remove_module(&mypackage_foo); - - let result = graph.get_modules(); - assert_eq!(result, FxHashSet::from_iter([&mypackage])); - } - - #[test] - fn remove_existing_module_without_imports() { - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_foo_alpha = Module::new("mypackage.foo.alpha".to_string()); - - let mut graph = Graph::default(); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_foo_alpha.clone()); - - graph.remove_module(&mypackage_foo); - - let result = graph.get_modules(); - assert_eq!( - result, - FxHashSet::from_iter([ - &mypackage, - &mypackage_foo_alpha, // To be consistent with previous versions of Grimp. - ]) - ); - } - - #[test] - fn remove_existing_module_with_imports() { - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_foo_alpha = Module::new("mypackage.foo.alpha".to_string()); - let importer = Module::new("importer".to_string()); - let imported = Module::new("importer".to_string()); - let mut graph = Graph::default(); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_foo_alpha.clone()); - graph.add_import(&importer, &mypackage_foo); - graph.add_import(&mypackage_foo, &imported); - - graph.remove_module(&mypackage_foo); - - let result = graph.get_modules(); - assert_eq!( - result, - FxHashSet::from_iter([&mypackage, &mypackage_foo_alpha, &importer, &imported]) - ); - assert_eq!( - graph.direct_import_exists(&importer, &mypackage_foo, false), - false - ); - assert_eq!( - graph.direct_import_exists(&mypackage_foo, &imported, false), - false - ); - } - - #[test] - fn remove_importer_module_removes_import_details() { - let importer = Module::new("importer".to_string()); - let imported = Module::new("importer".to_string()); - let mut graph = Graph::default(); - graph.add_detailed_import(&DetailedImport { - importer: importer.clone(), - imported: imported.clone(), - line_number: 99, - line_contents: "-".to_string(), - }); - - graph.remove_module(&importer); - - assert_eq!( - graph.get_import_details(&importer, &imported), - FxHashSet::default() - ); - } - - #[test] - fn remove_imported_module_removes_import_details() { - let importer = Module::new("importer".to_string()); - let imported = Module::new("importer".to_string()); - let mut graph = Graph::default(); - graph.add_detailed_import(&DetailedImport { - importer: importer.clone(), - imported: imported.clone(), - line_number: 99, - line_contents: "-".to_string(), - }); - - graph.remove_module(&imported); - - assert_eq!( - graph.get_import_details(&importer, &imported), - FxHashSet::default() - ); + ) -> Result>, String> { + todo!() } - #[test] - fn remove_import_that_exists() { - let importer = Module::new("importer".to_string()); - let imported = Module::new("importer".to_string()); - let mut graph = Graph::default(); - graph.add_import(&importer, &imported); - - graph.remove_import(&importer, &imported); - - // The import has gone... - assert_eq!( - graph.direct_import_exists(&importer, &imported, false), - false - ); - // ...but the modules are still there. - assert_eq!( - graph.get_modules(), - FxHashSet::from_iter([&importer, &imported]) - ); - } - - #[test] - fn remove_import_does_nothing_if_import_doesnt_exist() { - let importer = Module::new("importer".to_string()); - let imported = Module::new("importer".to_string()); - let mut graph = Graph::default(); - graph.add_module(importer.clone()); - graph.add_module(imported.clone()); - - graph.remove_import(&importer, &imported); - - // The modules are still there. - assert_eq!( - graph.get_modules(), - FxHashSet::from_iter([&importer, &imported]) - ); - } - - #[test] - fn remove_import_does_nothing_if_modules_dont_exist() { - let importer = Module::new("importer".to_string()); - let imported = Module::new("importer".to_string()); - let mut graph = Graph::default(); - - graph.remove_import(&importer, &imported); - } - - #[test] - fn remove_import_doesnt_affect_other_imports_from_same_modules() { - let blue = Module::new("blue".to_string()); - let green = Module::new("green".to_string()); - let yellow = Module::new("yellow".to_string()); - let red = Module::new("red".to_string()); - let mut graph = Graph::default(); - graph.add_import(&blue, &green); - graph.add_import(&blue, &yellow); - graph.add_import(&red, &blue); - - graph.remove_import(&blue, &green); - - // The other imports are still there. - assert_eq!(graph.direct_import_exists(&blue, &yellow, false), true); - assert_eq!(graph.direct_import_exists(&red, &blue, false), true); - } - - #[test] - #[should_panic(expected = "rootpackage is a root level package")] - fn new_parent_root_module() { - let root = Module::new("rootpackage".to_string()); - - Module::new_parent(&root); + pub fn chain_exists(&self, importer: &str, imported: &str, as_packages: bool) -> bool { + todo!() } - #[test] - fn is_root_true() { - let root = Module::new("rootpackage".to_string()); + // pub fn find_illegal_dependencies_for_layers( + // &self, + // levels: Vec, + // containers: FxHashSet, + // ) -> Result, NoSuchContainer> { + // todo!() + // } - assert!(root.is_root()); + pub fn squash_module(&mut self, module: &str) { + todo!() } - #[test] - fn is_descendant_of_true_for_child() { - let foo = Module::new("mypackage.foo".to_string()); - let foo_bar = Module::new("mypackage.foo.bar".to_string()); - - assert!(foo_bar.is_descendant_of(&foo)); - } - - #[test] - fn is_descendant_of_false_for_parent() { - let foo = Module::new("mypackage.foo".to_string()); - let foo_bar = Module::new("mypackage.foo.bar".to_string()); - - assert_eq!(foo.is_descendant_of(&foo_bar), false); - } - - #[test] - fn is_descendant_of_true_for_grandchild() { - let foo = Module::new("mypackage.foo".to_string()); - let foo_bar_baz = Module::new("mypackage.foo.bar.baz".to_string()); - - assert!(foo_bar_baz.is_descendant_of(&foo)); - } - - #[test] - fn is_descendant_of_false_for_grandparent() { - let foo = Module::new("mypackage.foo".to_string()); - let foo_bar_baz = Module::new("mypackage.foo.bar.baz".to_string()); - - assert_eq!(foo.is_descendant_of(&foo_bar_baz), false); - } - - #[test] - fn is_root_false() { - let non_root = Module::new("rootpackage.blue".to_string()); - - assert_eq!(non_root.is_root(), false); - } - - #[test] - fn find_children_no_results() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - - assert_eq!(graph.find_children(&mypackage_foo), FxHashSet::default()); - } - - #[test] - fn find_children_one_result() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - - assert_eq!( - graph.find_children(&mypackage), - FxHashSet::from_iter([&mypackage_foo, &mypackage_bar]) - ); - } - - #[test] - fn find_children_multiple_results() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - - assert_eq!( - graph.find_children(&mypackage), - FxHashSet::from_iter([&mypackage_foo, &mypackage_bar]) - ); - } - - #[test] - fn find_children_returns_empty_set_with_nonexistent_module() { - let mut graph = Graph::default(); - // Note: mypackage is not in the graph. - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - - assert_eq!( - graph.find_children(&Module::new("mypackage".to_string())), - FxHashSet::default() - ); - } - - #[test] - fn find_descendants_no_results() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - let mypackage_foo_alpha = Module::new("mypackage.foo.alpha".to_string()); - let mypackage_foo_alpha_blue = Module::new("mypackage.foo.alpha.blue".to_string()); - let mypackage_foo_alpha_green = Module::new("mypackage.foo.alpha.green".to_string()); - let mypackage_foo_beta = Module::new("mypackage.foo.beta".to_string()); - - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_module(mypackage_foo_alpha.clone()); - graph.add_module(mypackage_foo_alpha_blue.clone()); - graph.add_module(mypackage_foo_alpha_green.clone()); - graph.add_module(mypackage_foo_beta.clone()); - - assert_eq!( - graph.find_descendants(&mypackage_bar), - Ok(FxHashSet::default()) - ); - } - - #[test] - fn find_descendants_module_not_in_graph() { - let mut graph = Graph::default(); - let blue = Module::new("blue".to_string()); - let green = Module::new("green".to_string()); - graph.add_module(blue.clone()); - - assert_eq!( - graph.find_descendants(&green), - Err(ModuleNotPresent { - module: green.clone() - }) - ); - } - - #[test] - fn find_descendants_multiple_results() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - let mypackage_foo_alpha = Module::new("mypackage.foo.alpha".to_string()); - let mypackage_foo_alpha_blue = Module::new("mypackage.foo.alpha.blue".to_string()); - let mypackage_foo_alpha_green = Module::new("mypackage.foo.alpha.green".to_string()); - let mypackage_foo_beta = Module::new("mypackage.foo.beta".to_string()); - - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_module(mypackage_foo_alpha.clone()); - graph.add_module(mypackage_foo_alpha_blue.clone()); - graph.add_module(mypackage_foo_alpha_green.clone()); - graph.add_module(mypackage_foo_beta.clone()); - - assert_eq!( - graph.find_descendants(&mypackage_foo), - Ok(FxHashSet::from_iter([ - &mypackage_foo_alpha, - &mypackage_foo_alpha_blue, - &mypackage_foo_alpha_green, - &mypackage_foo_beta - ])) - ); - } - - #[test] - fn find_descendants_with_gap() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - // mypackage.foo.blue is not added. - let mypackage_foo_blue_alpha = Module::new("mypackage.foo.blue.alpha".to_string()); - let mypackage_foo_blue_alpha_one = Module::new("mypackage.foo.blue.alpha.one".to_string()); - let mypackage_foo_blue_alpha_two = Module::new("mypackage.foo.blue.alpha.two".to_string()); - let mypackage_foo_blue_beta_three = - Module::new("mypackage.foo.blue.beta.three".to_string()); - let mypackage_bar_green_alpha = Module::new("mypackage.bar.green.alpha".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_foo_blue_alpha.clone()); - graph.add_module(mypackage_foo_blue_alpha_one.clone()); - graph.add_module(mypackage_foo_blue_alpha_two.clone()); - graph.add_module(mypackage_foo_blue_beta_three.clone()); - graph.add_module(mypackage_bar_green_alpha.clone()); - - assert_eq!( - graph.find_descendants(&mypackage_foo), - // mypackage.foo.blue is not included. - Ok(FxHashSet::from_iter([ - &mypackage_foo_blue_alpha, - &mypackage_foo_blue_alpha_one, - &mypackage_foo_blue_alpha_two, - &mypackage_foo_blue_beta_three, - ])) - ); - } - - #[test] - fn find_descendants_added_in_different_order() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_foo_blue_alpha = Module::new("mypackage.foo.blue.alpha".to_string()); - let mypackage_foo_blue_alpha_one = Module::new("mypackage.foo.blue.alpha.one".to_string()); - let mypackage_foo_blue_alpha_two = Module::new("mypackage.foo.blue.alpha.two".to_string()); - let mypackage_foo_blue_beta_three = - Module::new("mypackage.foo.blue.beta.three".to_string()); - let mypackage_bar_green_alpha = Module::new("mypackage.bar.green.alpha".to_string()); - let mypackage_foo_blue = Module::new("mypackage.foo.blue".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_foo_blue_alpha.clone()); - graph.add_module(mypackage_foo_blue_alpha_one.clone()); - graph.add_module(mypackage_foo_blue_alpha_two.clone()); - graph.add_module(mypackage_foo_blue_beta_three.clone()); - graph.add_module(mypackage_bar_green_alpha.clone()); - // Add the middle one at the end. - graph.add_module(mypackage_foo_blue.clone()); - - assert_eq!( - graph.find_descendants(&mypackage_foo), - Ok(FxHashSet::from_iter([ - &mypackage_foo_blue, // Should be included. - &mypackage_foo_blue_alpha, - &mypackage_foo_blue_alpha_one, - &mypackage_foo_blue_alpha_two, - &mypackage_foo_blue_beta_three, - ])) - ); - } - - #[test] - fn direct_import_exists_returns_true() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_import(&mypackage_foo, &mypackage_bar); - - assert!(graph.direct_import_exists(&mypackage_foo, &mypackage_bar, false)); - } - - #[test] - fn add_detailed_import_adds_import() { - let mut graph = Graph::default(); - let blue = Module::new("blue".to_string()); - let green = Module::new("green".to_string()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - let import = DetailedImport { - importer: blue.clone(), - imported: green.clone(), - line_number: 11, - line_contents: "-".to_string(), - }; - - graph.add_detailed_import(&import); - - assert_eq!(graph.direct_import_exists(&blue, &green, false), true); - } - - #[test] - fn direct_import_exists_returns_false() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_import(&mypackage_foo, &mypackage_bar); - - assert!(!graph.direct_import_exists(&mypackage_bar, &mypackage_foo, false)); - } - - #[test] - fn direct_import_exists_returns_false_root_to_child() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - let mypackage_foo_alpha = Module::new("mypackage.foo.alpha".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_module(mypackage_foo_alpha.clone()); - graph.add_import(&mypackage_bar, &mypackage_foo_alpha); - - assert_eq!( - graph.pretty_str(), - " -hierarchy: - mypackage -> mypackage.bar - mypackage -> mypackage.foo - mypackage.foo -> mypackage.foo.alpha -imports: - mypackage.bar -> mypackage.foo.alpha -" - .trim_start() - ); - assert!(!graph.direct_import_exists(&mypackage_bar, &mypackage_foo, false)); - } - - #[test] - fn add_import_with_non_existent_importer_adds_that_module() { - let mut graph = Graph::default(); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - graph.add_module(mypackage_bar.clone()); - - graph.add_import(&mypackage_foo, &mypackage_bar); - - assert_eq!( - graph.get_modules(), - FxHashSet::from_iter([&mypackage_bar, &mypackage_foo]) - ); - assert!(graph.direct_import_exists(&mypackage_foo, &mypackage_bar, false)); - assert_eq!( - graph.pretty_str(), - " -hierarchy: - (mypackage) -> mypackage.bar - (mypackage) -> mypackage.foo -imports: - mypackage.foo -> mypackage.bar -" - .trim_start() - ); - } - - #[test] - fn add_import_with_non_existent_imported_adds_that_module() { - let mut graph = Graph::default(); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - graph.add_module(mypackage_foo.clone()); - - graph.add_import(&mypackage_foo, &mypackage_bar); - - assert_eq!( - graph.get_modules(), - FxHashSet::from_iter([&mypackage_bar, &mypackage_foo]) - ); - assert!(graph.direct_import_exists(&mypackage_foo, &mypackage_bar, false)); - assert_eq!( - graph.pretty_str(), - " -hierarchy: - (mypackage) -> mypackage.bar - (mypackage) -> mypackage.foo -imports: - mypackage.foo -> mypackage.bar -" - .trim_start() - ); - } - - #[test] - fn direct_import_exists_with_as_packages_returns_false() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - let mypackage_foo_alpha = Module::new("mypackage.foo.alpha".to_string()); - let mypackage_foo_alpha_blue = Module::new("mypackage.foo.alpha.blue".to_string()); - let mypackage_foo_alpha_green = Module::new("mypackage.foo.alpha.green".to_string()); - let mypackage_foo_beta = Module::new("mypackage.foo.beta".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_module(mypackage_foo_alpha.clone()); - graph.add_module(mypackage_foo_alpha_blue.clone()); - graph.add_module(mypackage_foo_alpha_green.clone()); - graph.add_module(mypackage_foo_beta.clone()); - // Add an import in the other direction. - graph.add_import(&mypackage_bar, &mypackage_foo); - - assert!(!graph.direct_import_exists(&mypackage_foo, &mypackage_bar, true)); - } - - #[test] - fn direct_import_exists_with_as_packages_returns_true_between_roots() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - let mypackage_foo_alpha = Module::new("mypackage.foo.alpha".to_string()); - let mypackage_foo_alpha_blue = Module::new("mypackage.foo.alpha.blue".to_string()); - let mypackage_foo_alpha_green = Module::new("mypackage.foo.alpha.green".to_string()); - let mypackage_foo_beta = Module::new("mypackage.foo.beta".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_module(mypackage_foo_alpha.clone()); - graph.add_module(mypackage_foo_alpha_blue.clone()); - graph.add_module(mypackage_foo_alpha_green.clone()); - graph.add_module(mypackage_foo_beta.clone()); - graph.add_import(&mypackage_foo, &mypackage_bar); - - assert!(graph.direct_import_exists(&mypackage_foo, &mypackage_bar, true)); - } - - #[test] - fn direct_import_exists_with_as_packages_returns_true_root_to_child() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - let mypackage_foo_alpha = Module::new("mypackage.foo.alpha".to_string()); - let mypackage_foo_alpha_blue = Module::new("mypackage.foo.alpha.blue".to_string()); - let mypackage_foo_alpha_green = Module::new("mypackage.foo.alpha.green".to_string()); - let mypackage_foo_beta = Module::new("mypackage.foo.beta".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_module(mypackage_foo_alpha.clone()); - graph.add_module(mypackage_foo_alpha_blue.clone()); - graph.add_module(mypackage_foo_alpha_green.clone()); - graph.add_module(mypackage_foo_beta.clone()); - graph.add_import(&mypackage_bar, &mypackage_foo_alpha); - - assert!(graph.direct_import_exists(&mypackage_bar, &mypackage_foo, true)); - } - - #[test] - fn direct_import_exists_with_as_packages_returns_true_child_to_root() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - let mypackage_foo_alpha = Module::new("mypackage.foo.alpha".to_string()); - let mypackage_foo_alpha_blue = Module::new("mypackage.foo.alpha.blue".to_string()); - let mypackage_foo_alpha_green = Module::new("mypackage.foo.alpha.green".to_string()); - let mypackage_foo_beta = Module::new("mypackage.foo.beta".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_module(mypackage_foo_alpha.clone()); - graph.add_module(mypackage_foo_alpha_blue.clone()); - graph.add_module(mypackage_foo_alpha_green.clone()); - graph.add_module(mypackage_foo_beta.clone()); - graph.add_import(&mypackage_foo_alpha, &mypackage_bar); - - assert!(graph.direct_import_exists(&mypackage_foo, &mypackage_bar, true)); - } - - #[test] - #[should_panic] - fn direct_import_exists_within_package_panics() { - let mut graph = Graph::default(); - let ancestor = Module::new("mypackage.foo".to_string()); - let descendant = Module::new("mypackage.foo.blue.alpha".to_string()); - graph.add_import(&ancestor, &descendant); - - graph.direct_import_exists(&ancestor, &descendant, true); - } - - #[test] - fn find_modules_that_directly_import() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - let mypackage_foo_alpha = Module::new("mypackage.foo.alpha".to_string()); - let mypackage_foo_alpha_blue = Module::new("mypackage.foo.alpha.blue".to_string()); - let mypackage_foo_alpha_green = Module::new("mypackage.foo.alpha.green".to_string()); - let mypackage_foo_beta = Module::new("mypackage.foo.beta".to_string()); - let anotherpackage = Module::new("anotherpackage".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_module(mypackage_foo_alpha.clone()); - graph.add_module(mypackage_foo_alpha_blue.clone()); - graph.add_module(mypackage_foo_alpha_green.clone()); - graph.add_module(mypackage_foo_beta.clone()); - graph.add_import(&mypackage_foo_alpha, &mypackage_bar); - graph.add_import(&anotherpackage, &mypackage_bar); - graph.add_import(&mypackage_bar, &mypackage_foo_alpha_green); - - let result = graph.find_modules_that_directly_import(&mypackage_bar); - - assert_eq!( - result, - FxHashSet::from_iter([&mypackage_foo_alpha, &anotherpackage]) - ) - } - - #[test] - fn find_modules_that_directly_import_after_removal() { - let mut graph = Graph::default(); - let blue = Module::new("blue".to_string()); - let green = Module::new("green".to_string()); - let yellow = Module::new("yellow".to_string()); - graph.add_import(&green, &blue); - graph.add_import(&yellow, &blue); - - graph.remove_import(&green, &blue); - let result = graph.find_modules_that_directly_import(&blue); - - assert_eq!(result, FxHashSet::from_iter([&yellow])) - } - - #[test] - fn find_modules_directly_imported_by() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - let mypackage_foo_alpha = Module::new("mypackage.foo.alpha".to_string()); - let mypackage_foo_alpha_blue = Module::new("mypackage.foo.alpha.blue".to_string()); - let mypackage_foo_alpha_green = Module::new("mypackage.foo.alpha.green".to_string()); - let mypackage_foo_beta = Module::new("mypackage.foo.beta".to_string()); - let anotherpackage = Module::new("anotherpackage".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_module(mypackage_foo_alpha.clone()); - graph.add_module(mypackage_foo_alpha_blue.clone()); - graph.add_module(mypackage_foo_alpha_green.clone()); - graph.add_module(mypackage_foo_beta.clone()); - graph.add_import(&mypackage_bar, &mypackage_foo_alpha); - graph.add_import(&mypackage_bar, &anotherpackage); - graph.add_import(&mypackage_foo_alpha_green, &mypackage_bar); - - let result = graph.find_modules_directly_imported_by(&mypackage_bar); - - assert_eq!( - result, - FxHashSet::from_iter([&mypackage_foo_alpha, &anotherpackage]) - ) - } - - #[test] - fn find_modules_directly_imported_by_after_removal() { - let mut graph = Graph::default(); - let blue = Module::new("blue".to_string()); - let green = Module::new("green".to_string()); - let yellow = Module::new("yellow".to_string()); - graph.add_import(&blue, &green); - graph.add_import(&blue, &yellow); - - graph.remove_import(&blue, &green); - let result = graph.find_modules_directly_imported_by(&blue); - - assert_eq!(result, FxHashSet::from_iter([&yellow])) - } - - #[test] - fn squash_module_descendants() { - let mut graph = Graph::default(); - // Module we're going to squash. - let mypackage = Module::new("mypackage".to_string()); - let mypackage_blue = Module::new("mypackage.blue".to_string()); - let mypackage_blue_alpha = Module::new("mypackage.blue.alpha".to_string()); - let mypackage_blue_alpha_foo = Module::new("mypackage.blue.alpha.foo".to_string()); - let mypackage_blue_beta = Module::new("mypackage.blue.beta".to_string()); - // Other modules. - let mypackage_green = Module::new("mypackage.green".to_string()); - let mypackage_red = Module::new("mypackage.red".to_string()); - let mypackage_orange = Module::new("mypackage.orange".to_string()); - let mypackage_yellow = Module::new("mypackage.yellow".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_blue.clone()); - // Module's descendants importing other modules. - graph.add_import(&mypackage_blue_alpha, &mypackage_green); - graph.add_import(&mypackage_blue_alpha, &mypackage_red); - graph.add_import(&mypackage_blue_alpha_foo, &mypackage_yellow); - graph.add_import(&mypackage_blue_beta, &mypackage_orange); - // Other modules importing squashed module's descendants. - graph.add_import(&mypackage_red, &mypackage_blue_alpha); - graph.add_import(&mypackage_yellow, &mypackage_blue_alpha); - graph.add_import(&mypackage_orange, &mypackage_blue_alpha_foo); - graph.add_import(&mypackage_green, &mypackage_blue_beta); - // Unrelated imports. - graph.add_import(&mypackage_green, &mypackage_orange); - assert_eq!( - graph.pretty_str(), - " -hierarchy: - mypackage -> mypackage.blue - mypackage -> mypackage.green - mypackage -> mypackage.orange - mypackage -> mypackage.red - mypackage -> mypackage.yellow - mypackage.blue -> mypackage.blue.alpha - mypackage.blue -> mypackage.blue.beta - mypackage.blue.alpha -> mypackage.blue.alpha.foo -imports: - mypackage.blue.alpha -> mypackage.green - mypackage.blue.alpha -> mypackage.red - mypackage.blue.alpha.foo -> mypackage.yellow - mypackage.blue.beta -> mypackage.orange - mypackage.green -> mypackage.blue.beta - mypackage.green -> mypackage.orange - mypackage.orange -> mypackage.blue.alpha.foo - mypackage.red -> mypackage.blue.alpha - mypackage.yellow -> mypackage.blue.alpha -" - .trim_start() - ); - - graph.squash_module(&mypackage_blue); - - assert_eq!( - graph.pretty_str(), - " -hierarchy: - mypackage -> mypackage.blue - mypackage -> mypackage.green - mypackage -> mypackage.orange - mypackage -> mypackage.red - mypackage -> mypackage.yellow -imports: - mypackage.blue -> mypackage.green - mypackage.blue -> mypackage.orange - mypackage.blue -> mypackage.red - mypackage.blue -> mypackage.yellow - mypackage.green -> mypackage.blue - mypackage.green -> mypackage.orange - mypackage.orange -> mypackage.blue - mypackage.red -> mypackage.blue - mypackage.yellow -> mypackage.blue -" - .trim_start() - ); - } - - #[test] - fn squash_module_no_descendants() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let mypackage_blue = Module::new("mypackage.blue".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(mypackage_blue.clone()); - - graph.squash_module(&mypackage_blue); - - assert_eq!( - graph.pretty_str(), - " -hierarchy: - mypackage -> mypackage.blue -imports: - -" - .trim_start() - ); - } - - #[test] - fn find_count_imports_empty_graph() { - let graph = Graph::default(); - - let result = graph.count_imports(); - - assert_eq!(result, 0); - } - - #[test] - fn find_count_imports_modules_but_no_imports() { - let mut graph = Graph::default(); - graph.add_module(Module::new("mypackage.foo".to_string())); - graph.add_module(Module::new("mypackage.bar".to_string())); - - let result = graph.count_imports(); - - assert_eq!(result, 0); - } - - #[test] - fn find_count_imports_some_imports() { - let mut graph = Graph::default(); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - let mypackage_baz = Module::new("mypackage.baz".to_string()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_import(&mypackage_foo, &mypackage_bar); - graph.add_import(&mypackage_foo, &mypackage_baz); - - let result = graph.count_imports(); - - assert_eq!(result, 2); - } - - #[test] - fn find_count_imports_treats_two_imports_between_same_modules_as_one() { - let mut graph = Graph::default(); - let mypackage_foo = Module::new("mypackage.foo".to_string()); - let mypackage_bar = Module::new("mypackage.bar".to_string()); - graph.add_module(mypackage_foo.clone()); - graph.add_module(mypackage_bar.clone()); - graph.add_import(&mypackage_foo, &mypackage_bar); - graph.add_import(&mypackage_foo, &mypackage_bar); - - let result = graph.count_imports(); - - assert_eq!(result, 1); - } - - #[test] - fn is_module_squashed_when_not_squashed() { - let mut graph = Graph::default(); - // Module we're going to squash. - let mypackage_blue = Module::new("mypackage.blue".to_string()); - let mypackage_blue_alpha = Module::new("mypackage.blue.alpha".to_string()); - // Other module. - let mypackage_green = Module::new("mypackage.green".to_string()); - graph.add_module(mypackage_blue.clone()); - graph.add_module(mypackage_blue_alpha.clone()); - graph.add_module(mypackage_green.clone()); - graph.squash_module(&mypackage_blue); - - let result = graph.is_module_squashed(&mypackage_green); - - assert!(!result); - } - - #[test] - fn is_module_squashed_when_squashed() { - let mut graph = Graph::default(); - // Module we're going to squash. - let mypackage_blue = Module::new("mypackage.blue".to_string()); - let mypackage_blue_alpha = Module::new("mypackage.blue.alpha".to_string()); - // Other module. - let mypackage_green = Module::new("mypackage.green".to_string()); - graph.add_module(mypackage_blue.clone()); - graph.add_module(mypackage_blue_alpha.clone()); - graph.add_module(mypackage_green.clone()); - graph.squash_module(&mypackage_blue); - - let result = graph.is_module_squashed(&mypackage_blue); - - assert!(result); - } - - #[test] - fn find_upstream_modules_when_there_are_some() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let yellow = Module::new("mypackage.yellow".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - let orange = Module::new("mypackage.orange".to_string()); - let brown = Module::new("mypackage.brown".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(yellow.clone()); - graph.add_module(purple.clone()); - graph.add_module(orange.clone()); - graph.add_module(brown.clone()); - // Add the import chain we care about. - graph.add_import(&blue, &green); - graph.add_import(&blue, &red); - graph.add_import(&green, &yellow); - graph.add_import(&yellow, &purple); - // Add an import to blue. - graph.add_import(&brown, &blue); - - let result = graph.find_upstream_modules(&blue, false); - - assert_eq!( - result, - FxHashSet::from_iter([&green, &red, &yellow, &purple]) - ) - } - - #[test] - fn find_upstream_modules_when_module_doesnt_exist() { - let graph = Graph::default(); - let blue = Module::new("mypackage.blue".to_string()); - - let result = graph.find_upstream_modules(&blue, false); - - assert_eq!(result, FxHashSet::default()) - } - - #[test] - fn find_upstream_modules_as_packages() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let alpha = Module::new("mypackage.blue.alpha".to_string()); - let beta = Module::new("mypackage.blue.beta".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let yellow = Module::new("mypackage.yellow".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - let orange = Module::new("mypackage.orange".to_string()); - let brown = Module::new("mypackage.brown".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(alpha.clone()); - graph.add_module(beta.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(yellow.clone()); - graph.add_module(purple.clone()); - graph.add_module(orange.clone()); - graph.add_module(brown.clone()); - // Add the import chains we care about. - graph.add_import(&blue, &green); - graph.add_import(&green, &yellow); - graph.add_import(&alpha, &purple); - graph.add_import(&purple, &brown); - // Despite being technically upstream, beta doesn't appear because it's - // in the same package. - graph.add_import(&purple, &beta); - - let result = graph.find_upstream_modules(&blue, true); - - assert_eq!( - result, - FxHashSet::from_iter([&green, &yellow, &purple, &brown]) - ) - } - - #[test] - fn find_downstream_modules_when_there_are_some() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let yellow = Module::new("mypackage.yellow".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - let orange = Module::new("mypackage.orange".to_string()); - let brown = Module::new("mypackage.brown".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(yellow.clone()); - graph.add_module(purple.clone()); - graph.add_module(orange.clone()); - graph.add_module(brown.clone()); - // Add the import chain we care about. - graph.add_import(&blue, &green); - graph.add_import(&blue, &red); - graph.add_import(&green, &yellow); - graph.add_import(&yellow, &purple); - // Add an import from purple. - graph.add_import(&purple, &brown); - - let result = graph.find_downstream_modules(&purple, false); - - assert_eq!(result, FxHashSet::from_iter([&yellow, &green, &blue])) - } - - #[test] - fn find_downstream_modules_when_module_doesnt_exist() { - let graph = Graph::default(); - let blue = Module::new("mypackage.blue".to_string()); - - let result = graph.find_downstream_modules(&blue, false); - - assert_eq!(result, FxHashSet::default()) - } - - #[test] - fn find_downstream_modules_as_packages() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let alpha = Module::new("mypackage.blue.alpha".to_string()); - let beta = Module::new("mypackage.blue.beta".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let yellow = Module::new("mypackage.yellow".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - let orange = Module::new("mypackage.orange".to_string()); - let brown = Module::new("mypackage.brown".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(alpha.clone()); - graph.add_module(beta.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(yellow.clone()); - graph.add_module(purple.clone()); - graph.add_module(orange.clone()); - graph.add_module(brown.clone()); - // Add the import chains we care about. - graph.add_import(&yellow, &green); - graph.add_import(&green, &blue); - graph.add_import(&brown, &purple); - graph.add_import(&purple, &alpha); - // Despite being technically downstream, beta doesn't appear because it's - // in the same package. - graph.add_import(&beta, &yellow); - - let result = graph.find_downstream_modules(&blue, true); - - assert_eq!( - result, - FxHashSet::from_iter([&green, &yellow, &purple, &brown]) - ) - } - - // find_shortest_chain - #[test] - fn find_shortest_chain_none() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let green = Module::new("mypackage.green".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - graph.add_module(purple.clone()); - // Add imports that are irrelevant. - graph.add_import(&purple, &blue); - graph.add_import(&green, &purple); - - let result = graph.find_shortest_chain(&blue, &green); - - assert!(result.is_none()) - } - - #[test] - fn find_shortest_chain_one_step() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(purple.clone()); - // Add the one-step chain. - graph.add_import(&blue, &green); - // Add a longer chain. - graph.add_import(&blue, &red); - graph.add_import(&red, &green); - // Add other imports that are irrelevant. - graph.add_import(&purple, &blue); - graph.add_import(&green, &purple); - - let result = graph.find_shortest_chain(&blue, &green).unwrap(); - - assert_eq!(result, vec![&blue, &green]) - } - - #[test] - fn find_shortest_chain_one_step_reverse() { - let mut graph = Graph::default(); - let blue = Module::new("mypackage.blue".to_string()); - let green = Module::new("mypackage.green".to_string()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - // Add the one-step chain. - graph.add_import(&blue, &green); - - let result = graph.find_shortest_chain(&green, &blue); - - assert_eq!(result.is_none(), true); - } - - #[test] - fn find_shortest_chain_two_steps() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let orange = Module::new("mypackage.orange".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(orange.clone()); - graph.add_module(purple.clone()); - // Add the two-step chain. - graph.add_import(&blue, &red); - graph.add_import(&red, &green); - // Add a longer chain. - graph.add_import(&blue, &red); - graph.add_import(&red, &orange); - graph.add_import(&orange, &green); - // Add other imports that are irrelevant. - graph.add_import(&purple, &blue); - graph.add_import(&green, &purple); - - let result = graph.find_shortest_chain(&blue, &green).unwrap(); - - assert_eq!(result, vec![&blue, &red, &green]) - } - - #[test] - fn find_shortest_chain_three_steps() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let orange = Module::new("mypackage.orange".to_string()); - let yellow = Module::new("mypackage.yellow".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(orange.clone()); - graph.add_module(yellow.clone()); - graph.add_module(purple.clone()); - // Add the three-step chain. - graph.add_import(&blue, &red); - graph.add_import(&red, &orange); - graph.add_import(&orange, &green); - // Add a longer chain. - graph.add_import(&blue, &red); - graph.add_import(&red, &orange); - graph.add_import(&orange, &yellow); - graph.add_import(&yellow, &green); - // Add other imports that are irrelevant. - graph.add_import(&purple, &blue); - graph.add_import(&green, &purple); - - let result = graph.find_shortest_chain(&blue, &green).unwrap(); - - assert_eq!(result, vec![&blue, &red, &orange, &green]) - } - - // find_shortest_chains - - #[test] - fn find_shortest_chains_none() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let green = Module::new("mypackage.green".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - graph.add_module(purple.clone()); - // Add imports that are irrelevant. - graph.add_import(&purple, &blue); - graph.add_import(&green, &purple); - - let result = graph.find_shortest_chains(&blue, &green, true); - - assert_eq!(result, Ok(FxHashSet::default())); - } - - #[test] - fn find_shortest_chains_between_passed_modules() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(purple.clone()); - // Add a chain. - graph.add_import(&blue, &red); - graph.add_import(&red, &green); - // Add other imports that are irrelevant. - graph.add_import(&purple, &blue); - graph.add_import(&green, &purple); - - let result = graph.find_shortest_chains(&blue, &green, true); - - assert_eq!(result, Ok(FxHashSet::from_iter([vec![blue, red, green],]))); - } - - #[test] - fn find_shortest_chains_between_passed_module_and_child() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let green = Module::new("mypackage.green".to_string()); - let green_alpha = Module::new("mypackage.green.alpha".to_string()); - let red = Module::new("mypackage.red".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - graph.add_module(green_alpha.clone()); - graph.add_module(red.clone()); - graph.add_module(purple.clone()); - // Add a chain. - graph.add_import(&blue, &red); - graph.add_import(&red, &green_alpha); - // Add other imports that are irrelevant. - graph.add_import(&purple, &blue); - graph.add_import(&green, &purple); - - let result = graph.find_shortest_chains(&blue, &green, true); - - assert_eq!( - result, - Ok(FxHashSet::from_iter([vec![blue, red, green_alpha]])) - ); - } - - #[test] - fn find_shortest_chains_between_passed_module_and_grandchild() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let green = Module::new("mypackage.green".to_string()); - let green_alpha = Module::new("mypackage.green.alpha".to_string()); - let green_alpha_one = Module::new("mypackage.green.alpha.one".to_string()); - let red = Module::new("mypackage.red".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - graph.add_module(green_alpha.clone()); - graph.add_module(green_alpha_one.clone()); - graph.add_module(red.clone()); - graph.add_module(purple.clone()); - // Add a chain. - graph.add_import(&blue, &red); - graph.add_import(&red, &green_alpha_one); - // Add other imports that are irrelevant. - graph.add_import(&purple, &blue); - graph.add_import(&green, &purple); - - let result = graph.find_shortest_chains(&blue, &green, true); - - assert_eq!( - result, - Ok(FxHashSet::from_iter([vec![blue, red, green_alpha_one],])) - ) - } - - #[test] - fn find_shortest_chains_between_child_and_passed_module() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let blue_alpha = Module::new("mypackage.blue.alpha".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(blue_alpha.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(purple.clone()); - // Add a chain. - graph.add_import(&blue_alpha, &red); - graph.add_import(&red, &green); - // Add other imports that are irrelevant. - graph.add_import(&purple, &blue); - graph.add_import(&green, &purple); - - let result = graph.find_shortest_chains(&blue, &green, true); - - assert_eq!( - result, - Ok(FxHashSet::from_iter([vec![blue_alpha, red, green],])) - ); - } - - #[test] - fn find_shortest_chains_between_grandchild_and_passed_module() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let blue_alpha = Module::new("mypackage.blue.alpha".to_string()); - let blue_alpha_one = Module::new("mypackage.blue.alpha.one".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(blue_alpha.clone()); - graph.add_module(blue_alpha_one.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(purple.clone()); - // Add a chain. - graph.add_import(&blue_alpha_one, &red); - graph.add_import(&red, &green); - // Add other imports that are irrelevant. - graph.add_import(&purple, &blue); - graph.add_import(&green, &purple); - - let result = graph.find_shortest_chains(&blue, &green, true); - - assert_eq!( - result, - Ok(FxHashSet::from_iter([vec![blue_alpha_one, red, green],])) - ) - } - - #[test] - fn chain_exists_true_as_packages_false() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let blue_alpha = Module::new("mypackage.blue.alpha".to_string()); - let blue_alpha_one = Module::new("mypackage.blue.alpha.one".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(blue_alpha.clone()); - graph.add_module(blue_alpha_one.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(purple.clone()); - // Add a chain. - graph.add_import(&blue_alpha_one, &red); - graph.add_import(&red, &green); - // Add other imports that are irrelevant. - graph.add_import(&purple, &blue); - graph.add_import(&green, &purple); - - let result = graph.chain_exists(&blue_alpha_one, &green, false); - - assert!(result); - } - - #[test] - fn chain_exists_false_as_packages_false() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let blue_alpha = Module::new("mypackage.blue.alpha".to_string()); - let blue_alpha_one = Module::new("mypackage.blue.alpha.one".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(blue_alpha.clone()); - graph.add_module(blue_alpha_one.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(purple.clone()); - // Add a chain. - graph.add_import(&blue_alpha_one, &red); - graph.add_import(&red, &green); - - let result = graph.chain_exists(&blue, &green, false); - - assert_eq!(result, false); - } - - #[test] - fn chain_exists_true_as_packages_true() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let blue_alpha = Module::new("mypackage.blue.alpha".to_string()); - let blue_alpha_one = Module::new("mypackage.blue.alpha.one".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(blue_alpha.clone()); - graph.add_module(blue_alpha_one.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(purple.clone()); - // Add a chain. - graph.add_import(&blue_alpha_one, &red); - graph.add_import(&red, &green); - - let result = graph.chain_exists(&blue, &green, true); - - assert_eq!(result, true); - } - - #[test] - fn chain_exists_false_as_packages_true() { - let mut graph = Graph::default(); - let mypackage = Module::new("mypackage".to_string()); - let blue = Module::new("mypackage.blue".to_string()); - let blue_alpha = Module::new("mypackage.blue.alpha".to_string()); - let blue_alpha_one = Module::new("mypackage.blue.alpha.one".to_string()); - let green = Module::new("mypackage.green".to_string()); - let red = Module::new("mypackage.red".to_string()); - let purple = Module::new("mypackage.purple".to_string()); - graph.add_module(mypackage.clone()); - graph.add_module(blue.clone()); - graph.add_module(blue_alpha.clone()); - graph.add_module(blue_alpha_one.clone()); - graph.add_module(green.clone()); - graph.add_module(red.clone()); - graph.add_module(purple.clone()); - // Add a chain. - graph.add_import(&blue_alpha_one, &red); - graph.add_import(&red, &green); - - let result = graph.chain_exists(&green, &blue, true); - - assert_eq!(result, false); - } - - #[test] - fn find_illegal_dependencies_for_layers_empty_everything() { - let graph = Graph::default(); - - let dependencies = graph.find_illegal_dependencies_for_layers(vec![], FxHashSet::default()); - - assert_eq!(dependencies, Ok(vec![])); - } - - #[test] - fn find_illegal_dependencies_for_layers_no_such_container() { - let graph = Graph::default(); - let container = "nonexistent_container".to_string(); - - let dependencies = graph.find_illegal_dependencies_for_layers( - vec![], - FxHashSet::from_iter([container.clone()]), - ); - - assert_eq!( - dependencies, - Err(NoSuchContainer { - container: container - }) - ); - } - - #[test] - fn find_illegal_dependencies_for_layers_nonexistent_layers_no_container() { - let graph = Graph::default(); - let level = Level { - layers: vec!["nonexistent".to_string()], - independent: true, - }; - - let dependencies = - graph.find_illegal_dependencies_for_layers(vec![level], FxHashSet::default()); - - assert_eq!(dependencies, Ok(vec![])); - } - - #[test] - fn find_illegal_dependencies_for_layers_nonexistent_layers_with_container() { - let mut graph = Graph::default(); - graph.add_module(Module::new("mypackage".to_string())); - let level = Level { - layers: vec!["nonexistent".to_string()], - independent: true, - }; - let container = "mypackage".to_string(); - - let dependencies = graph - .find_illegal_dependencies_for_layers(vec![level], FxHashSet::from_iter([container])); - - assert_eq!(dependencies, Ok(vec![])); - } - - #[test] - fn find_illegal_dependencies_for_layers_no_container_direct_dependency() { - let mut graph = Graph::default(); - let high = Module::new("high".to_string()); - let low = Module::new("low".to_string()); - graph.add_import(&low, &high); - let levels = vec![ - Level { - layers: vec![high.name.clone()], - independent: true, - }, - Level { - layers: vec![low.name.clone()], - independent: true, - }, - ]; - - let dependencies = graph.find_illegal_dependencies_for_layers(levels, FxHashSet::default()); - - assert_eq!( - dependencies, - Ok(vec![PackageDependency { - importer: low.clone(), - imported: high.clone(), - routes: vec![Route { - heads: vec![low.clone()], - middle: vec![], - tails: vec![high.clone()], - }] - }]) - ); - } - - #[test] - fn find_illegal_dependencies_for_layers_no_container_indirect_dependency() { - let mut graph = Graph::default(); - let high = Module::new("high".to_string()); - let elsewhere = Module::new("elsewhere".to_string()); - let low = Module::new("low".to_string()); - graph.add_import(&low, &elsewhere); - graph.add_import(&elsewhere, &high); - let levels = vec![ - Level { - layers: vec![high.name.clone()], - independent: true, - }, - Level { - layers: vec![low.name.clone()], - independent: true, - }, - ]; - - let dependencies = graph.find_illegal_dependencies_for_layers(levels, FxHashSet::default()); - - assert_eq!( - dependencies, - Ok(vec![PackageDependency { - importer: low.clone(), - imported: high.clone(), - routes: vec![Route { - heads: vec![low.clone()], - middle: vec![elsewhere.clone()], - tails: vec![high.clone()], - }] - }]) - ); - } - - #[test] - fn find_illegal_dependencies_for_layers_containers() { - let mut graph = Graph::default(); - let blue_high = Module::new("blue.high".to_string()); - let blue_high_alpha = Module::new("blue.high.alpha".to_string()); - let blue_low = Module::new("blue.low".to_string()); - let blue_low_beta = Module::new("blue.low.beta".to_string()); - let green_high = Module::new("green.high".to_string()); - let green_high_gamma = Module::new("green.high.gamma".to_string()); - let green_low = Module::new("green.low".to_string()); - let green_low_delta = Module::new("green.low.delta".to_string()); - graph.add_module(Module::new("blue".to_string())); - graph.add_module(blue_high.clone()); - graph.add_module(blue_low.clone()); - graph.add_module(Module::new("green".to_string())); - graph.add_module(green_high.clone()); - graph.add_module(green_low.clone()); - graph.add_import(&blue_low_beta, &blue_high_alpha); - graph.add_import(&green_low_delta, &green_high_gamma); - - let levels = vec![ - Level { - layers: vec!["high".to_string()], - independent: true, - }, - Level { - layers: vec!["low".to_string()], - independent: true, - }, - ]; - let containers = FxHashSet::from_iter(["blue".to_string(), "green".to_string()]); - - let dependencies = graph.find_illegal_dependencies_for_layers(levels, containers); - - assert_eq!( - dependencies, - Ok(vec![ - PackageDependency { - importer: blue_low.clone(), - imported: blue_high.clone(), - routes: vec![Route { - heads: vec![blue_low_beta.clone()], - middle: vec![], - tails: vec![blue_high_alpha.clone()], - }] - }, - PackageDependency { - importer: green_low.clone(), - imported: green_high.clone(), - routes: vec![Route { - heads: vec![green_low_delta.clone()], - middle: vec![], - tails: vec![green_high_gamma.clone()], - }] - } - ]) - ); - } - - #[test] - fn find_illegal_dependencies_for_layers_independent() { - let mut graph = Graph::default(); - let blue = Module::new("blue".to_string()); - let green = Module::new("green".to_string()); - let blue_alpha = Module::new("blue.alpha".to_string()); - let green_beta = Module::new("green.beta".to_string()); - graph.add_module(blue.clone()); - graph.add_module(green.clone()); - graph.add_import(&blue_alpha, &green_beta); - - let levels = vec![Level { - layers: vec![blue.name.clone(), green.name.clone()], - independent: true, - }]; - - let dependencies = graph.find_illegal_dependencies_for_layers(levels, FxHashSet::default()); - - assert_eq!( - dependencies, - Ok(vec![PackageDependency { - importer: blue.clone(), - imported: green.clone(), - routes: vec![Route { - heads: vec![blue_alpha.clone()], - middle: vec![], - tails: vec![green_beta.clone()], - }] - }]) - ); - } - - #[test] - fn get_import_details_no_modules() { - let graph = Graph::default(); - let importer = Module::new("foo".to_string()); - let imported = Module::new("bar".to_string()); - - let result = graph.get_import_details(&importer, &imported); - - assert_eq!(result, FxHashSet::default()); - } - - #[test] - fn get_import_details_module_without_metadata() { - let mut graph = Graph::default(); - let importer = Module::new("foo".to_string()); - let imported = Module::new("bar".to_string()); - graph.add_import(&importer, &imported); - - let result = graph.get_import_details(&importer, &imported); - - assert_eq!(result, FxHashSet::default()); - } - - #[test] - fn get_import_details_module_one_result() { - let mut graph = Graph::default(); - let importer = Module::new("foo".to_string()); - let imported = Module::new("bar".to_string()); - let import = DetailedImport { - importer: importer.clone(), - imported: imported.clone(), - line_number: 5, - line_contents: "import bar".to_string(), - }; - let unrelated_import = DetailedImport { - importer: importer.clone(), - imported: Module::new("baz".to_string()), - line_number: 2, - line_contents: "-".to_string(), - }; - graph.add_detailed_import(&import); - graph.add_detailed_import(&unrelated_import); - - let result = graph.get_import_details(&importer, &imported); - - assert_eq!(result, FxHashSet::from_iter([import])); - } - - #[test] - fn get_import_details_module_two_results() { - let mut graph = Graph::default(); - let blue = Module::new("blue".to_string()); - let green = Module::new("green".to_string()); - let blue_to_green_a = DetailedImport { - importer: blue.clone(), - imported: green.clone(), - line_number: 5, - line_contents: "import green".to_string(), - }; - let blue_to_green_b = DetailedImport { - importer: blue.clone(), - imported: green.clone(), - line_number: 15, - line_contents: "import green".to_string(), - }; - graph.add_detailed_import(&blue_to_green_a); - graph.add_detailed_import(&blue_to_green_b); - - let result = graph.get_import_details(&blue, &green); - - assert_eq!( - result, - FxHashSet::from_iter([blue_to_green_a, blue_to_green_b]) - ); - } - - #[test] - fn get_import_details_after_removal() { - let mut graph = Graph::default(); - let importer = Module::new("foo".to_string()); - let imported = Module::new("bar".to_string()); - let import = DetailedImport { - importer: importer.clone(), - imported: imported.clone(), - line_number: 5, - line_contents: "import bar".to_string(), - }; - let unrelated_import = DetailedImport { - importer: importer.clone(), - imported: Module::new("baz".to_string()), - line_number: 2, - line_contents: "-".to_string(), - }; - graph.add_detailed_import(&import); - graph.add_detailed_import(&unrelated_import); - graph.remove_import(&import.importer, &import.imported); - - let result = graph.get_import_details(&importer, &imported); - - assert_eq!(result, FxHashSet::default()); - } - - #[test] - fn get_import_details_after_removal_of_unrelated_import() { - let mut graph = Graph::default(); - let importer = Module::new("foo".to_string()); - let imported = Module::new("bar".to_string()); - let import = DetailedImport { - importer: importer.clone(), - imported: imported.clone(), - line_number: 5, - line_contents: "import bar".to_string(), - }; - let unrelated_import = DetailedImport { - importer: importer.clone(), - imported: Module::new("baz".to_string()), - line_number: 2, - line_contents: "-".to_string(), - }; - graph.add_detailed_import(&import); - graph.add_detailed_import(&unrelated_import); - graph.remove_import(&unrelated_import.importer, &unrelated_import.imported); - - let result = graph.get_import_details(&importer, &imported); - - assert_eq!(result, FxHashSet::from_iter([import])); + pub fn is_module_squashed(&self, module: &str) -> bool { + todo!() } } diff --git a/rust/src/hierarchy.rs b/rust/src/hierarchy.rs new file mode 100644 index 00000000..ee8b134f --- /dev/null +++ b/rust/src/hierarchy.rs @@ -0,0 +1,294 @@ +use anyhow::{bail, Context, Result}; +use bimap::BiMap; +use getset::{CopyGetters, Getters}; +use slotmap::{new_key_type, SecondaryMap, SlotMap}; +use std::collections::HashSet; + +new_key_type! { pub struct ModuleToken; } + +#[derive(Debug, Clone, Default, PartialEq, Eq, Hash, Getters, CopyGetters)] +pub struct Module { + #[getset(get_copy = "pub")] + token: ModuleToken, + + #[getset(get = "pub")] + name: String, + + // Invisible modules exist in the hierarchy but haven't been explicitly added to the graph. + #[getset(get_copy = "pub")] + is_invisible: bool, + + #[getset(get_copy = "pub")] + is_squashed: bool, +} + +impl From for ModuleToken { + fn from(value: Module) -> Self { + value.token + } +} + +#[derive(Debug, Clone, Default)] +pub struct ModuleHierarchy { + modules_by_name: BiMap, + modules: SlotMap, + module_parents: SecondaryMap>, + module_children: SecondaryMap>, +} + +impl ModuleHierarchy { + pub fn add_module(&mut self, name: &str) -> Result { + if let Some(module) = self.get_by_name(name) { + let module = self.modules.get_mut(module.token).unwrap(); + module.is_invisible = false; + return Ok(module.token); + } + + // foo.bar.baz => [foo.bar.baz, foo.bar, foo] + let mut names = { + let mut names = vec![name.to_owned()]; + while let Some(parent_name) = parent_name(names.last().unwrap()) { + names.push(parent_name); + } + names + }; + + let mut parent: Option = None; + while let Some(name) = names.pop() { + if let Some(module) = self.modules_by_name.get_by_left(&name) { + parent = Some(*module) + } else { + let module = self.modules.insert_with_key(|token| Module { + token, + name: name.clone(), + is_invisible: !names.is_empty(), + is_squashed: false, + }); + self.modules_by_name.insert(name, module); + self.module_parents.insert(module, parent); + self.module_children.insert(module, HashSet::new()); + if let Some(parent) = parent { + self.module_children[parent].insert(module); + } + parent = Some(module) + } + } + + Ok(*self.modules_by_name.get_by_left(name).unwrap()) + } + + // TODO(peter) Replace with method to actually squash the module? + pub fn mark_squashed(&mut self, module: ModuleToken) -> Result<()> { + let module = self + .modules + .get_mut(module) + .context("Module does not exist")?; + if self.module_children[module.token].is_empty() { + bail!("Cannot mark a module with children as squashed") + } + module.is_squashed = true; + Ok(()) + } + + pub fn remove_module(&mut self, module: ModuleToken) -> Result<()> { + if !self.modules.contains_key(module) { + return Ok(()); + } + + if !self.module_children[module].is_empty() { + bail!("Cannot remove a module that has children") + } + + if let Some(parent) = self.module_parents[module] { + self.module_children[parent].remove(&module); + } + + self.modules_by_name.remove_by_right(&module); + self.modules.remove(module); + self.module_parents.remove(module); + self.module_children.remove(module); + + Ok(()) + } + + pub fn get(&self, module: ModuleToken) -> Option<&Module> { + self.modules.get(module) + } + + pub fn get_by_name(&self, name: &str) -> Option<&Module> { + match self.modules_by_name.get_by_left(name) { + Some(token) => self.get(*token), + None => None, + } + } + + pub fn get_parent(&self, module: ModuleToken) -> Option<&Module> { + match self.module_parents.get(module) { + Some(parent) => parent.map(|parent| self.get(parent).unwrap()), + None => None, + } + } + + pub fn get_children(&self, module: ModuleToken) -> impl Iterator { + let children = match self.module_children.get(module) { + Some(children) => children + .iter() + .map(|child| self.get(*child).unwrap()) + .collect(), + None => Vec::new(), + }; + children.into_iter() + } + + /// Returns an iterator over the passed modules descendants. + /// + /// Parent modules will be yielded before their child modules. + pub fn get_descendants(&self, module: ModuleToken) -> impl Iterator { + let mut descendants = self.get_children(module).collect::>(); + for child in descendants.clone() { + descendants.extend(self.get_descendants(child.token).collect::>()) + } + descendants.into_iter() + } +} + +impl From for Vec { + fn from(value: ModuleToken) -> Self { + vec![value] + } +} + +impl From for HashSet { + fn from(value: ModuleToken) -> Self { + HashSet::from([value]) + } +} + +pub trait ExtendWithDescendants: + Sized + Clone + IntoIterator + Extend +{ + /// Extend this collection of module tokens with all descendant items. + fn extend_with_descendants(&mut self, hierarchy: &ModuleHierarchy) { + for item in self.clone().into_iter() { + let descendants = hierarchy.get_descendants(item).map(|item| item.token()); + self.extend(descendants); + } + } + + /// Extend this collection of module tokens with all descendant items. + fn with_descendants(mut self, hierarchy: &ModuleHierarchy) -> Self { + self.extend_with_descendants(hierarchy); + self + } +} + +impl + Extend> + ExtendWithDescendants for T +{ +} + +fn parent_name(name: &str) -> Option { + match name.rsplit_once(".") { + Some((base, _)) => Some(base.to_owned()), + None => None, + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hierarchy() -> Result<()> { + let mut hierarchy = ModuleHierarchy::default(); + + let _ = hierarchy.add_module("foo.bar")?; + let _ = hierarchy.add_module("foo.baz")?; + + let foo = hierarchy.get_by_name("foo").unwrap().token; + let foo_bar = hierarchy.get_by_name("foo.bar").unwrap().token; + let foo_baz = hierarchy.get_by_name("foo.baz").unwrap().token; + + assert_eq!(hierarchy.modules_by_name.len(), 3); + assert_eq!(hierarchy.modules.len(), 3); + assert_eq!(hierarchy.module_parents.len(), 3); + assert_eq!(hierarchy.module_children.len(), 3); + + assert_eq!( + hierarchy + .get_children(foo) + .map(|child| child.token) + .collect::>(), + HashSet::from([foo_bar, foo_baz]), + ); + + assert_eq!( + hierarchy.get_parent(foo_bar).map(|parent| parent.token), + Some(foo), + ); + assert_eq!( + hierarchy.get_parent(foo_baz).map(|parent| parent.token), + Some(foo), + ); + + assert_eq!(hierarchy.get(foo).unwrap().is_invisible, true); + assert_eq!(hierarchy.get(foo).unwrap().is_squashed, false); + + assert_eq!(hierarchy.get(foo_bar).unwrap().is_invisible, false); + assert_eq!(hierarchy.get(foo_bar).unwrap().is_squashed, false); + + let _ = hierarchy.add_module("foo")?; + assert_eq!(hierarchy.get(foo).unwrap().is_invisible, false); + + Ok(()) + } + + #[test] + fn test_get_descendants() -> Result<()> { + let mut hierarchy = ModuleHierarchy::default(); + + let _ = hierarchy.add_module("foo.bar.baz.bax")?; + + let foo = hierarchy.get_by_name("foo").unwrap().token; + let foo_bar = hierarchy.get_by_name("foo.bar").unwrap().token; + let foo_bar_baz = hierarchy.get_by_name("foo.bar.baz").unwrap().token; + let foo_bar_baz_bax = hierarchy.get_by_name("foo.bar.baz.bax").unwrap().token; + + assert_eq!( + hierarchy + .get_children(foo) + .map(|child| child.token) + .collect::>(), + vec![foo_bar], + ); + + // Collect into Vec<_> here rather than HashSet<_> so that we can check ordering. + assert_eq!( + hierarchy + .get_descendants(foo) + .map(|child| child.token) + .collect::>(), + vec![foo_bar, foo_bar_baz, foo_bar_baz_bax], + ); + + Ok(()) + } + + #[test] + fn test_extend_with_descendants() -> Result<()> { + let mut hierarchy = ModuleHierarchy::default(); + + let _ = hierarchy.add_module("foo.bar.baz.bax")?; + + let foo = hierarchy.get_by_name("foo").unwrap().token; + let foo_bar = hierarchy.get_by_name("foo.bar").unwrap().token; + let foo_bar_baz = hierarchy.get_by_name("foo.bar.baz").unwrap().token; + let foo_bar_baz_bax = hierarchy.get_by_name("foo.bar.baz.bax").unwrap().token; + + let mut modules: Vec<_> = foo.into(); + modules.extend_with_descendants(&hierarchy); + assert_eq!(modules, vec![foo, foo_bar, foo_bar_baz, foo_bar_baz_bax]); + + Ok(()) + } +} diff --git a/rust/src/imports.rs b/rust/src/imports.rs new file mode 100644 index 00000000..9e22d43b --- /dev/null +++ b/rust/src/imports.rs @@ -0,0 +1,55 @@ +use crate::hierarchy::ModuleToken; +use anyhow::Result; +use derive_new::new; +use getset::{CopyGetters, Getters}; +use slotmap::SecondaryMap; +use std::collections::{HashMap, HashSet}; + +#[derive(Debug, Clone, new, Getters, CopyGetters)] +pub struct ImportDetails { + #[getset(get_copy = "pub")] + line_number: usize, + + #[new(into)] + #[getset(get = "pub")] + line_contents: String, +} + +#[derive(Debug, Clone, Default)] +pub struct ModuleImports { + imports: SecondaryMap>, + reverse_imports: SecondaryMap>, + import_details: HashMap<(ModuleToken, ModuleToken), ImportDetails>, +} + +impl ModuleImports { + pub fn direct_import_exists(&self, importer: ModuleToken, imported: ModuleToken) -> bool { + match self.imports.get(importer) { + Some(imports) => imports.contains(&imported), + None => false, + } + } + + pub fn add_import( + &mut self, + importer: ModuleToken, + imported: ModuleToken, + details: Option, + ) -> Result<()> { + self.imports + .entry(importer) + .unwrap() + .or_default() + .insert(imported); + self.reverse_imports + .entry(imported) + .unwrap() + .or_default() + .insert(importer); + if details.is_some() { + self.import_details + .insert((importer, imported), details.unwrap()); + } + Ok(()) + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 10d52631..ad6e0552 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,11 +1,11 @@ pub mod graph; +pub(crate) mod hierarchy; +pub(crate) mod imports; -use crate::graph::{DetailedImport, Graph, Level, Module, PackageDependency}; -use log::info; +use crate::graph::{DetailedImport, Graph}; use pyo3::create_exception; -use pyo3::exceptions::PyValueError; use pyo3::prelude::*; -use pyo3::types::{PyDict, PyFrozenSet, PyList, PySet, PyString, PyTuple}; +use pyo3::types::{PyList, PySet, PyTuple}; use rustc_hash::FxHashSet; #[pymodule] @@ -35,53 +35,47 @@ impl GraphWrapper { } pub fn get_modules(&self) -> FxHashSet { - self._graph - .get_modules() - .iter() - .map(|module| module.name.clone()) - .collect() + todo!() } #[pyo3(signature = (module, is_squashed = false))] pub fn add_module(&mut self, module: &str, is_squashed: bool) -> PyResult<()> { - let module_struct = Module::new(module.to_string()); - - if let Some(ancestor_squashed_module) = - self._graph.find_ancestor_squashed_module(&module_struct) - { - return Err(PyValueError::new_err(format!( - "Module is a descendant of squashed module {}.", - &ancestor_squashed_module.name - ))); - } - - if self._graph.get_modules().contains(&module_struct) { - if self._graph.is_module_squashed(&module_struct) != is_squashed { - return Err(PyValueError::new_err( - "Cannot add a squashed module when it is already present in the graph \ - as an unsquashed module, or vice versa.", - )); - } - } + // TODO(peter) + // if let Some(ancestor_squashed_module) = + // self._graph.find_ancestor_squashed_module(&module_struct) + // { + // return Err(PyValueError::new_err(format!( + // "Module is a descendant of squashed module {}.", + // &ancestor_squashed_module.name + // ))); + // } + // + // if self._graph.get_modules().contains(&module_struct) { + // if self._graph.is_module_squashed(&module_struct) != is_squashed { + // return Err(PyValueError::new_err( + // "Cannot add a squashed module when it is already present in the graph \ + // as an unsquashed module, or vice versa.", + // )); + // } + // } match is_squashed { - false => self._graph.add_module(module_struct), - true => self._graph.add_squashed_module(module_struct), + false => self._graph.add_module(module), + true => self._graph.add_squashed_module(module), }; Ok(()) } pub fn remove_module(&mut self, module: &str) { - self._graph.remove_module(&Module::new(module.to_string())); + self._graph.remove_module(module); } pub fn squash_module(&mut self, module: &str) { - self._graph.squash_module(&Module::new(module.to_string())); + todo!() } pub fn is_module_squashed(&self, module: &str) -> bool { - self._graph - .is_module_squashed(&Module::new(module.to_string())) + todo!() } #[pyo3(signature = (*, importer, imported, line_number=None, line_contents=None))] @@ -92,15 +86,13 @@ impl GraphWrapper { line_number: Option, line_contents: Option<&str>, ) { - let importer = Module::new(importer.to_string()); - let imported = Module::new(imported.to_string()); match (line_number, line_contents) { (Some(line_number), Some(line_contents)) => { self._graph.add_detailed_import(&DetailedImport { - importer: importer, - imported: imported, - line_number: line_number, - line_contents: line_contents.to_string(), + importer, + imported, + line_number, + line_contents, }); } (None, None) => { @@ -115,31 +107,19 @@ impl GraphWrapper { #[pyo3(signature = (*, importer, imported))] pub fn remove_import(&mut self, importer: &str, imported: &str) { - self._graph.remove_import( - &Module::new(importer.to_string()), - &Module::new(imported.to_string()), - ); + todo!() } pub fn count_imports(&self) -> usize { - self._graph.count_imports() + todo!() } pub fn find_children(&self, module: &str) -> FxHashSet { - self._graph - .find_children(&Module::new(module.to_string())) - .iter() - .map(|child| child.name.clone()) - .collect() + todo!() } pub fn find_descendants(&self, module: &str) -> FxHashSet { - self._graph - .find_descendants(&Module::new(module.to_string())) - .unwrap() - .iter() - .map(|descendant| descendant.name.clone()) - .collect() + todo!() } #[pyo3(signature = (*, importer, imported, as_packages = false))] @@ -149,40 +129,15 @@ impl GraphWrapper { imported: &str, as_packages: bool, ) -> PyResult { - if as_packages { - let importer_module = Module::new(importer.to_string()); - let imported_module = Module::new(imported.to_string()); - // Raise a ValueError if they are in the same package. - // (direct_import_exists) will panic if they are passed. - // TODO - this is a simpler check than Python, is it enough? - if importer_module.is_descendant_of(&imported_module) - || imported_module.is_descendant_of(&importer_module) - { - return Err(PyValueError::new_err("Modules have shared descendants.")); - } - } - - Ok(self._graph.direct_import_exists( - &Module::new(importer.to_string()), - &Module::new(imported.to_string()), - as_packages, - )) + todo!() } pub fn find_modules_directly_imported_by(&self, module: &str) -> FxHashSet { - self._graph - .find_modules_directly_imported_by(&Module::new(module.to_string())) - .iter() - .map(|imported| imported.name.clone()) - .collect() + todo!() } pub fn find_modules_that_directly_import(&self, module: &str) -> FxHashSet { - self._graph - .find_modules_that_directly_import(&Module::new(module.to_string())) - .iter() - .map(|importer| importer.name.clone()) - .collect() + todo!() } #[pyo3(signature = (*, importer, imported))] @@ -192,66 +147,23 @@ impl GraphWrapper { importer: &str, imported: &str, ) -> PyResult> { - let mut vector: Vec> = vec![]; - - let mut rust_import_details_vec: Vec = self - ._graph - .get_import_details( - &Module::new(importer.to_string()), - &Module::new(imported.to_string()), - ) - .into_iter() - .collect(); - rust_import_details_vec.sort(); - - for detailed_import in rust_import_details_vec { - let pydict = PyDict::new(py); - pydict.set_item( - "importer".to_string(), - detailed_import.importer.name.clone(), - )?; - pydict.set_item( - "imported".to_string(), - detailed_import.imported.name.clone(), - )?; - pydict.set_item("line_number".to_string(), detailed_import.line_number)?; - pydict.set_item( - "line_contents".to_string(), - detailed_import.line_contents.clone(), - )?; - vector.push(pydict); - } - PyList::new(py, &vector) + todo!() } #[allow(unused_variables)] #[pyo3(signature = (module, as_package=false))] pub fn find_downstream_modules(&self, module: &str, as_package: bool) -> FxHashSet { - // Turn the Modules to Strings. - self._graph - .find_downstream_modules(&Module::new(module.to_string()), as_package) - .iter() - .map(|downstream| downstream.name.clone()) - .collect() + todo!() } #[allow(unused_variables)] #[pyo3(signature = (module, as_package=false))] pub fn find_upstream_modules(&self, module: &str, as_package: bool) -> FxHashSet { - self._graph - .find_upstream_modules(&Module::new(module.to_string()), as_package) - .iter() - .map(|upstream| upstream.name.clone()) - .collect() + todo!() } pub fn find_shortest_chain(&self, importer: &str, imported: &str) -> Option> { - let chain = self._graph.find_shortest_chain( - &Module::new(importer.to_string()), - &Module::new(imported.to_string()), - )?; - - Some(chain.iter().map(|module| module.name.clone()).collect()) + todo!() } #[pyo3(signature = (importer, imported, as_packages=true))] @@ -262,25 +174,7 @@ impl GraphWrapper { imported: &str, as_packages: bool, ) -> PyResult> { - let rust_chains: FxHashSet> = self - ._graph - .find_shortest_chains( - &Module::new(importer.to_string()), - &Module::new(imported.to_string()), - as_packages, - ) - .map_err(|string| PyValueError::new_err(string))?; - - let mut tuple_chains: Vec> = vec![]; - for rust_chain in rust_chains.iter() { - let module_names: Vec> = rust_chain - .iter() - .map(|module| PyString::new(py, &module.name)) - .collect(); - let tuple = PyTuple::new(py, &module_names)?; - tuple_chains.push(tuple); - } - PySet::new(py, &tuple_chains) + todo!() } #[pyo3(signature = (importer, imported, as_packages=false))] @@ -290,22 +184,7 @@ impl GraphWrapper { imported: &str, as_packages: bool, ) -> PyResult { - if as_packages { - let importer_module = Module::new(importer.to_string()); - let imported_module = Module::new(imported.to_string()); - // Raise a ValueError if they are in the same package. - // TODO - this is a simpler check than Python, is it enough? - if importer_module.is_descendant_of(&imported_module) - || imported_module.is_descendant_of(&importer_module) - { - return Err(PyValueError::new_err("Modules have shared descendants.")); - } - } - Ok(self._graph.chain_exists( - &Module::new(importer.to_string()), - &Module::new(imported.to_string()), - as_packages, - )) + todo!() } #[pyo3(signature = (layers, containers))] @@ -315,215 +194,10 @@ impl GraphWrapper { layers: &Bound<'py, PyTuple>, containers: FxHashSet, ) -> PyResult> { - info!("Using Rust to find illegal dependencies."); - let levels = rustify_levels(layers); - - let dependencies = py.allow_threads(|| { - self._graph - .find_illegal_dependencies_for_layers(levels, containers) - }); - match dependencies { - Ok(dependencies) => _convert_dependencies_to_python(py, &dependencies), - Err(error) => Err(NoSuchContainer::new_err(format!( - "Container {} does not exist.", - error.container - ))), - } - } - pub fn clone(&self) -> GraphWrapper { - GraphWrapper { - _graph: self._graph.clone(), - } - } -} - -fn rustify_levels<'a>(levels_python: &Bound<'a, PyTuple>) -> Vec { - let mut rust_levels: Vec = vec![]; - for level_python in levels_python.into_iter() { - let level_dict = level_python.downcast::().unwrap(); - let layers: FxHashSet = level_dict - .get_item("layers") - .unwrap() - .unwrap() - .extract() - .unwrap(); - - let independent: bool = level_dict - .get_item("independent") - .unwrap() - .unwrap() - .extract() - .unwrap(); - rust_levels.push(Level { - independent, - layers: layers.into_iter().collect(), - }); + todo!() } - rust_levels -} - -fn _convert_dependencies_to_python<'py>( - py: Python<'py>, - dependencies: &Vec, -) -> PyResult> { - let mut python_dependencies: Vec> = vec![]; - - for rust_dependency in dependencies { - let python_dependency = PyDict::new(py); - python_dependency.set_item("imported", &rust_dependency.imported.name)?; - python_dependency.set_item("importer", &rust_dependency.importer.name)?; - let mut python_routes: Vec> = vec![]; - for rust_route in &rust_dependency.routes { - let route = PyDict::new(py); - let heads: Vec> = rust_route - .heads - .iter() - .map(|module| PyString::new(py, &module.name)) - .collect(); - route.set_item("heads", PyFrozenSet::new(py, &heads)?)?; - let middle: Vec> = rust_route - .middle - .iter() - .map(|module| PyString::new(py, &module.name)) - .collect(); - route.set_item("middle", PyTuple::new(py, &middle)?)?; - let tails: Vec> = rust_route - .tails - .iter() - .map(|module| PyString::new(py, &module.name)) - .collect(); - route.set_item("tails", PyFrozenSet::new(py, &tails)?)?; - python_routes.push(route); - } - - python_dependency.set_item("routes", PyTuple::new(py, python_routes)?)?; - python_dependencies.push(python_dependency) - } - - PyTuple::new(py, python_dependencies) -} - -#[cfg(test)] -mod tests { - use super::*; - - // 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![ - pydict! (py, { - "independent" => true, - "layers" => FxHashSet::from_iter(["high"]), - }), - pydict! (py, { - "independent" => true, - "layers" => FxHashSet::from_iter(["medium"]), - }), - pydict! (py, { - "independent" => true, - "layers" => FxHashSet::from_iter(["low"]), - }), - ]; - let python_levels = PyTuple::new(py, elements)?; - - let result = rustify_levels(&python_levels); - - assert_eq!( - result, - vec![ - Level { - independent: true, - layers: vec!["high".to_string()] - }, - Level { - independent: true, - layers: vec!["medium".to_string()] - }, - Level { - independent: true, - layers: vec!["low".to_string()] - } - ] - ); - - Ok(()) - }) - .unwrap(); - } - - #[test] - fn test_rustify_levels_sibling_layers() { - pyo3::prepare_freethreaded_python(); - Python::with_gil(|py| -> PyResult<()> { - let elements = vec![ - pydict! (py, { - "independent" => true, - "layers" => FxHashSet::from_iter(["high"]), - }), - pydict! (py, { - "independent" => true, - "layers" => FxHashSet::from_iter(["blue", "green", "orange"]), - }), - pydict! (py, { - "independent" => false, - "layers" => FxHashSet::from_iter(["red", "yellow"]), - }), - pydict! (py, { - "independent" => true, - "layers" => FxHashSet::from_iter(["low"]), - }), - ]; - let python_levels = PyTuple::new(py, elements)?; - - let mut result = rustify_levels(&python_levels); - - for level in &mut result { - level.layers.sort(); - } - assert_eq!( - result, - vec![ - Level { - independent: true, - layers: vec!["high".to_string()] - }, - Level { - independent: true, - layers: vec![ - "blue".to_string(), - "green".to_string(), - "orange".to_string() - ] - }, - Level { - independent: false, - layers: vec!["red".to_string(), "yellow".to_string()] - }, - Level { - independent: true, - layers: vec!["low".to_string()] - } - ] - ); - - Ok(()) - }) - .unwrap(); + pub fn clone(&self) -> GraphWrapper { + todo!() } } diff --git a/tests/benchmarking/test_benchmarking.py b/tests/benchmarking/test_benchmarking.py index 2079bb84..efceb9fa 100644 --- a/tests/benchmarking/test_benchmarking.py +++ b/tests/benchmarking/test_benchmarking.py @@ -67,6 +67,7 @@ def test_build_django_from_cache(benchmark): benchmark(fn) +@pytest.mark.skip # TODO(peter) def test_top_level_large_graph(large_graph, benchmark): benchmark( lambda: large_graph.find_illegal_dependencies_for_layers( @@ -76,6 +77,7 @@ def test_top_level_large_graph(large_graph, benchmark): ) +@pytest.mark.skip # TODO(peter) def test_deep_layers_large_graph(large_graph, benchmark): fn = lambda: large_graph.find_illegal_dependencies_for_layers(layers=DEEP_LAYERS) if hasattr(benchmark, "pedantic"): @@ -92,6 +94,7 @@ def test_deep_layers_large_graph(large_graph, benchmark): # behaviour hasn't changed. +@pytest.mark.skip # TODO(peter) def test_top_level_large_graph_result_check(large_graph): result = large_graph.find_illegal_dependencies_for_layers( layers=TOP_LEVEL_LAYERS, @@ -101,6 +104,7 @@ def test_top_level_large_graph_result_check(large_graph): assert result == set() +@pytest.mark.skip # TODO(peter) def test_deep_layers_large_graph_result_check(large_graph): result = large_graph.find_illegal_dependencies_for_layers(layers=DEEP_LAYERS) assert result == {