Skip to content

Commit 7ff7225

Browse files
committed
Add tests for factories
1 parent 2f25ef8 commit 7ff7225

File tree

4 files changed

+878
-0
lines changed

4 files changed

+878
-0
lines changed
Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
import os
2+
import sys
3+
import types
4+
from pathlib import Path
5+
6+
import pytest
7+
8+
from pysatl_experiment.configuration.criteria_config.criteria_config import CriterionConfig
9+
from pysatl_experiment.configuration.experiment_config.time_complexity.time_complexity import (
10+
TimeComplexityExperimentConfig,
11+
)
12+
from pysatl_experiment.configuration.experiment_data.time_complexity.time_complexity import TimeComplexityExperimentData
13+
from pysatl_experiment.configuration.model.criterion.criterion import Criterion
14+
from pysatl_experiment.configuration.model.experiment_type.experiment_type import ExperimentType
15+
from pysatl_experiment.configuration.model.hypothesis.hypothesis import Hypothesis
16+
from pysatl_experiment.configuration.model.report_mode.report_mode import ReportMode
17+
from pysatl_experiment.configuration.model.run_mode.run_mode import RunMode
18+
from pysatl_experiment.configuration.model.step_type.step_type import StepType
19+
from pysatl_experiment.factory.model.abstract_experiment_factory.abstract_experiment_factory import (
20+
AbstractExperimentFactory,
21+
)
22+
23+
24+
# Stub line_profiler to avoid optional dependency issues in imports
25+
_lp = types.ModuleType("line_profiler")
26+
27+
28+
def _profile(func):
29+
return func
30+
31+
32+
_lp.profile = _profile
33+
sys.modules.setdefault("line_profiler", _lp)
34+
35+
36+
# ----------------- Fakes / helpers -----------------
37+
38+
39+
class FakeRandomValuesStorage:
40+
def __init__(self):
41+
self.deleted_all_queries = []
42+
43+
def init(self): # pragma: no cover
44+
pass
45+
46+
def delete_all_data(self, query):
47+
self.deleted_all_queries.append(query)
48+
49+
50+
class FakeResultStorage:
51+
def __init__(self):
52+
self.deleted_queries = []
53+
54+
def init(self): # pragma: no cover
55+
pass
56+
57+
def delete_data(self, query):
58+
self.deleted_queries.append(query)
59+
60+
61+
class FakeExperimentStorage:
62+
def __init__(self, experiment_id: int = 123):
63+
self._id = experiment_id
64+
65+
def init(self): # pragma: no cover
66+
pass
67+
68+
def get_experiment_id(self, query):
69+
return self._id
70+
71+
72+
class FakeStatistics:
73+
@staticmethod
74+
def code() -> str:
75+
return "FAKE_CODE"
76+
77+
78+
class DummyStep:
79+
def __init__(self, name: str):
80+
self.name = name
81+
82+
83+
class ConcreteFactory(
84+
AbstractExperimentFactory[TimeComplexityExperimentData, DummyStep, DummyStep, DummyStep, FakeResultStorage]
85+
):
86+
def __init__(
87+
self,
88+
experiment_data: TimeComplexityExperimentData,
89+
data_storage: FakeRandomValuesStorage,
90+
result_storage: FakeResultStorage,
91+
experiment_storage: FakeExperimentStorage,
92+
):
93+
super().__init__(experiment_data)
94+
self._ds = data_storage
95+
self._rs = result_storage
96+
self._es = experiment_storage
97+
98+
# Deterministic overrides
99+
def _get_hypothesis_generator_metadata(self): # type: ignore[override]
100+
return "FAKEGEN", [1.0], object()
101+
102+
def _get_criteria_config(self): # type: ignore[override]
103+
crit = Criterion(criterion_code="FAKE", parameters=[])
104+
return [
105+
CriterionConfig(
106+
criterion=crit, criterion_code=FakeStatistics.code(), statistics_class_object=FakeStatistics()
107+
)
108+
]
109+
110+
# Storage initializers
111+
def _init_data_storage(self): # type: ignore[override]
112+
return self._ds
113+
114+
def _init_result_storage(self): # type: ignore[override]
115+
return self._rs
116+
117+
def _init_experiment_storage(self): # type: ignore[override]
118+
return self._es
119+
120+
# Step creators
121+
def _create_generation_step(self, data_storage): # type: ignore[override]
122+
return DummyStep("generation")
123+
124+
def _create_execution_step(self, data_storage, result_storage, experiment_storage): # type: ignore[override]
125+
return DummyStep("execution")
126+
127+
def _create_report_building_step(self, result_storage): # type: ignore[override]
128+
return DummyStep("report")
129+
130+
131+
def build_tc_data(
132+
results_path: Path, run_mode: RunMode, is_gen_done: bool, is_exec_done: bool
133+
) -> TimeComplexityExperimentData:
134+
config = TimeComplexityExperimentConfig(
135+
experiment_type=ExperimentType.TIME_COMPLEXITY,
136+
storage_connection=os.fspath(results_path / "test.sqlite"),
137+
run_mode=run_mode,
138+
hypothesis=Hypothesis.EXPONENTIAL,
139+
generator_type=StepType.STANDARD,
140+
executor_type=StepType.STANDARD,
141+
report_builder_type=StepType.STANDARD,
142+
sample_sizes=[10, 20],
143+
monte_carlo_count=5,
144+
criteria=[Criterion(criterion_code="FAKE", parameters=[])],
145+
report_mode=ReportMode.WITH_CHART,
146+
)
147+
steps_done = type(
148+
"StepsDone", (), {"is_generation_step_done": is_gen_done, "is_execution_step_done": is_exec_done}
149+
)()
150+
return TimeComplexityExperimentData(
151+
name="abstract_test",
152+
config=config,
153+
steps_done=steps_done,
154+
results_path=results_path,
155+
)
156+
157+
158+
@pytest.fixture()
159+
def tmp_results_path(tmp_path: Path) -> Path:
160+
return tmp_path
161+
162+
163+
def test_create_experiment_steps_reuse_sets_steps(tmp_results_path: Path):
164+
data = build_tc_data(tmp_results_path, RunMode.REUSE, is_gen_done=False, is_exec_done=False)
165+
ds = FakeRandomValuesStorage()
166+
rs = FakeResultStorage()
167+
es = FakeExperimentStorage(experiment_id=11)
168+
factory = ConcreteFactory(data, ds, rs, es)
169+
170+
steps = factory.create_experiment_steps()
171+
# Should not delete anything in REUSE
172+
assert ds.deleted_all_queries == []
173+
assert rs.deleted_queries == []
174+
175+
# Both generation and execution present; report always present
176+
assert steps.generation_step is not None and isinstance(steps.generation_step, DummyStep)
177+
assert steps.execution_step is not None and isinstance(steps.execution_step, DummyStep)
178+
assert steps.report_building_step is not None and isinstance(steps.report_building_step, DummyStep)
179+
assert steps.experiment_id == 11
180+
181+
182+
def test_create_experiment_steps_overwrite_deletes_and_respects_steps_done(tmp_results_path: Path):
183+
data = build_tc_data(tmp_results_path, RunMode.OVERWRITE, is_gen_done=True, is_exec_done=True)
184+
ds = FakeRandomValuesStorage()
185+
rs = FakeResultStorage()
186+
es = FakeExperimentStorage(experiment_id=22)
187+
factory = ConcreteFactory(data, ds, rs, es)
188+
189+
steps = factory.create_experiment_steps()
190+
191+
# Overwrite triggers deletion of sample data for each sample size
192+
assert len(ds.deleted_all_queries) == len(data.config.sample_sizes)
193+
# Overwrite triggers deletion of result queries (1 criterion code × sizes)
194+
assert len(rs.deleted_queries) == len(data.config.sample_sizes)
195+
196+
# Since steps_done indicates both done, generation/execution should be None; report present
197+
assert steps.generation_step is None
198+
assert steps.execution_step is None
199+
assert steps.report_building_step is not None
200+
201+
202+
def test_create_experiment_steps_partial_steps_done(tmp_results_path: Path):
203+
data = build_tc_data(tmp_results_path, RunMode.REUSE, is_gen_done=True, is_exec_done=False)
204+
ds = FakeRandomValuesStorage()
205+
rs = FakeResultStorage()
206+
es = FakeExperimentStorage(experiment_id=33)
207+
factory = ConcreteFactory(data, ds, rs, es)
208+
209+
steps = factory.create_experiment_steps()
210+
# Generation skipped, execution included, report included
211+
assert steps.generation_step is None
212+
assert steps.execution_step is not None
213+
assert steps.report_building_step is not None

0 commit comments

Comments
 (0)