|
| 1 | +"""Tests of :class:`ixmp.model.base.Model` classes in :mod:`message_ix.models`.""" |
| 2 | + |
| 3 | +import re |
1 | 4 | from collections import defaultdict |
| 5 | +from typing import TYPE_CHECKING |
2 | 6 |
|
3 | 7 | import ixmp |
4 | 8 | import pytest |
5 | 9 | from ixmp.backend.jdbc import JDBCBackend |
6 | 10 |
|
7 | 11 | from message_ix.models import MESSAGE, MESSAGE_MACRO |
8 | 12 |
|
| 13 | +if TYPE_CHECKING: |
| 14 | + from ixmp import Platform |
| 15 | + |
| 16 | + |
| 17 | +class TestMESSAGE: |
| 18 | + def test_initialize(self, caplog, test_mp: "Platform") -> None: |
| 19 | + # Expected numbers of items by type |
| 20 | + exp = defaultdict(list) |
| 21 | + for name, spec in MESSAGE.items.items(): |
| 22 | + exp[str(spec.type.name).lower()].append(name) |
| 23 | + |
| 24 | + # balance_equality is removed in initialize() for JDBC |
| 25 | + if isinstance(test_mp._backend, JDBCBackend): |
| 26 | + exp["set"].remove("balance_equality") |
| 27 | + |
| 28 | + # Use ixmp.Scenario to avoid invoking ixmp_source/Java code that automatically |
| 29 | + # populates empty scenarios |
| 30 | + s = ixmp.Scenario(test_mp, model="m", scenario="s", version="new") |
| 31 | + |
| 32 | + # Initialization succeeds on a totally empty scenario |
| 33 | + MESSAGE.initialize(s) |
| 34 | + |
| 35 | + # The expected items exist |
| 36 | + for ix_type, exp_names in exp.items(): |
| 37 | + obs_names = getattr(s, f"{ix_type}_list")() |
| 38 | + assert sorted(obs_names) == sorted(exp_names) |
| 39 | + |
| 40 | + def test_initialize_filter_log(self, caplog, test_mp: "Platform") -> None: |
| 41 | + """Test :meth:`MESSAGE.initialize` logging under some conditions. |
| 42 | +
|
| 43 | + For :class:`.Scenario` created with message_ix v3.10 or earlier, equations and |
| 44 | + variables may be initialized but have zero dimensions, thus empty lists of |
| 45 | + "index sets" and "index names". When :class:`.Scenario` is instantiated, |
| 46 | + :meth:`MESSAGE.initialize` is invoked, and in turn |
| 47 | + :meth:`ixmp.model.base.Model.initialize_items`. This method generates many log |
| 48 | + messages on level :data:`~logging.WARNING`. |
9 | 49 |
|
10 | | -def test_initialize(test_mp: ixmp.Platform) -> None: |
11 | | - # Expected numbers of items by type |
12 | | - exp = defaultdict(list) |
13 | | - for name, spec in MESSAGE.items.items(): |
14 | | - exp[str(spec.type.name).lower()].append(name) |
| 50 | + In order to prevent this log noise, :func:`.models._filter_log_initialize_items` |
| 51 | + is used. This test checks that it is effective. |
| 52 | + """ |
| 53 | + # Use ixmp.Scenario to avoid invoking ixmp_source/Java code that automatically |
| 54 | + # populates empty scenarios |
| 55 | + s = ixmp.Scenario(test_mp, model="m", scenario="s", version="new") |
15 | 56 |
|
16 | | - # balance_equality is removed in initialize() for JDBC |
17 | | - if isinstance(test_mp._backend, JDBCBackend): |
18 | | - exp["set"].remove("balance_equality") |
| 57 | + # Initialize an equation with no dimensions. This mocks the state of a Scenario |
| 58 | + # created with message_ix v3.10 or earlier. |
| 59 | + s.init_equ("NEW_CAPACITY_BOUND_LO", idx_sets=[], idx_names=[]) |
19 | 60 |
|
20 | | - # Use ixmp.Scenario to avoid invoking ixmp_source/Java code that |
21 | | - # automatically populates empty scenarios |
22 | | - s = ixmp.Scenario(test_mp, "test_initialize", "test_initialize", version="new") |
| 61 | + s.commit("") |
| 62 | + s.set_as_default() |
| 63 | + caplog.clear() |
23 | 64 |
|
24 | | - # Initialization succeeds on a totally empty scenario |
25 | | - MESSAGE.initialize(s) |
| 65 | + # Initialize items. |
| 66 | + MESSAGE.initialize(s) |
26 | 67 |
|
27 | | - # The expected items exist |
28 | | - for ix_type, exp_names in exp.items(): |
29 | | - obs_names = getattr(s, f"{ix_type}_list")() |
30 | | - assert sorted(obs_names) == sorted(exp_names) |
| 68 | + # Messages related to re-initializing items with 0 dimensions are filtered and |
| 69 | + # do not reach `caplog`. This assertion fails with message_ix v3.10. |
| 70 | + message_pattern = re.compile( |
| 71 | + r"Existing index (name|set)s of 'NEW_CAPACITY_BOUND_LO' \[\] do not match " |
| 72 | + r"\('node', '.*', 'year'\)" |
| 73 | + ) |
| 74 | + extra = list(filter(message_pattern.match, caplog.messages)) |
| 75 | + assert not extra, f"{len(extra)} unwanted log messages: {extra}" |
31 | 76 |
|
32 | 77 |
|
33 | 78 | def test_message_macro() -> None: |
|
0 commit comments