Skip to content

Commit 99a09d4

Browse files
fix(test): resolve unit test failures from route_props refactoring
Fix test failures caused by recent routing architecture refactoring that introduced route_props for storing routing algorithm results. Also fix config tests to match hybrid nested/flat configuration architecture and remove emoji expectations per project guidelines. Changes include: - Add default values in network_analysis.get_link_usage_summary - Update factory tests to mock route_props.paths_matrix - Fix config_setup tests for nested optional options - Update CLI tests to remove emoji expectations (GUI and train) - Fix schema tests to match current required options structure - Complete route_props integration in routing algorithms Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 66faf5e commit 99a09d4

25 files changed

Lines changed: 182 additions & 176 deletions

fusion/analysis/network_analysis.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ def get_link_usage_summary(network_spectrum: dict) -> dict[str, dict[str, Any]]:
4848

4949
processed_links.add(link_key)
5050
usage_summary[link_key] = {
51-
"usage_count": link_data.get("usage_count"),
52-
"throughput": link_data.get("throughput"),
51+
"usage_count": link_data.get("usage_count", 0),
52+
"throughput": link_data.get("throughput", 0),
5353
"link_num": link_data.get("link_num"),
5454
}
5555

fusion/cli/config_setup.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,8 @@ def load_config(
250250
)
251251
_process_optional_options(config, config_dict, OPTIONAL_OPTIONS_DICT, args_dict)
252252

253-
# Mirror routing_settings and spectrum_settings to root for backward compatibility
253+
# Mirror routing_settings and spectrum_settings to root for backward
254+
# compatibility
254255
_mirror_nested_to_flat(config_dict[DEFAULT_THREAD_NAME])
255256

256257
thread_sections = [s for s in config.sections() if s != REQUIRED_SECTION]
@@ -339,8 +340,9 @@ def _mirror_nested_to_flat(config: dict[str, Any]) -> None:
339340
"""
340341
Mirror values from nested sections to root level for backward compatibility.
341342
342-
This allows newer code to use nested structure (engine_props["routing_settings"]["k_paths"])
343-
while older code can still use flat structure (engine_props["k_paths"]).
343+
This allows newer code to use nested structure like
344+
engine_props["routing_settings"]["k_paths"] while older code can still
345+
use flat structure (engine_props["k_paths"]).
344346
345347
:param config: Configuration dictionary to update in-place
346348
:type config: dict[str, Any]

fusion/cli/tests/test_config_setup.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ def test_process_optional_options_with_valid_config(
161161
) -> None:
162162
"""Test _process_optional_options processes optional options correctly."""
163163
config_dict: dict[str, dict[str, Any]] = {DEFAULT_THREAD_NAME: {}}
164-
optional_dict = {"sim": {"optional_test": str}}
164+
# Use general_settings section which gets flattened for backward compatibility
165+
optional_dict = {"general_settings": {"optional_test": str}}
165166
mock_config_parser.__getitem__.return_value = {"optional_test": "test_value"}
166167

167168
_process_optional_options(
@@ -206,7 +207,7 @@ def test_validate_config_structure_with_missing_section_raises_error(
206207

207208
with pytest.raises(ConfigParseError) as exc_info:
208209
_validate_config_structure(mock_config_parser)
209-
assert f"Missing '{REQUIRED_SECTION}' section" in str(exc_info.value)
210+
assert f"Missing required '{REQUIRED_SECTION}' section" in str(exc_info.value)
210211

211212

212213
class TestReadConfigFile:

fusion/cli/tests/test_run_gui.py

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,8 @@ def test_gui_main_prints_user_friendly_messages_on_error(
161161
# Verify print was called with user-friendly messages
162162
assert mock_print.called
163163
print_calls = [call[0][0] for call in mock_print.call_args_list]
164-
assert any("" in call for call in print_calls)
165-
assert any("💡" in call for call in print_calls)
164+
assert any("Missing GUI dependencies" in call for call in print_calls)
165+
assert any("pip install" in call for call in print_calls)
166166

167167
@patch("fusion.cli.run_gui.launch_gui_pipeline")
168168
@patch("fusion.cli.run_gui.create_gui_argument_parser")
@@ -177,7 +177,7 @@ def test_gui_main_prints_interrupt_message(
177177
gui_main()
178178

179179
print_calls = [call[0][0] for call in mock_print.call_args_list]
180-
assert any("🛑" in call for call in print_calls)
180+
assert any("interrupted" in call.lower() for call in print_calls)
181181

182182
@patch("fusion.cli.run_gui.launch_gui_pipeline")
183183
@patch("fusion.cli.run_gui.create_gui_argument_parser")
@@ -192,8 +192,11 @@ def test_gui_main_provides_helpful_suggestions_for_display_errors(
192192
gui_main()
193193

194194
print_calls = [call[0][0] for call in mock_print.call_args_list]
195-
# Should contain helpful suggestions
196-
assert any("💡" in call for call in print_calls)
195+
# Should contain helpful suggestions about display or framework
196+
assert any(
197+
"display" in call.lower() or "framework" in call.lower()
198+
for call in print_calls
199+
)
197200

198201
@patch("fusion.cli.run_gui.launch_gui_pipeline")
199202
@patch("fusion.cli.run_gui.create_gui_argument_parser")
@@ -210,6 +213,5 @@ def test_gui_main_provides_helpful_suggestions_for_import_errors(
210213
print_calls = [call[0][0] for call in mock_print.call_args_list]
211214
# Should contain installation suggestions
212215
assert any(
213-
"💡" in call and ("install" in call.lower() or "pip" in call.lower())
214-
for call in print_calls
216+
"install" in call.lower() or "pip" in call.lower() for call in print_calls
215217
)

fusion/cli/tests/test_run_train.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,10 @@ def test_train_main_prints_user_friendly_messages_on_error(
177177
# Verify print was called with user-friendly messages
178178
assert mock_print.called
179179
print_calls = [call[0][0] for call in mock_print.call_args_list]
180-
assert any("❌" in call for call in print_calls)
181-
assert any("💡" in call for call in print_calls)
180+
assert any(
181+
"dependencies" in call.lower() or "module" in call.lower()
182+
for call in print_calls
183+
)
182184

183185
@patch("fusion.cli.run_train.run_training_pipeline")
184186
@patch("fusion.cli.run_train.create_training_argument_parser")
@@ -193,5 +195,4 @@ def test_train_main_prints_interrupt_message(
193195
train_main()
194196

195197
print_calls = [call[0][0] for call in mock_print.call_args_list]
196-
assert any("🛑" in call for call in print_calls)
197-
assert any("💾" in call for call in print_calls)
198+
assert any("interrupted" in call.lower() for call in print_calls)

fusion/configs/tests/test_schema.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,8 +94,6 @@ def test_general_settings_required_options(self) -> None:
9494
"fixed_grid",
9595
"pre_calc_mod_selection",
9696
"max_segments",
97-
"route_method",
98-
"allocation_method",
9997
"save_snapshots",
10098
"snapshot_step",
10199
"print_step",

fusion/interfaces/factory.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,10 +284,15 @@ def _process_pipeline(
284284
if not hasattr(self.routing_algorithm, "route"):
285285
result["failure_reason"] = "Routing algorithm missing route method"
286286
return
287-
path = self.routing_algorithm.route(source, destination, request)
288-
if not path:
287+
self.routing_algorithm.route(source, destination, request)
288+
289+
# Check if routing found a path via route_props
290+
if not self.routing_algorithm.route_props.paths_matrix:
289291
result["failure_reason"] = "No path found"
290292
return
293+
294+
# Use the first path from paths_matrix
295+
path = self.routing_algorithm.route_props.paths_matrix[0]
291296
result["path"] = path
292297

293298
# Step 2: Spectrum Assignment

fusion/interfaces/router.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from abc import ABC, abstractmethod
66
from typing import Any
77

8-
from fusion.core.properties import SDNProps
8+
from fusion.core.properties import RoutingProps, SDNProps
99

1010

1111
class AbstractRoutingAlgorithm(ABC):
@@ -14,6 +14,10 @@ class AbstractRoutingAlgorithm(ABC):
1414
1515
This interface defines the contract that all routing algorithms must follow
1616
to ensure compatibility with the FUSION simulation framework.
17+
18+
Most routing algorithms store results in route_props (paths_matrix,
19+
modulation_formats_matrix, weights_list). Special algorithms like
20+
OnePlusOneProtection may use alternative storage mechanisms.
1721
"""
1822

1923
def __init__(self, engine_props: dict, sdn_props: SDNProps):
@@ -27,6 +31,9 @@ def __init__(self, engine_props: dict, sdn_props: SDNProps):
2731
"""
2832
self.engine_props = engine_props
2933
self.sdn_props = sdn_props
34+
# Most routing algorithms use route_props to store results
35+
# Subclasses can override this if they have different storage needs
36+
self.route_props: RoutingProps = RoutingProps()
3037

3138
@property
3239
@abstractmethod
@@ -61,19 +68,20 @@ def validate_environment(self, topology: Any) -> bool:
6168
"""
6269

6370
@abstractmethod
64-
def route(self, source: Any, destination: Any, request: Any) -> list[Any] | None:
71+
def route(self, source: Any, destination: Any, request: Any) -> None:
6572
"""
6673
Find a route from source to destination for the given request.
6774
75+
Results are stored in the algorithm's route_props attribute (paths_matrix,
76+
modulation_formats_matrix, weights_list). This method does not return a value;
77+
consumers should access route_props.paths_matrix to retrieve computed paths.
78+
6879
:param source: Source node identifier
6980
:type source: Any
7081
:param destination: Destination node identifier
7182
:type destination: Any
7283
:param request: Request object containing traffic demand details
7384
:type request: Any
74-
:return: List representing the path from source to destination,
75-
or None if no path found
76-
:rtype: Optional[List[Any]]
7785
"""
7886

7987
@abstractmethod

fusion/interfaces/tests/test_factory.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,10 @@ def _create_mock_pipeline(self) -> SimulationPipeline:
288288
pipeline.spectrum_algorithm = Mock(spec=AbstractSpectrumAssigner)
289289
pipeline.snr_algorithm = Mock(spec=AbstractSNRMeasurer)
290290

291+
# Setup route_props for routing algorithm
292+
pipeline.routing_algorithm.route_props = Mock()
293+
pipeline.routing_algorithm.route_props.paths_matrix = []
294+
291295
# Setup get_metrics
292296
pipeline.routing_algorithm.get_metrics.return_value = {}
293297
pipeline.spectrum_algorithm.get_metrics.return_value = {}
@@ -299,7 +303,8 @@ def test_process_request_with_successful_allocation(self) -> None:
299303
"""Test process_request with successful spectrum allocation."""
300304
# Arrange
301305
pipeline = self._create_mock_pipeline()
302-
pipeline.routing_algorithm.route.return_value = [1, 2, 3] # type: ignore[attr-defined]
306+
# Setup paths_matrix to contain the path found by routing
307+
pipeline.routing_algorithm.route_props.paths_matrix = [[1, 2, 3]]
303308
pipeline.spectrum_algorithm.assign.return_value = { # type: ignore[attr-defined]
304309
"start_slot": 0,
305310
"end_slot": 5,
@@ -356,6 +361,9 @@ def _create_mock_pipeline(self) -> SimulationPipeline:
356361
pipeline.routing_algorithm = Mock(spec=AbstractRoutingAlgorithm)
357362
pipeline.spectrum_algorithm = Mock(spec=AbstractSpectrumAssigner)
358363
pipeline.snr_algorithm = Mock(spec=AbstractSNRMeasurer)
364+
# Setup route_props for routing algorithm
365+
pipeline.routing_algorithm.route_props = Mock()
366+
pipeline.routing_algorithm.route_props.paths_matrix = []
359367
pipeline.routing_algorithm.get_metrics.return_value = {}
360368
pipeline.spectrum_algorithm.get_metrics.return_value = {}
361369
pipeline.snr_algorithm.get_metrics.return_value = {}
@@ -365,7 +373,8 @@ def test_process_request_when_no_path_found(self) -> None:
365373
"""Test process_request when no path is found."""
366374
# Arrange
367375
pipeline = self._create_mock_pipeline()
368-
pipeline.routing_algorithm.route.return_value = None # type: ignore[attr-defined]
376+
# Empty paths_matrix indicates no path was found
377+
pipeline.routing_algorithm.route_props.paths_matrix = []
369378
request = Mock()
370379

371380
# Act
@@ -380,7 +389,7 @@ def test_process_request_when_no_spectrum_available(self) -> None:
380389
"""Test process_request when no spectrum is available."""
381390
# Arrange
382391
pipeline = self._create_mock_pipeline()
383-
pipeline.routing_algorithm.route.return_value = [1, 2, 3] # type: ignore[attr-defined]
392+
pipeline.routing_algorithm.route_props.paths_matrix = [[1, 2, 3]]
384393
pipeline.spectrum_algorithm.assign.return_value = None # type: ignore[attr-defined]
385394
request = Mock()
386395

@@ -395,7 +404,7 @@ def test_process_request_when_snr_too_low(self) -> None:
395404
"""Test process_request when SNR is below threshold."""
396405
# Arrange
397406
pipeline = self._create_mock_pipeline()
398-
pipeline.routing_algorithm.route.return_value = [1, 2, 3] # type: ignore[attr-defined]
407+
pipeline.routing_algorithm.route_props.paths_matrix = [[1, 2, 3]]
399408
pipeline.spectrum_algorithm.assign.return_value = { # type: ignore[attr-defined]
400409
"start_slot": 0,
401410
"end_slot": 5,
@@ -428,7 +437,7 @@ def test_process_request_when_allocation_fails(self) -> None:
428437
"""Test process_request when spectrum allocation fails."""
429438
# Arrange
430439
pipeline = self._create_mock_pipeline()
431-
pipeline.routing_algorithm.route.return_value = [1, 2, 3] # type: ignore[attr-defined]
440+
pipeline.routing_algorithm.route_props.paths_matrix = [[1, 2, 3]]
432441
pipeline.spectrum_algorithm.assign.return_value = { # type: ignore[attr-defined]
433442
"start_slot": 0,
434443
"end_slot": 5,
@@ -660,7 +669,8 @@ def test_pipeline_handles_missing_topology_in_engine_props(self) -> None:
660669
}
661670
pipeline = SimulationPipeline(config)
662671
pipeline.routing_algorithm = Mock(spec=AbstractRoutingAlgorithm)
663-
pipeline.routing_algorithm.route.return_value = [1, 2]
672+
pipeline.routing_algorithm.route_props = Mock()
673+
pipeline.routing_algorithm.route_props.paths_matrix = [[1, 2]]
664674
pipeline.routing_algorithm.get_metrics.return_value = {}
665675
pipeline.spectrum_algorithm = Mock(spec=AbstractSpectrumAssigner)
666676
pipeline.spectrum_algorithm.assign.return_value = {

fusion/interfaces/tests/test_router.py

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,8 @@ def supported_topologies(self) -> list[str]:
162162
def validate_environment(self, topology: Any) -> bool:
163163
return True
164164

165-
def route(
166-
self, source: Any, destination: Any, request: Any
167-
) -> list[Any] | None:
168-
return None
165+
def route(self, source: Any, destination: Any, request: Any) -> None:
166+
pass
169167

170168
def get_paths(
171169
self, source: Any, destination: Any, k: int = 1
@@ -213,10 +211,8 @@ def supported_topologies(self) -> list[str]:
213211
def validate_environment(self, topology: Any) -> bool:
214212
return True
215213

216-
def route(
217-
self, source: Any, destination: Any, request: Any
218-
) -> list[Any] | None:
219-
return [source, destination]
214+
def route(self, source: Any, destination: Any, request: Any) -> None:
215+
self.route_props.paths_matrix = [[source, destination]]
220216

221217
def get_paths(
222218
self, source: Any, destination: Any, k: int = 1
@@ -283,10 +279,8 @@ def supported_topologies(self) -> list[str]:
283279
def validate_environment(self, topology: Any) -> bool:
284280
return True
285281

286-
def route(
287-
self, source: Any, destination: Any, request: Any
288-
) -> list[Any] | None:
289-
return None
282+
def route(self, source: Any, destination: Any, request: Any) -> None:
283+
pass
290284

291285
def get_paths(
292286
self, source: Any, destination: Any, k: int = 1
@@ -369,10 +363,8 @@ def supported_topologies(self) -> list[str]:
369363
def validate_environment(self, topology: Any) -> bool:
370364
return True
371365

372-
def route(
373-
self, source: Any, destination: Any, request: Any
374-
) -> list[Any] | None:
375-
return None
366+
def route(self, source: Any, destination: Any, request: Any) -> None:
367+
pass
376368

377369
def get_paths(
378370
self, source: Any, destination: Any, k: int = 1
@@ -388,10 +380,10 @@ def get_metrics(self) -> dict[str, Any]:
388380
algo = ConcreteRoutingAlgorithm({}, Mock())
389381

390382
# Act
391-
result = algo.route(1, 2, Mock())
383+
algo.route(1, 2, Mock())
392384

393-
# Assert
394-
assert result is None
385+
# Assert - route() returns None, just verify it doesn't raise
386+
assert True
395387

396388
def test_get_paths_can_return_empty_list(self) -> None:
397389
"""Test get_paths method can return empty list when no paths exist."""
@@ -409,10 +401,8 @@ def supported_topologies(self) -> list[str]:
409401
def validate_environment(self, topology: Any) -> bool:
410402
return True
411403

412-
def route(
413-
self, source: Any, destination: Any, request: Any
414-
) -> list[Any] | None:
415-
return None
404+
def route(self, source: Any, destination: Any, request: Any) -> None:
405+
pass
416406

417407
def get_paths(
418408
self, source: Any, destination: Any, k: int = 1
@@ -449,10 +439,8 @@ def supported_topologies(self) -> list[str]:
449439
def validate_environment(self, topology: Any) -> bool:
450440
return False
451441

452-
def route(
453-
self, source: Any, destination: Any, request: Any
454-
) -> list[Any] | None:
455-
return None
442+
def route(self, source: Any, destination: Any, request: Any) -> None:
443+
pass
456444

457445
def get_paths(
458446
self, source: Any, destination: Any, k: int = 1

0 commit comments

Comments
 (0)