Skip to content

Commit 6508bc0

Browse files
committed
improved test coverage
1 parent 92f7fc2 commit 6508bc0

6 files changed

Lines changed: 250 additions & 0 deletions

File tree

tests/cli/test_cli_helpers.py

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
from typing import Any
2+
3+
from ngraph import cli as cli_mod
4+
5+
6+
class DummyStep:
7+
def __init__(self) -> None:
8+
self.name = "x"
9+
self.src_path = "A/*"
10+
self.dst_regex = "B.*"
11+
self.empty = ""
12+
self._private = "ignore"
13+
self.count = 3
14+
15+
16+
class DummyNet:
17+
def __init__(self) -> None:
18+
self.calls: list[str] = []
19+
20+
def select_node_groups_by_path(self, pattern: str) -> dict[str, list[Any]]:
21+
self.calls.append(pattern)
22+
if pattern == "ERR":
23+
raise RuntimeError("boom")
24+
25+
# Return two labeled groups with simple objects exposing .disabled
26+
class N:
27+
def __init__(self, disabled: bool) -> None:
28+
self.disabled = disabled
29+
30+
return {"G1": [N(False), N(True)], "G2": [N(False)]}
31+
32+
33+
def test_format_table_and_plural() -> None:
34+
table = cli_mod._format_table(
35+
["H1", "H2"], [["abc", "1"], ["defghi", "2"]], max_col_width=5
36+
)
37+
assert "H1" in table and "H2" in table
38+
# Ensure clipping with ASCII ellipsis (max_col_width=5 -> keep 2 chars + '...')
39+
assert "de..." in table
40+
41+
assert cli_mod._plural(1, "node") == "node"
42+
assert cli_mod._plural(2, "node") == "nodes"
43+
assert cli_mod._plural(2, "node", "vertices") == "vertices"
44+
45+
46+
def test_collect_and_summarize_node_matches() -> None:
47+
step = DummyStep()
48+
net = DummyNet()
49+
summary = cli_mod._summarize_node_matches(step, net)
50+
# Only *_path and *_regex fields considered
51+
assert set(summary.keys()) == {"src_path", "dst_regex"}
52+
# Each entry should include expected keys
53+
for v in summary.values():
54+
assert set(v.keys()) >= {
55+
"pattern",
56+
"groups",
57+
"nodes",
58+
"enabled_nodes",
59+
"labels",
60+
}
61+
assert v["groups"] == 2
62+
assert v["nodes"] == 3
63+
assert v["enabled_nodes"] == 2
64+
65+
66+
def test_summarize_pattern_error_path() -> None:
67+
net = DummyNet()
68+
out = cli_mod._summarize_pattern("ERR", net)
69+
assert out["pattern"] == "ERR"
70+
assert "error" in out
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
def test_import_failure_manager_helper_modules() -> None:
2+
import ngraph.failure.manager.aggregate as agg
3+
import ngraph.failure.manager.enumerate as enum
4+
import ngraph.failure.manager.simulate as sim
5+
6+
assert isinstance(agg.__doc__, str)
7+
assert isinstance(enum.__doc__, str)
8+
assert isinstance(sim.__doc__, str)
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from typing import Any
2+
3+
import pytest
4+
5+
from ngraph.failure.conditions import (
6+
FailureCondition,
7+
evaluate_condition,
8+
evaluate_conditions,
9+
)
10+
11+
12+
class TestEvaluateCondition:
13+
def test_equality_and_inequality(self) -> None:
14+
attrs = {"x": 5, "y": "abc"}
15+
assert evaluate_condition(attrs, FailureCondition("x", "==", 5)) is True
16+
assert evaluate_condition(attrs, FailureCondition("x", "!=", 6)) is True
17+
assert evaluate_condition(attrs, FailureCondition("y", "==", "abc")) is True
18+
19+
def test_ordering_with_none_guard(self) -> None:
20+
attrs = {"a": 3, "b": None}
21+
assert evaluate_condition(attrs, FailureCondition("a", ">", 2)) is True
22+
assert evaluate_condition(attrs, FailureCondition("a", ">=", 3)) is True
23+
assert evaluate_condition(attrs, FailureCondition("a", "<", 10)) is True
24+
assert evaluate_condition(attrs, FailureCondition("a", "<=", 3)) is True
25+
# None comparisons must return False rather than raising
26+
assert evaluate_condition(attrs, FailureCondition("b", ">", 0)) is False
27+
assert evaluate_condition(attrs, FailureCondition("missing", "<", 0)) is False
28+
29+
def test_contains_and_not_contains(self) -> None:
30+
attrs = {"s": "hello", "l": [1, 2, 3], "n": None, "i": 123}
31+
assert (
32+
evaluate_condition(attrs, FailureCondition("s", "contains", "ell")) is True
33+
)
34+
assert evaluate_condition(attrs, FailureCondition("l", "contains", 2)) is True
35+
assert (
36+
evaluate_condition(attrs, FailureCondition("s", "not_contains", "xyz"))
37+
is True
38+
)
39+
# None yields False for contains and True for not_contains
40+
assert evaluate_condition(attrs, FailureCondition("n", "contains", 1)) is False
41+
assert (
42+
evaluate_condition(attrs, FailureCondition("n", "not_contains", 1)) is True
43+
)
44+
# Non-iterable must not raise
45+
assert evaluate_condition(attrs, FailureCondition("i", "contains", 1)) is False
46+
assert (
47+
evaluate_condition(attrs, FailureCondition("i", "not_contains", 1)) is True
48+
)
49+
50+
def test_any_value_and_no_value(self) -> None:
51+
attrs: dict[str, Any] = {"p": 0, "q": None}
52+
assert evaluate_condition(attrs, FailureCondition("p", "any_value")) is True
53+
assert evaluate_condition(attrs, FailureCondition("q", "any_value")) is True
54+
assert (
55+
evaluate_condition(attrs, FailureCondition("missing", "any_value")) is False
56+
)
57+
assert (
58+
evaluate_condition(attrs, FailureCondition("missing", "no_value")) is True
59+
)
60+
assert evaluate_condition(attrs, FailureCondition("q", "no_value")) is True
61+
assert evaluate_condition(attrs, FailureCondition("p", "no_value")) is False
62+
63+
def test_unsupported_operator_raises(self) -> None:
64+
with pytest.raises(ValueError, match="Unsupported operator"):
65+
evaluate_condition({}, FailureCondition("x", "bad"))
66+
67+
68+
class TestEvaluateConditions:
69+
def test_and_or_logic(self) -> None:
70+
attrs = {"x": 10, "y": "abc"}
71+
conds = [FailureCondition("x", ">", 5), FailureCondition("y", "==", "abc")]
72+
assert evaluate_conditions(attrs, conds, "and") is True
73+
assert evaluate_conditions(attrs, conds, "or") is True
74+
75+
conds2 = [FailureCondition("x", "<", 5), FailureCondition("y", "!=", "abc")]
76+
assert evaluate_conditions(attrs, conds2, "and") is False
77+
assert evaluate_conditions(attrs, conds2, "or") is False
78+
79+
def test_unsupported_logic(self) -> None:
80+
with pytest.raises(ValueError, match="Unsupported logic"):
81+
evaluate_conditions({}, [], "xor")
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
def test_import_profiling_reporter_module() -> None:
2+
# Module is a placeholder; ensure it imports
3+
import ngraph.profiling.reporter as reporter
4+
5+
assert hasattr(reporter, "__doc__")
6+
# Some environments may drop docstrings when -OO; only assert attribute exists

tests/solver/test_helpers_smoke.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
def test_import_solver_helpers_module() -> None:
2+
import ngraph.solver.helpers as helpers
3+
4+
assert hasattr(helpers, "__doc__")
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
from typing import Any, Dict
2+
3+
import pandas as pd
4+
import pytest
5+
6+
from ngraph.workflow.analysis.placement_matrix import PlacementMatrixAnalyzer
7+
8+
9+
class TestPlacementMatrixAnalyzer:
10+
def test_analyze_requires_step_name(self) -> None:
11+
analyzer = PlacementMatrixAnalyzer()
12+
with pytest.raises(ValueError, match="step_name required"):
13+
analyzer.analyze({}, step_name=None) # type: ignore[arg-type]
14+
15+
def test_analyze_no_envelopes(self) -> None:
16+
analyzer = PlacementMatrixAnalyzer()
17+
results: Dict[str, Dict[str, Any]] = {"step": {"placement_envelopes": {}}}
18+
with pytest.raises(ValueError, match="No placement envelope data"):
19+
analyzer.analyze(results, step_name="step")
20+
21+
def test_extract_filter_and_analyze_happy_path(self) -> None:
22+
analyzer = PlacementMatrixAnalyzer()
23+
# Mixed-quality input: some entries missing fields must be ignored
24+
envs = {
25+
"A->B|prio=0": {"src": "A", "dst": "B", "priority": 0, "mean": 0.8},
26+
"A->C|prio=1": {"source": "A", "sink": "C", "priority": 1, "mean": 0.5},
27+
# Invalid entries below should be skipped by _extract_matrix_data
28+
"bad1": {"src": "A", "dst": None, "mean": 0.1},
29+
"bad2": {"src": None, "dst": "B", "mean": 0.2},
30+
"bad3": {"src": "X", "dst": "Y", "priority": 2},
31+
"bad4": 42,
32+
}
33+
results = {"pm": {"placement_envelopes": envs}}
34+
35+
out = analyzer.analyze(results, step_name="pm")
36+
assert out["status"] == "success"
37+
assert out["step_name"] == "pm"
38+
39+
matrix_data = out["matrix_data"]
40+
# Only two valid rows should remain
41+
assert isinstance(matrix_data, list)
42+
assert len(matrix_data) == 2
43+
# Ensure schema
44+
row0 = matrix_data[0]
45+
for key in ("source", "destination", "ratio", "flow_path", "priority"):
46+
assert key in row0
47+
48+
# Combined matrix should have sources as index and destinations as columns
49+
pmatrix: pd.DataFrame = out["placement_matrix"]
50+
assert set(pmatrix.index) == {"A"}
51+
assert set(pmatrix.columns) == {"B", "C"}
52+
assert pytest.approx(pmatrix.loc["A", "B"], rel=1e-9) == 0.8
53+
assert pytest.approx(pmatrix.loc["A", "C"], rel=1e-9) == 0.5
54+
55+
# Per-priority matrices present for priorities 0 and 1
56+
by_prio: Dict[int, pd.DataFrame] = out["placement_matrices"]
57+
assert set(by_prio.keys()) == {0, 1}
58+
assert pytest.approx(by_prio[0].loc["A", "B"], rel=1e-9) == 0.8
59+
60+
# Statistics computed with non-zero enforcement
61+
stats: Dict[str, Any] = out["statistics"]
62+
assert stats["has_data"] is True
63+
assert stats["ratio_min"] <= stats["ratio_mean"] <= stats["ratio_max"]
64+
assert stats["num_sources"] == 1
65+
assert stats["num_destinations"] == 2
66+
67+
def test_calculate_statistics_empty_returns_flag(self) -> None:
68+
analyzer = PlacementMatrixAnalyzer()
69+
# Empty matrix yields has_data=False
70+
empty_matrix = pd.DataFrame()
71+
assert analyzer._calculate_statistics(empty_matrix) == {"has_data": False}
72+
73+
def test_analyze_and_display_step_raises_and_prints_on_error(
74+
self, capsys: Any
75+
) -> None:
76+
analyzer = PlacementMatrixAnalyzer()
77+
with pytest.raises(ValueError):
78+
analyzer.analyze_and_display_step({}, step_name="missing")
79+
captured = capsys.readouterr()
80+
# Should include the error banner
81+
assert "❌ Placement matrix analysis failed" in captured.out

0 commit comments

Comments
 (0)