Skip to content

Commit 5eecfe1

Browse files
committed
test: optimize integration tests with intelligent timestep sampling
Reduce advanced model test duration by 69% through strategic sampling: - test_json_advanced_model: 42.56s → 13.21s (-69%) - test_example_models_validity[advanced]: 35.88s → 12.20s (-66%) Implementation: - Add session-scoped advanced_yaml_sampled fixtures to conftest.py - Sample 5 strategic years (2020, 2023, 2030, 2040, 2041) from 21-year model - Preserve temporal periods (flare rate changes) and numerical ranges - Reduce base_profile.csv from 22 to 5 rows (77% reduction) Overall suite improvement limited to 4.5% (2.62s) due to parallel execution with pytest-xdist - the 53s of individual savings are real but distributed across workers, with other tests now on critical path.
1 parent d1b4247 commit 5eecfe1

4 files changed

Lines changed: 7358 additions & 90982 deletions

File tree

tests/conftest.py

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from libecalc.common.time_utils import Frequency, Period, Periods
1515
from libecalc.common.utils.rates import RateType
1616
from libecalc.common.variables import ExpressionEvaluator, VariablesMap
17-
from libecalc.domain.process.value_objects.fluid_stream.fluid_model import FluidModel, EoSModel, FluidComposition
17+
from libecalc.domain.process.value_objects.fluid_stream.fluid_model import EoSModel, FluidComposition, FluidModel
1818
from libecalc.domain.regularity import Regularity
1919
from libecalc.domain.resource import Resource
2020
from libecalc.examples import advanced, drogon, simple
@@ -29,7 +29,7 @@
2929
from libecalc.presentation.yaml.domain.expression_time_series_pressure import ExpressionTimeSeriesPressure
3030
from libecalc.presentation.yaml.domain.time_series_expression import TimeSeriesExpression
3131
from libecalc.presentation.yaml.domain.time_series_resource import TimeSeriesResource
32-
from libecalc.presentation.yaml.mappers.fluid_mapper import MEDIUM_MW_19P4, RICH_MW_21P4, DRY_MW_18P3
32+
from libecalc.presentation.yaml.mappers.fluid_mapper import DRY_MW_18P3, MEDIUM_MW_19P4, RICH_MW_21P4
3333
from libecalc.presentation.yaml.model import YamlModel
3434
from libecalc.presentation.yaml.resource_service import ResourceService, TupleWithError
3535
from libecalc.presentation.yaml.yaml_entities import MemoryResource, ResourceStream
@@ -132,6 +132,97 @@ def advanced_yaml_path():
132132
return valid_example_cases["advanced"]
133133

134134

135+
@pytest.fixture(scope="session")
136+
def advanced_yaml_sampled_path(tmp_path_factory):
137+
"""Create advanced example with sampled timesteps for faster testing.
138+
139+
Reduces test time by ~75% (from ~40s to ~10s per test) by sampling 5 key timesteps
140+
that preserve numerical range, temporal model periods, and edge cases from the
141+
original 21-year model:
142+
- 2020: Stable baseline + high flare period (flare_a=5000, flare_b=10000)
143+
- 2023: First production change (OIL_PROD: 1000 → 2500)
144+
- 2030: Peak production (OIL_PROD: 9000) + low flare period (flare_a=2000, flare_b=7000)
145+
- 2040: Late decline phase
146+
- 2041: Final timestep (all zeros)
147+
148+
Returns:
149+
Path: Temporary directory containing the advanced model with sampled base_profile.csv
150+
151+
Note:
152+
This fixture uses session scope for efficiency - the directory is created once and
153+
shared across all tests (read-only). The fixture copies the advanced example directory
154+
to avoid modifying original files used by documentation. Temporal variables (flare rates)
155+
have changes at 2020-06-01 and 2030-01-01, so we keep at least one timestep per period.
156+
"""
157+
import csv
158+
import shutil
159+
160+
# Configuration: Sampled years covering key phases and temporal model periods
161+
SAMPLED_YEARS = {2020, 2023, 2030, 2040, 2041}
162+
163+
# Copy the entire advanced directory to preserve all resources and relative paths
164+
original_path = valid_example_cases["advanced"]
165+
advanced_dir = original_path.parent
166+
167+
tmp_dir = tmp_path_factory.mktemp("yaml_configs")
168+
temp_advanced_dir = tmp_dir / "advanced"
169+
# Session-scoped: Copy happens once per test session, not per test
170+
shutil.copytree(advanced_dir, temp_advanced_dir)
171+
172+
# Sample base_profile.csv to reduce computation while preserving numerical range
173+
base_profile_path = temp_advanced_dir / "base_profile.csv"
174+
if not base_profile_path.exists():
175+
raise FileNotFoundError(f"Expected base_profile.csv not found at {base_profile_path}")
176+
177+
with open(base_profile_path) as f:
178+
reader = csv.DictReader(f)
179+
rows = list(reader)
180+
181+
if not rows:
182+
raise ValueError("base_profile.csv is empty")
183+
184+
# Filter rows to sampled years
185+
sampled_rows = [row for row in rows if any(str(year) in row["DATE"] for year in SAMPLED_YEARS)]
186+
187+
if not sampled_rows:
188+
raise ValueError(f"No rows matched sampled years {SAMPLED_YEARS} in base_profile.csv")
189+
190+
# Write sampled CSV back
191+
with open(base_profile_path, "w", newline="") as f:
192+
writer = csv.DictWriter(f, fieldnames=rows[0].keys())
193+
writer.writeheader()
194+
writer.writerows(sampled_rows)
195+
196+
return temp_advanced_dir
197+
198+
199+
@pytest.fixture(scope="session")
200+
def advanced_yaml_sampled(advanced_yaml_sampled_path):
201+
"""Create YamlCase for advanced example with sampled timesteps.
202+
203+
This fixture wraps the sampled advanced directory in a YamlCase object for use
204+
in integration tests that call model.evaluate_energy_usage() directly.
205+
206+
For CLI tests, use advanced_yaml_sampled_path directly to get the file path.
207+
208+
Session-scoped for efficiency since tests only read from the model.
209+
210+
Returns:
211+
YamlCase: Loaded model with all resources and sampled timesteps
212+
"""
213+
from libecalc.fixtures.case_utils import YamlCaseLoader
214+
215+
# All resource files needed by the advanced model
216+
resource_names = [
217+
"base_profile.csv",
218+
"compressor_chart.csv",
219+
"compressor_sampled.csv",
220+
"genset.csv",
221+
"pump_chart.csv",
222+
]
223+
return YamlCaseLoader.load(advanced_yaml_sampled_path, "model.yaml", resource_names)
224+
225+
135226
@pytest.fixture(scope="session")
136227
def drogon_yaml_path():
137228
return valid_example_cases["drogon"]

0 commit comments

Comments
 (0)