Skip to content

Commit

Permalink
Improve test coverage
Browse files Browse the repository at this point in the history
- Add a context-manager form of .jdbc._raise_jexception.
  • Loading branch information
khaeru committed Nov 21, 2023
1 parent 3560213 commit f743e57
Show file tree
Hide file tree
Showing 9 changed files with 54 additions and 34 deletions.
5 changes: 3 additions & 2 deletions ixmp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,9 @@

def __getattr__(name):
if name == "utils":
import ixmp.util
# Import via the old name to trigger DeprecatedPathFinder
import ixmp.utils as util # type: ignore [import-not-found]

return ixmp.util
return util
else:
raise AttributeError(name)
45 changes: 21 additions & 24 deletions ixmp/backend/jdbc.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import re
from collections import ChainMap
from collections.abc import Iterable, Sequence
from contextlib import contextmanager
from copy import copy
from functools import lru_cache
from pathlib import Path, PurePosixPath
Expand Down Expand Up @@ -141,6 +142,15 @@ def _raise_jexception(exc, msg="unhandled Java exception: "):
raise RuntimeError(msg) from None


@contextmanager
def _handle_jexception():
"""Context manager form of :func:`_raise_jexception`."""
try:
yield
except java.Exception as e:
_raise_jexception(e)


@lru_cache
def _fixed_index_sets(scheme: str) -> Mapping[str, List[str]]:
"""Return index sets for items that are fixed in the Java code.
Expand Down Expand Up @@ -475,10 +485,8 @@ def get_scenario_names(self) -> Generator[str, None, None]:

def get_scenarios(self, default, model, scenario):
# List<Map<String, Object>>
try:
with _handle_jexception():
scenarios = self.jobj.getScenarioList(default, model, scenario)
except java.IxException as e:
_raise_jexception(e)

for s in scenarios:
data = []
Expand Down Expand Up @@ -537,7 +545,7 @@ def read_file(self, path, item_type: ItemType, **kwargs):
if path.suffix == ".gdx" and item_type is ItemType.MODEL:
kw = {"check_solution", "comment", "equ_list", "var_list"}

if not isinstance(ts, Scenario):
if not isinstance(ts, Scenario): # pragma: no cover
raise ValueError("read from GDX requires a Scenario object")
elif set(kwargs.keys()) != kw:
raise ValueError(
Expand All @@ -556,10 +564,8 @@ def read_file(self, path, item_type: ItemType, **kwargs):
if len(kwargs):
raise ValueError(f"extra keyword arguments {kwargs}")

try:
with _handle_jexception():
self.jindex[ts].readSolutionFromGDX(*args)
except java.Exception as e:
_raise_jexception(e)

self.cache_invalidate(ts)
else:
Expand Down Expand Up @@ -601,7 +607,7 @@ def write_file(self, path, item_type: ItemType, **kwargs):
if path.suffix == ".gdx" and item_type is ItemType.SET | ItemType.PAR:
if len(filters):
raise NotImplementedError("write to GDX with filters")
elif not isinstance(ts, Scenario):
elif not isinstance(ts, Scenario): # pragma: no cover
raise ValueError("write to GDX requires a Scenario object")

# include_var_equ=False -> do not include variables/equations in GDX
Expand Down Expand Up @@ -688,10 +694,8 @@ def init(self, ts, annotation):

# Call either newTimeSeries or newScenario
method = getattr(self.jobj, "new" + klass)
try:
with _handle_jexception():
jobj = method(ts.model, ts.scenario, *args)
except java.IxException as e: # pragma: no cover
_raise_jexception(e)

self._index_and_set_attrs(jobj, ts)

Expand All @@ -703,12 +707,11 @@ def get(self, ts):

# either getTimeSeries or getScenario
method = getattr(self.jobj, "get" + ts.__class__.__name__)
try:

# Re-raise as a ValueError for bad model or scenario name, or other
with _handle_jexception():
# Either the 2- or 3- argument form, depending on args
jobj = method(*args)
except java.IxException as e:
# Re-raise as a ValueError for bad model or scenario name, or other
_raise_jexception(e)

self._index_and_set_attrs(jobj, ts)

Expand All @@ -719,10 +722,8 @@ def del_ts(self, ts):
self.gc()

def check_out(self, ts, timeseries_only):
try:
with _handle_jexception():
self.jindex[ts].checkOut(timeseries_only)
except java.IxException as e:
_raise_jexception(e)

def commit(self, ts, comment):
try:
Expand Down Expand Up @@ -1120,10 +1121,8 @@ def get_meta(
if version is not None:
version = java.Long(version)

try:
with _handle_jexception():
meta = self.jobj.getMeta(model, scenario, version, strict)
except java.IxException as e:
_raise_jexception(e)

return {entry.getKey(): _unwrap(entry.getValue()) for entry in meta.entrySet()}

Expand All @@ -1142,10 +1141,8 @@ def set_meta(
for k, v in meta.items():
jmeta.put(str(k), _wrap(v))

try:
with _handle_jexception():
self.jobj.setMeta(model, scenario, version, jmeta)
except java.IxException as e:
_raise_jexception(e)

def remove_meta(
self,
Expand Down
2 changes: 1 addition & 1 deletion ixmp/core/scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -478,7 +478,7 @@ def init_item(

if idx_names and len(idx_names) != len(idx_sets):
raise ValueError(
f"index names {repr(idx_names)} must have same length as index sets"
f"index names {repr(idx_names)} must have the same length as index sets"
f" {repr(idx_sets)}"
)

Expand Down
2 changes: 1 addition & 1 deletion ixmp/testing/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import resource

has_resource_module = True
except ImportError:
except ImportError: # pragma: no cover
# Windows
has_resource_module = False

Expand Down
5 changes: 5 additions & 0 deletions ixmp/tests/backend/test_jdbc.py
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,11 @@ def test_write_file(self, tmp_path, be):
with pytest.raises(NotImplementedError):
be.write_file(tmp_path / "test.csv", ixmp.ItemType.ALL, filters={})

# Specific to JDBCBackend
def test_gc(self, monkeypatch, be):
monkeypatch.setattr(ixmp.backend.jdbc, "_GC_AGGRESSIVE", True)
be.gc()


def test_exceptions(test_mp):
"""Ensure that Python exceptions are raised for some actions."""
Expand Down
13 changes: 9 additions & 4 deletions ixmp/tests/core/test_scenario.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def test_init_set(self, scen):
with pytest.raises(ValueError, match="'foo' already exists"):
scen.init_set("foo")

def test_init_par(self, scen):
def test_init_par(self, scen) -> None:
scen = scen.clone(keep_solution=False)
scen.check_out()

Expand All @@ -157,6 +157,10 @@ def test_init_par(self, scen):
# Return type of idx_sets is still list
assert scen.idx_sets("foo") == ["i", "j"]

# Mismatched sets and names
with pytest.raises(ValueError, match="must have the same length"):
scen.init_par("bar", idx_sets=("i", "j"), idx_names=("a", "b", "c"))

def test_init_scalar(self, scen):
scen2 = scen.clone(keep_solution=False)
scen2.check_out()
Expand Down Expand Up @@ -243,14 +247,15 @@ def test_par(self, scen):

def test_items0(self, scen):
# Without filters
iterator = scen.items()
with pytest.warns(FutureWarning, match="par_data=False will be the default"):
iterator = scen.items()

# next() can be called → an iterator was returned
next(iterator)

# Iterator returns the expected parameter names
exp = ["a", "b", "d", "f"]
for i, (name, data) in enumerate(scen.items()):
for i, (name, data) in enumerate(scen.items(par_data=True)):
# Name is correct in the expected order
assert exp[i] == name
# Data is one of the return types of .par()
Expand All @@ -260,7 +265,7 @@ def test_items0(self, scen):
assert i == 3

# With filters
iterator = scen.items(filters=dict(i=["seattle"]))
iterator = scen.items(filters=dict(i=["seattle"]), par_data=True)
exp = [("a", 1), ("d", 3)]
for i, (name, data) in enumerate(iterator):
# Name is correct in the expected order
Expand Down
5 changes: 5 additions & 0 deletions ixmp/tests/report/test_util.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
def test_import_rename_dims():
"""RENAME_DIMS can be imported from .report.util, though defined in .common."""
from ixmp.report.util import RENAME_DIMS

assert isinstance(RENAME_DIMS, dict)
7 changes: 7 additions & 0 deletions ixmp/tests/test_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,13 @@ def test_import(self):
):
import ixmp.reporting.computations # type: ignore # noqa: F401

@pytest.mark.filterwarnings("ignore")
def test_import1(self):
"""utils can be imported from ixmp, but raises DeprecationWarning."""
from ixmp import utils

assert "diff" in dir(utils)

def test_importerror(self):
with pytest.warns(DeprecationWarning), pytest.raises(ImportError):
import ixmp.reporting.foo # type: ignore # noqa: F401
Expand Down
4 changes: 2 additions & 2 deletions ixmp/util/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,8 @@ def diff(a, b, filters=None) -> Iterator[Tuple[str, pd.DataFrame]]:
"""
# Iterators; index 0 corresponds to `a`, 1 to `b`
items = [
a.items(filters=filters, type=ItemType.PAR),
b.items(filters=filters, type=ItemType.PAR),
a.items(filters=filters, type=ItemType.PAR, par_data=True),
b.items(filters=filters, type=ItemType.PAR, par_data=True),
]
# State variables for loop
name = ["", ""]
Expand Down

0 comments on commit f743e57

Please sign in to comment.