Skip to content

Commit

Permalink
Change: dependency_graph checks result structure and output
Browse files Browse the repository at this point in the history
  • Loading branch information
NiklasHargarter committed Jan 20, 2025
1 parent 63c0f64 commit 8dc409b
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 58 deletions.
2 changes: 1 addition & 1 deletion tests/standalone_plugins/test_dependency_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,4 @@ def test_full_run(self):
patch.object(sys, "argv", test_args),
):
return_code = main()
self.assertEqual(return_code, 4)
self.assertEqual(return_code, 1)
164 changes: 107 additions & 57 deletions troubadix/standalone_plugins/dependency_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import re
import sys
from argparse import ArgumentParser, Namespace
from dataclasses import dataclass
from dataclasses import dataclass, field
from pathlib import Path

import networkx as nx
Expand Down Expand Up @@ -44,6 +44,51 @@ class Script:
deprecated: bool


@dataclass
class Result:
name: str
warnings: list[str] = field(default_factory=list)
errors: list[str] = field(default_factory=list)

def has_errors(self) -> bool:
return bool(self.errors)

def has_warnings(self) -> bool:
return bool(self.warnings)


class Reporter:
def __init__(self, verbosity) -> None:
self.verbosity = verbosity

def report(self, results: list[Result]):
for result in results:
if self.verbosity >= 2:
self.print_statistic(result)
self.print_divider("-")

Check warning on line 68 in troubadix/standalone_plugins/dependency_graph.py

View check run for this annotation

Codecov / codecov/patch

troubadix/standalone_plugins/dependency_graph.py#L67-L68

Added lines #L67 - L68 were not covered by tests
if self.verbosity >= 1:
self.print_warnings(result)

Check warning on line 70 in troubadix/standalone_plugins/dependency_graph.py

View check run for this annotation

Codecov / codecov/patch

troubadix/standalone_plugins/dependency_graph.py#L70

Added line #L70 was not covered by tests
self.print_errors(result)
if self.verbosity >= 2:
self.print_divider("=")

Check warning on line 73 in troubadix/standalone_plugins/dependency_graph.py

View check run for this annotation

Codecov / codecov/patch

troubadix/standalone_plugins/dependency_graph.py#L73

Added line #L73 was not covered by tests

def print_divider(self, char="-", length=40):
print(char * length)

Check warning on line 76 in troubadix/standalone_plugins/dependency_graph.py

View check run for this annotation

Codecov / codecov/patch

troubadix/standalone_plugins/dependency_graph.py#L76

Added line #L76 was not covered by tests

def print_statistic(self, result: Result):
print(

Check warning on line 79 in troubadix/standalone_plugins/dependency_graph.py

View check run for this annotation

Codecov / codecov/patch

troubadix/standalone_plugins/dependency_graph.py#L79

Added line #L79 was not covered by tests
f"{result.name} - warnings: {len(result.warnings)}, errors: {len(result.errors)}"
)

def print_warnings(self, result: Result):
for warning in result.warnings:
print(f"warning: {warning}")

Check warning on line 85 in troubadix/standalone_plugins/dependency_graph.py

View check run for this annotation

Codecov / codecov/patch

troubadix/standalone_plugins/dependency_graph.py#L85

Added line #L85 was not covered by tests

def print_errors(self, result: Result):
for error in result.errors:
print(f"error: {error}")


def directory_type(string: str) -> Path:
directory_path = Path(string)
if not directory_path.is_dir():
Expand Down Expand Up @@ -71,6 +116,7 @@ def parse_args() -> Namespace:
default="WARNING",
help="Set the logging level (INFO, WARNING, ERROR)",
)
parser.add_argument("-v", "--verbose", action="count", default=0)
return parser.parse_args()


Expand Down Expand Up @@ -196,57 +242,60 @@ def create_graph(scripts: list[Script]):
return graph


def check_duplicates(scripts: list[Script]):
def check_duplicates(scripts: list[Script]) -> Result:
"""
checks for a script depending on a script multiple times
"""
warnings = []
for script in scripts:
dependencies = [dep for dep, _ in script.dependencies]
duplicates = {
dep for dep in dependencies if dependencies.count(dep) > 1
}
if duplicates:
logging.warning(
f"Duplicate dependencies in {script.name}: {', '.join(duplicates)}"
)
msg = f"Duplicate dependencies in {script.name}: {', '.join(duplicates)}"
warnings.append(msg)

return Result(name="check_duplicates", warnings=warnings)

def check_missing_dependencies(scripts: list[Script], graph: nx.DiGraph) -> int:

def check_missing_dependencies(
scripts: list[Script], graph: nx.DiGraph
) -> Result:
"""
Checks if any scripts that are depended on are missing from
the list of scripts created from the local file system,
logs the scripts dependending on the missing script
"""
errors = []
dependencies = {dep for script in scripts for dep, _ in script.dependencies}
script_names = {script.name for script in scripts}
missing_dependencies = dependencies - script_names
if not missing_dependencies:
return 0

for missing in missing_dependencies:
depending_scripts = graph.predecessors(missing)
logging.error(f"missing dependency file: {missing}:")
msg = f"missing dependency file: {missing}:"
for script in depending_scripts:
logging.info(f" - used by: {script}")
msg += f"\n - used by: {script}"
errors.append(msg)

return 1
return Result(name="missing_dependencies", errors=errors)


def check_cycles(graph) -> int:
def check_cycles(graph) -> Result:
"""
checks for cyclic dependencies
"""
if nx.is_directed_acyclic_graph(graph):
return 0
return Result(name="check_cycles")

Check warning on line 290 in troubadix/standalone_plugins/dependency_graph.py

View check run for this annotation

Codecov / codecov/patch

troubadix/standalone_plugins/dependency_graph.py#L290

Added line #L290 was not covered by tests

cyles = nx.simple_cycles(graph)
for cycle in cyles:
logging.error(f"cyclic dependency: {cycle}")
cycles = nx.simple_cycles(graph)

return 1
errors = [f"cyclic dependency: {cycle}" for cycle in cycles]
return Result(name="check_cycles", errors=errors)


def cross_feed_dependencies(graph, gated_status: bool):
def cross_feed_dependencies(graph, gated_status: bool) -> list[tuple[str, str]]:
"""
creates a list of script and dependency for scripts
in community feed that depend on scripts in enterprise folders
Expand All @@ -261,59 +310,50 @@ def cross_feed_dependencies(graph, gated_status: bool):
return cross_feed_dependencies


def check_cross_feed_dependecies(graph):
def check_cross_feed_dependecies(graph) -> Result:
"""
Checks if scripts in the community feed have dependencies to enterprise scripts,
and if they are contained within a gate.
"""
gated_cfd = cross_feed_dependencies(graph, gated_status=True)
for dependent, dependency in gated_cfd:
logging.info(
f"gated cross-feed-dependency: {dependent} depends on {dependency}"
)
warnings = [
f"gated cross-feed-dependency: {dependent} depends on {dependency}"
for dependent, dependency in gated_cfd
]

ungated_cfd = cross_feed_dependencies(graph, gated_status=False)
if not ungated_cfd:
return 0
for dependent, dependency in ungated_cfd:
logging.error(
f"ungated cross-feed-dependency: {dependent} depends on {dependency}"
)
errors = [
f"ungated cross-feed-dependency: {dependent} depends on {dependency}"
for dependent, dependency in ungated_cfd
]

return 1
return Result(
name="check_cross_feed_dependencies", warnings=warnings, errors=errors
)


def check_category_order(graph):
def check_category_order(graph) -> Result:
problematic_edges = [
(dependent, dependency)
for dependent, dependency in graph.edges()
if graph.nodes[dependent]["category"]
< graph.nodes[dependency].get("category", -1)
]

if not problematic_edges:
return 0
for dependent, dependency in problematic_edges:
logging.error(
"Not allowed category order."
f" {dependent} is higher in the execution order than {dependency}"
)
return 1
errors = [
f"{dependent} depends on {dependency} which has a lower category order"
for dependent, dependency in problematic_edges
]
return Result(name="check_category_order", errors=errors)


def check_deprecated_dependencies(graph) -> int:
deprecated_edges = [
(dependent, dependency)
def check_deprecated_dependencies(graph) -> Result:
errors = [
f"{dependent} depends on deprectated script {dependency}"
for dependent, dependency in graph.edges()
if graph.nodes[dependency].get("deprecated", False)
]
if not deprecated_edges:
return 0
for dependent, dependency in deprecated_edges:
logging.error(
f"Deprecated dependency: {dependent} depends on {dependency}"
)
return 1
return Result(name="check_deprecated_dependencies", errors=errors)


def main():
Expand All @@ -329,16 +369,26 @@ def main():
logging.info(f"nodes (scripts) in graph: {graph.number_of_nodes()}")
logging.info(f"edges (dependencies) in graph: {graph.number_of_edges()}")

failed = 0
results = [
check_duplicates(scripts),
check_missing_dependencies(scripts, graph),
check_cycles(graph),
check_cross_feed_dependecies(graph),
check_category_order(graph),
check_deprecated_dependencies(graph),
]
reporter = Reporter(args.verbose)
reporter.report(results)

check_duplicates(scripts)
failed += check_missing_dependencies(scripts, graph)
failed += check_cycles(graph)
failed += check_cross_feed_dependecies(graph)
failed += check_category_order(graph)
failed += check_deprecated_dependencies(graph)
has_errors = any(result.has_errors() for result in results)
has_warnings = any(result.has_warnings() for result in results)

return failed
if has_errors:
return 1
elif has_warnings:
return 2

Check warning on line 389 in troubadix/standalone_plugins/dependency_graph.py

View check run for this annotation

Codecov / codecov/patch

troubadix/standalone_plugins/dependency_graph.py#L389

Added line #L389 was not covered by tests
else:
return 0

Check warning on line 391 in troubadix/standalone_plugins/dependency_graph.py

View check run for this annotation

Codecov / codecov/patch

troubadix/standalone_plugins/dependency_graph.py#L391

Added line #L391 was not covered by tests


if __name__ == "__main__":
Expand Down

0 comments on commit 8dc409b

Please sign in to comment.