Skip to content

Commit 4ffa080

Browse files
committed
update tests and add new for post expedition report
1 parent 749f2f1 commit 4ffa080

File tree

1 file changed

+92
-18
lines changed

1 file changed

+92
-18
lines changed

tests/make_realistic/problems/test_simulator.py

Lines changed: 92 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
import json
2+
import random
23
from datetime import datetime, timedelta
34

4-
from virtualship.make_realistic.problems.scenarios import GeneralProblem
5+
from virtualship.instruments.types import InstrumentType
6+
from virtualship.make_realistic.problems.scenarios import (
7+
GeneralProblem,
8+
InstrumentProblem,
9+
)
510
from virtualship.make_realistic.problems.simulator import ProblemSimulator
611
from virtualship.models.expedition import (
712
Expedition,
@@ -11,21 +16,26 @@
1116
Waypoint,
1217
)
1318
from virtualship.models.location import Location
14-
from virtualship.utils import GENERAL_PROBLEM_REG
19+
from virtualship.utils import GENERAL_PROBLEM_REG, REPORT
1520

1621

1722
def _make_simple_expedition(
18-
num_waypoints: int = 2, distance_scale: float = 1.0
23+
num_waypoints: int = 2, distance_scale: float = 1.0, no_instruments: bool = False
1924
) -> Expedition:
25+
"""Func. rather than fixture to allow for configurability in different tests."""
2026
sample_datetime = datetime(2024, 1, 1, 0, 0, 0)
27+
instruments_non_underway = [inst for inst in InstrumentType if not inst.is_underway]
28+
2129
waypoints = []
2230
for i in range(num_waypoints):
2331
wp = Waypoint(
2432
location=Location(
2533
latitude=0.0 + i * distance_scale, longitude=0.0 + i * distance_scale
2634
),
2735
time=sample_datetime + timedelta(days=i),
28-
instrument=[], # ensure is list, not None
36+
instrument=[]
37+
if no_instruments
38+
else random.sample(instruments_non_underway, 3),
2939
)
3040
waypoints.append(wp)
3141

@@ -39,8 +49,9 @@ def _make_simple_expedition(
3949

4050
def test_select_problems_single_waypoint_returns_pre_departure(tmp_path):
4151
expedition = _make_simple_expedition(num_waypoints=1)
52+
instruments_in_expedition = expedition.get_instruments()
4253
simulator = ProblemSimulator(expedition, str(tmp_path))
43-
problems = simulator.select_problems(set(), prob_level=2)
54+
problems = simulator.select_problems(instruments_in_expedition, prob_level=2)
4455

4556
assert isinstance(problems, dict)
4657
assert len(problems["problem_class"]) == 1
@@ -51,11 +62,28 @@ def test_select_problems_single_waypoint_returns_pre_departure(tmp_path):
5162
assert getattr(problem_cls, "pre_departure", False) is True
5263

5364

65+
def test_no_instruments_no_instruments_problems(tmp_path):
66+
expedition = _make_simple_expedition(num_waypoints=2, no_instruments=True)
67+
instruments_in_expedition = expedition.get_instruments()
68+
assert len(instruments_in_expedition) == 0, "Expedition should have no instruments"
69+
70+
simulator = ProblemSimulator(expedition, str(tmp_path))
71+
problems = simulator.select_problems(instruments_in_expedition, prob_level=2)
72+
73+
has_instrument_problems = any(
74+
issubclass(cls, InstrumentProblem) for cls in problems["problem_class"]
75+
)
76+
assert not has_instrument_problems, (
77+
"Should not select instrument problems when no instruments are present"
78+
)
79+
80+
5481
def test_select_problems_prob_level_zero():
5582
expedition = _make_simple_expedition(num_waypoints=2)
83+
instruments_in_expedition = expedition.get_instruments()
5684
simulator = ProblemSimulator(expedition, ".")
5785

58-
problems = simulator.select_problems(set(), prob_level=0)
86+
problems = simulator.select_problems(instruments_in_expedition, prob_level=0)
5987
assert problems is None
6088

6189

@@ -64,10 +92,10 @@ def test_cache_and_load_selected_problems_roundtrip(tmp_path):
6492
simulator = ProblemSimulator(expedition, str(tmp_path))
6593

6694
# pick two general problems (registry should contain entries)
67-
cls1 = GENERAL_PROBLEM_REG[0]
68-
cls2 = GENERAL_PROBLEM_REG[1] if len(GENERAL_PROBLEM_REG) > 1 else cls1
95+
problem1 = GENERAL_PROBLEM_REG[0]
96+
problem2 = GENERAL_PROBLEM_REG[1] if len(GENERAL_PROBLEM_REG) > 1 else problem1
6997

70-
problems = {"problem_class": [cls1, cls2], "waypoint_i": [None, 0]}
98+
problems = {"problem_class": [problem1, problem2], "waypoint_i": [None, 0]}
7199

72100
sel_fpath = tmp_path / "subdir" / "selected_problems.json"
73101
simulator.cache_selected_problems(problems, str(sel_fpath))
@@ -89,11 +117,11 @@ def test_hash_to_json(tmp_path):
89117
expedition = _make_simple_expedition(num_waypoints=2)
90118
simulator = ProblemSimulator(expedition, str(tmp_path))
91119

92-
cls = GENERAL_PROBLEM_REG[0]
120+
any_problem = GENERAL_PROBLEM_REG[0]
93121

94122
hash_path = tmp_path / "problem_hash.json"
95123
simulator._hash_to_json(
96-
cls, "deadbeef", None, hash_path
124+
any_problem, "deadbeef", None, hash_path
97125
) # "deadbeef" as sub for hex in test
98126

99127
assert hash_path.exists()
@@ -107,18 +135,27 @@ def test_hash_to_json(tmp_path):
107135
def test_has_contingency_pre_departure(tmp_path):
108136
expedition = _make_simple_expedition(num_waypoints=2)
109137
simulator = ProblemSimulator(expedition, str(tmp_path))
110-
cls = GENERAL_PROBLEM_REG[0]
111138

112-
# _has_contingency should return False for pre-departure (None)
113-
assert simulator._has_contingency(cls, None) is False
139+
pre_departure_problem = next(
140+
gp for gp in GENERAL_PROBLEM_REG if getattr(gp, "pre_departure", False)
141+
)
142+
assert pre_departure_problem is not None, (
143+
"Need at least one pre-departure problem class in the general problem registry"
144+
)
145+
146+
# _has_contingency should return False for pre-departure (waypoint = None)
147+
assert simulator._has_contingency(pre_departure_problem, None) is False
114148

115149

116150
def test_select_problems_prob_levels(tmp_path):
117151
expedition = _make_simple_expedition(num_waypoints=3)
152+
instruments_in_expedition = expedition.get_instruments()
118153
simulator = ProblemSimulator(expedition, str(tmp_path))
119154

120155
for level in range(3): # prob levels 0, 1, 2
121-
problems = simulator.select_problems(set(), prob_level=level)
156+
problems = simulator.select_problems(
157+
instruments_in_expedition, prob_level=level
158+
)
122159
if level == 0:
123160
assert problems is None
124161
else:
@@ -132,13 +169,22 @@ def test_select_problems_prob_levels(tmp_path):
132169
def test_prob_level_two_more_problems(tmp_path):
133170
prob_level = 2
134171

135-
short_expedition = _make_simple_expedition(num_waypoints=2)
172+
short_expedition = _make_simple_expedition(
173+
num_waypoints=2
174+
) # short in terms of number of waypoints
175+
instruments_in_short_expedition = short_expedition.get_instruments()
136176
simulator_short = ProblemSimulator(short_expedition, str(tmp_path))
177+
137178
long_expedition = _make_simple_expedition(num_waypoints=12)
179+
instruments_in_long_expedition = long_expedition.get_instruments()
138180
simulator_long = ProblemSimulator(long_expedition, str(tmp_path))
139181

140-
problems_short = simulator_short.select_problems(set(), prob_level=prob_level)
141-
problems_long = simulator_long.select_problems(set(), prob_level=prob_level)
182+
problems_short = simulator_short.select_problems(
183+
instruments_in_short_expedition, prob_level=prob_level
184+
)
185+
problems_long = simulator_long.select_problems(
186+
instruments_in_long_expedition, prob_level=prob_level
187+
)
142188

143189
assert len(problems_long["problem_class"]) >= len(
144190
problems_short["problem_class"]
@@ -165,3 +211,31 @@ def test_has_contingency_during_expedition(tmp_path):
165211
# short distance expedition should have contingency, long distance should not (given time between waypoints and ship speed is constant)
166212
assert short_simulator._has_contingency(problem_cls, problem_waypoint_i=0) is True
167213
assert long_simulator._has_contingency(problem_cls, problem_waypoint_i=0) is False
214+
215+
216+
def test_post_expedition_report(tmp_path):
217+
expedition = _make_simple_expedition(
218+
num_waypoints=12
219+
) # longer expedition to increase likelihood of multiple problems at prob_level=2
220+
instruments_in_expedition = expedition.get_instruments()
221+
222+
simulator = ProblemSimulator(expedition, str(tmp_path))
223+
problems = simulator.select_problems(instruments_in_expedition, prob_level=2)
224+
225+
report_path = tmp_path / REPORT
226+
simulator.post_expedition_report(problems, report_path)
227+
228+
assert report_path.exists()
229+
with open(report_path, encoding="utf-8") as f:
230+
content = f.read()
231+
232+
assert content.count("Problem:") == len(problems["problem_class"]), (
233+
"Number of reported problems should match number of selected problems."
234+
)
235+
assert content.count("Delay caused:") == len(problems["problem_class"]), (
236+
"Number of reported delay durations should match number of selected problems."
237+
)
238+
for problem in problems["problem_class"]:
239+
assert problem.message in content, (
240+
"Problem messages in report should match those of selected problems."
241+
)

0 commit comments

Comments
 (0)