From 2102c832a1f48b25e6a483e97eccff25e0b71527 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Tue, 12 Mar 2024 09:42:19 +0100 Subject: [PATCH 01/28] Use request.node.name for make_dantzig scenarios --- ixmp/testing/data.py | 24 ++++++++++++++++++----- ixmp/tests/backend/test_base.py | 4 ++-- ixmp/tests/backend/test_jdbc.py | 22 ++++++++++----------- ixmp/tests/core/test_scenario.py | 4 ++-- ixmp/tests/report/test_operator.py | 12 ++++++------ ixmp/tests/report/test_reporter.py | 8 ++++---- ixmp/tests/test_integration.py | 31 ++++++++++++------------------ ixmp/tests/test_model.py | 8 ++++---- ixmp/tests/test_util.py | 22 ++++++++++----------- 9 files changed, 71 insertions(+), 64 deletions(-) diff --git a/ixmp/testing/data.py b/ixmp/testing/data.py index 877c64f6e..216e06925 100644 --- a/ixmp/testing/data.py +++ b/ixmp/testing/data.py @@ -1,12 +1,13 @@ # Methods are in alphabetical order from itertools import product from math import ceil -from typing import Any, List +from typing import Any, List, Optional import genno import numpy as np import pandas as pd import pint +import pytest from ixmp import Platform, Scenario, TimeSeries from ixmp.backend import IAMC_IDX @@ -158,7 +159,12 @@ def add_test_data(scen: Scenario): return t, t_foo, t_bar, x -def make_dantzig(mp: Platform, solve: bool = False, quiet: bool = False) -> Scenario: +def make_dantzig( + mp: Platform, + solve: bool = False, + quiet: bool = False, + request: Optional["pytest.FixtureRequest"] = None, +) -> Scenario: """Return :class:`ixmp.Scenario` of Dantzig's canning/transport problem. Parameters @@ -169,6 +175,8 @@ def make_dantzig(mp: Platform, solve: bool = False, quiet: bool = False) -> Scen If :obj:`True`. then solve the scenario before returning. Default :obj:`False`. quiet : bool, optional If :obj:`True`, suppress console output when solving. + request : :class:`pytest.FixtureRequest`, optional + If present, use for a distinct scenario name for each test. Returns ------- @@ -189,14 +197,20 @@ def make_dantzig(mp: Platform, solve: bool = False, quiet: bool = False) -> Scen # Initialize a new Scenario, and use the DantzigModel class' initialize() # method to populate it annot = "Dantzig's transportation problem for illustration and testing" - scen = Scenario( - mp, - **models["dantzig"], # type: ignore [arg-type] + args = dict( + **models["dantzig"], version="new", annotation=annot, scheme="dantzig", with_data=True, ) + if request: + # Use a distinct scenario name for a particular test + args.update(scenario=request.node.name) + scen = Scenario( + mp, + **args, # type: ignore [arg-type] + ) # commit the scenario scen.commit("Import Dantzig's transport problem for testing.") diff --git a/ixmp/tests/backend/test_base.py b/ixmp/tests/backend/test_base.py index 4020d9066..deb83c7f0 100644 --- a/ixmp/tests/backend/test_base.py +++ b/ixmp/tests/backend/test_base.py @@ -135,14 +135,14 @@ def test_cache_invalidate(self, test_mp): backend.cache_invalidate(ts, "par", "baz", dict(x=["x1", "x2"], y=["y1", "y2"])) - def test_del_ts(self, test_mp): + def test_del_ts(self, test_mp, request): """Test CachingBackend.del_ts().""" # Since CachingBackend is an abstract class, test it via JDBCBackend backend = test_mp._backend cache_size_pre = len(backend._cache) # Load data, thereby adding to the cache - s = make_dantzig(test_mp) + s = make_dantzig(test_mp, request=request) s.par("d") # Cache size has increased diff --git a/ixmp/tests/backend/test_jdbc.py b/ixmp/tests/backend/test_jdbc.py index e59d4703e..b1bc5efd1 100644 --- a/ixmp/tests/backend/test_jdbc.py +++ b/ixmp/tests/backend/test_jdbc.py @@ -273,7 +273,7 @@ def test_connect_message(capfd, caplog): @pytest.mark.parametrize("arg", [True, False]) -def test_cache_arg(arg): +def test_cache_arg(arg, request): """Test 'cache' argument, passed to CachingBackend.""" mp = ixmp.Platform( backend="jdbc", @@ -281,7 +281,7 @@ def test_cache_arg(arg): url="jdbc:hsqldb:mem://test_cache_false", cache=arg, ) - scen = make_dantzig(mp) + scen = make_dantzig(mp, request=request) # Maybe put something in the cache scen.par("a") @@ -339,8 +339,8 @@ def test_init(tmp_env, args, kwargs, action, kind, match): ixmp.Platform(*args, **kwargs) -def test_gh_216(test_mp): - scen = make_dantzig(test_mp) +def test_gh_216(test_mp, request): + scen = make_dantzig(test_mp, request=request) filters = dict(i=["seattle", "beijing"]) @@ -382,7 +382,7 @@ def test_verbose_exception(test_mp, exception_verbose_true): # See also test_base.TestCachingBackend.test_del_ts reason="https://github.com/iiasa/ixmp/issues/463", ) -def test_del_ts(): +def test_del_ts(request): mp = ixmp.Platform( backend="jdbc", driver="hsqldb", @@ -394,9 +394,9 @@ def test_del_ts(): # Create a list of some Scenario objects N = 8 - scenarios = [make_dantzig(mp)] + scenarios = [make_dantzig(mp, request=request)] for i in range(1, N): - scenarios.append(scenarios[0].clone(scenario=f"clone {i}")) + scenarios.append(scenarios[0].clone(scenario=f"{request.node.name} clone {i}")) # Number of referenced objects has increased by 8 assert len(mp._backend.jindex) == N_obj + N @@ -648,8 +648,8 @@ def test_reload_cycle( memory_usage("shutdown") -def test_docs(test_mp): - scen = make_dantzig(test_mp) +def test_docs(test_mp, request): + scen = make_dantzig(test_mp, request=request) # test model docs test_mp.set_doc("model", {scen.model: "Dantzig model"}) assert test_mp.get_doc("model") == {"canning problem": "Dantzig model"} @@ -672,9 +672,9 @@ def test_docs(test_mp): assert ex.value.args[0] == exp -def test_cache_clear(test_mp): +def test_cache_clear(test_mp, request): """Removing set elements causes the cache to be cleared entirely.""" - scen = make_dantzig(test_mp) + scen = make_dantzig(test_mp, request=request) # Load an item so that it is cached d0 = scen.par("d") diff --git a/ixmp/tests/core/test_scenario.py b/ixmp/tests/core/test_scenario.py index c61460a39..6259066fb 100644 --- a/ixmp/tests/core/test_scenario.py +++ b/ixmp/tests/core/test_scenario.py @@ -696,7 +696,7 @@ def test_filter_str(scen_empty): assert_frame_equal(exp[["s", "value"]], obs[["s", "value"]]) -def test_solve_callback(test_mp): +def test_solve_callback(test_mp, request): """Test the callback argument to Scenario.solve(). In real usage, callback() would compute some kind of convergence criterion. This @@ -705,7 +705,7 @@ def test_solve_callback(test_mp): equals an expected value, and the model has 'converged'. """ # Set up the Dantzig problem - scen = make_dantzig(test_mp) + scen = make_dantzig(test_mp, request=request) # Solve the scenario as configured solve_args = dict(model="dantzig", quiet=True) diff --git a/ixmp/tests/report/test_operator.py b/ixmp/tests/report/test_operator.py index 9e653c3b8..04ddfc95f 100644 --- a/ixmp/tests/report/test_operator.py +++ b/ixmp/tests/report/test_operator.py @@ -27,8 +27,8 @@ pytestmark = pytest.mark.usefixtures("parametrize_quantity_class") -def test_from_url(test_mp) -> None: - ts = make_dantzig(test_mp) +def test_from_url(test_mp, request) -> None: + ts = make_dantzig(test_mp, request=request) full_url = f"ixmp://{ts.platform.name}/{ts.url}" @@ -45,8 +45,8 @@ def test_from_url(test_mp) -> None: assert ts.url == result.url -def test_get_remove_ts(caplog, test_mp) -> None: - ts = make_dantzig(test_mp) +def test_get_remove_ts(caplog, test_mp, request) -> None: + ts = make_dantzig(test_mp, request=request) caplog.set_level(logging.INFO, "ixmp") @@ -107,8 +107,8 @@ def test_map_as_qty() -> None: assert_qty_equal(exp, result) -def test_update_scenario(caplog, test_mp) -> None: - scen = make_dantzig(test_mp) +def test_update_scenario(caplog, test_mp, request) -> None: + scen = make_dantzig(test_mp, request=request) scen.check_out() scen.add_set("j", "toronto") scen.commit("Add j=toronto") diff --git a/ixmp/tests/report/test_reporter.py b/ixmp/tests/report/test_reporter.py index 230f9ad19..86b413df0 100644 --- a/ixmp/tests/report/test_reporter.py +++ b/ixmp/tests/report/test_reporter.py @@ -23,7 +23,7 @@ def scenario(test_mp): @pytest.mark.usefixtures("protect_rename_dims") -def test_configure(test_mp, test_data_path) -> None: +def test_configure(test_mp, test_data_path, request) -> None: # Configure globally; handles 'rename_dims' section configure(rename_dims={"i": "i_renamed"}) @@ -33,7 +33,7 @@ def test_configure(test_mp, test_data_path) -> None: assert "i" in RENAME_DIMS # Reporting uses the RENAME_DIMS mapping of 'i' to 'i_renamed' - scen = make_dantzig(test_mp) + scen = make_dantzig(test_mp, request=request) rep = Reporter.from_scenario(scen) assert "d:i_renamed-j" in rep, rep.graph.keys() assert ["seattle", "san-diego"] == rep.get("i_renamed") @@ -142,10 +142,10 @@ def test_platform_units(test_mp, caplog, ureg) -> None: assert unit.dimensionality == {"[USD]": 1, "[pkm]": -1} -def test_cli(ixmp_cli, test_mp, test_data_path) -> None: +def test_cli(ixmp_cli, test_mp, test_data_path, request) -> None: # Put something in the database test_mp.open_db() - make_dantzig(test_mp) + make_dantzig(test_mp, request=request) test_mp.close_db() platform_name = test_mp.name diff --git a/ixmp/tests/test_integration.py b/ixmp/tests/test_integration.py index f5f3a2753..38606c3b8 100644 --- a/ixmp/tests/test_integration.py +++ b/ixmp/tests/test_integration.py @@ -1,6 +1,4 @@ import logging -import os -import platform import numpy as np import pytest @@ -14,7 +12,7 @@ TS_DF_CLEARED.loc[0, 2005] = np.nan -def test_run_clone(caplog, test_mp): +def test_run_clone(caplog, test_mp, request): caplog.set_level(logging.WARNING) # this test is designed to cover the full functionality of the GAMS API @@ -24,7 +22,7 @@ def test_run_clone(caplog, test_mp): # - reads back the solution from the output # - performs the test on the objective value and the timeseries data mp = test_mp - scen = make_dantzig(mp, solve=True, quiet=True) + scen = make_dantzig(mp, solve=True, quiet=True, request=request) assert np.isclose(scen.var("z")["lvl"], 153.675) assert_frame_equal(scen.timeseries(iamc=True), TS_DF) @@ -57,10 +55,10 @@ def test_run_clone(caplog, test_mp): assert_frame_equal(scen4.timeseries(iamc=True), TS_DF_CLEARED) -def test_run_remove_solution(test_mp): +def test_run_remove_solution(test_mp, request): # create a new instance of the transport problem and solve it mp = test_mp - scen = make_dantzig(mp, solve=True, quiet=True) + scen = make_dantzig(mp, solve=True, quiet=True, request=request) assert np.isclose(scen.var("z")["lvl"], 153.675) # check that re-solving the model will raise an error if a solution exists @@ -95,16 +93,10 @@ def get_distance(scen): return scen.par("d").set_index(["i", "j"]).loc["san-diego", "topeka"]["value"] -@pytest.mark.flaky( - reruns=5, - rerun_delay=2, - condition="GITHUB_ACTIONS" in os.environ and platform.system() == "Darwin", - reason="Flaky; see iiasa/ixmp#489", -) -def test_multi_db_run(tmpdir): +def test_multi_db_run(tmpdir, request): # create a new instance of the transport problem and solve it mp1 = ixmp.Platform(backend="jdbc", driver="hsqldb", path=tmpdir / "mp1") - scen1 = make_dantzig(mp1, solve=True, quiet=True) + scen1 = make_dantzig(mp1, solve=True, quiet=True, request=request) mp2 = ixmp.Platform(backend="jdbc", driver="hsqldb", path=tmpdir / "mp2") # add other unit to make sure that the mapping is correct during clone @@ -124,7 +116,8 @@ def test_multi_db_run(tmpdir): # reopen the connection to the second platform and reload scenario _mp2 = ixmp.Platform(backend="jdbc", driver="hsqldb", path=tmpdir / "mp2") assert_multi_db(mp1, _mp2) - scen2 = ixmp.Scenario(_mp2, **models["dantzig"]) + args = dict(**models["dantzig"]).update(scenario=request.node.name) + scen2 = ixmp.Scenario(_mp2, **args) # check that sets, variables and parameter were copied correctly assert_array_equal(scen1.set("i"), scen2.set("i")) @@ -138,10 +131,10 @@ def test_multi_db_run(tmpdir): assert_frame_equal(scen2.timeseries(iamc=True), TS_DF) -def test_multi_db_edit_source(tmpdir): +def test_multi_db_edit_source(tmpdir, request): # create a new instance of the transport problem mp1 = ixmp.Platform(backend="jdbc", driver="hsqldb", path=tmpdir / "mp1") - scen1 = make_dantzig(mp1) + scen1 = make_dantzig(mp1, request=request) mp2 = ixmp.Platform(backend="jdbc", driver="hsqldb", path=tmpdir / "mp2") scen2 = scen1.clone(platform=mp2) @@ -163,10 +156,10 @@ def test_multi_db_edit_source(tmpdir): assert_multi_db(mp1, mp2) -def test_multi_db_edit_target(tmpdir): +def test_multi_db_edit_target(tmpdir, request): # create a new instance of the transport problem mp1 = ixmp.Platform(backend="jdbc", driver="hsqldb", path=tmpdir / "mp1") - scen1 = make_dantzig(mp1) + scen1 = make_dantzig(mp1, request=request) mp2 = ixmp.Platform(backend="jdbc", driver="hsqldb", path=tmpdir / "mp2") scen2 = scen1.clone(platform=mp2) diff --git a/ixmp/tests/test_model.py b/ixmp/tests/test_model.py index d9fd14ba2..83259563d 100644 --- a/ixmp/tests/test_model.py +++ b/ixmp/tests/test_model.py @@ -23,9 +23,9 @@ class M1(Model): M1() -def test_model_initialize(test_mp, caplog): +def test_model_initialize(test_mp, caplog, request): # Model.initialize runs on an empty Scenario - s = make_dantzig(test_mp) + s = make_dantzig(test_mp, request=request) b1 = s.par("b") assert len(b1) == 3 @@ -108,8 +108,8 @@ def test_gams_version(): class TestGAMSModel: @pytest.fixture(scope="class") - def dantzig(self, test_mp): - yield make_dantzig(test_mp) + def dantzig(self, test_mp, request): + yield make_dantzig(test_mp, request=request) @pytest.mark.parametrize("char", r'<>"/\|?*') def test_filename_invalid_char(self, dantzig, char): diff --git a/ixmp/tests/test_util.py b/ixmp/tests/test_util.py index 8cf70219a..05f12e37e 100644 --- a/ixmp/tests/test_util.py +++ b/ixmp/tests/test_util.py @@ -56,10 +56,10 @@ def test_check_year(): assert util.check_year(y3, s3) is True -def test_diff_identical(test_mp): +def test_diff_identical(test_mp, request): """diff() of identical Scenarios.""" - scen_a = make_dantzig(test_mp) - scen_b = make_dantzig(test_mp) + scen_a = make_dantzig(test_mp, request=request) + scen_b = make_dantzig(test_mp, request=request) # Compare identical scenarios: produces data of same length for name, df in util.diff(scen_a, scen_b): @@ -72,10 +72,10 @@ def test_diff_identical(test_mp): assert exp_name == name and len(df) == N -def test_diff_data(test_mp): +def test_diff_data(test_mp, request): """diff() when Scenarios contain the same items, but different data.""" - scen_a = make_dantzig(test_mp) - scen_b = make_dantzig(test_mp) + scen_a = make_dantzig(test_mp, request=request) + scen_b = make_dantzig(test_mp, request=request) # Modify `scen_a` and `scen_b` scen_a.check_out() @@ -133,10 +133,10 @@ def test_diff_data(test_mp): pdt.assert_frame_equal(exp_d.iloc[[0, 3], :].reset_index(drop=True), df) -def test_diff_items(test_mp): +def test_diff_items(test_mp, request): """diff() when Scenarios contain the different items.""" - scen_a = make_dantzig(test_mp) - scen_b = make_dantzig(test_mp) + scen_a = make_dantzig(test_mp, request=request) + scen_b = make_dantzig(test_mp, request=request) # Modify `scen_a` and `scen_b` scen_a.check_out() @@ -156,11 +156,11 @@ def test_diff_items(test_mp): pass # No check of the contents -def test_discard_on_error(caplog, test_mp): +def test_discard_on_error(caplog, test_mp, request): caplog.set_level(logging.INFO, "ixmp.util") # Create a test scenario, checked-in state - s = make_dantzig(test_mp) + s = make_dantzig(test_mp, request=request) url = s.url # Some actions that don't trigger exceptions From c625e12312e273bc6806c4ddf7e0ce77882e2fb6 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Tue, 12 Mar 2024 13:04:38 +0100 Subject: [PATCH 02/28] Fix typo in docstring --- ixmp/core/platform.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ixmp/core/platform.py b/ixmp/core/platform.py index 10b770b27..ce7021858 100644 --- a/ixmp/core/platform.py +++ b/ixmp/core/platform.py @@ -214,6 +214,7 @@ def export_timeseries_data( - subannual - year - value + default : bool, optional :obj:`True` to include only TimeSeries versions marked as default. model: str, optional From 31d7a44a012dfa1ee8389d815a0f858934772488 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Tue, 12 Mar 2024 13:05:13 +0100 Subject: [PATCH 03/28] Group tutorials that depend on each other --- ixmp/tests/test_tutorials.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/ixmp/tests/test_tutorials.py b/ixmp/tests/test_tutorials.py index 08fb739cd..7bea28fa2 100644 --- a/ixmp/tests/test_tutorials.py +++ b/ixmp/tests/test_tutorials.py @@ -7,15 +7,10 @@ from ixmp.testing import get_cell_output, run_notebook -FLAKY = pytest.mark.flaky( - reruns=5, - rerun_delay=2, - condition="GITHUB_ACTIONS" in os.environ and platform.system() == "Darwin", - reason="Flaky; see iiasa/ixmp#489", -) +group_base_name = platform.system() + platform.python_version() -@FLAKY +@pytest.mark.xdist_group(name=f"{group_base_name}-0") def test_py_transport(tutorial_path, tmp_path, tmp_env): fname = tutorial_path / "transport" / "py_transport.ipynb" nb, errors = run_notebook(fname, tmp_path, tmp_env) @@ -25,7 +20,7 @@ def test_py_transport(tutorial_path, tmp_path, tmp_env): assert np.isclose(get_cell_output(nb, -5)["lvl"], 153.6750030517578) -@FLAKY +@pytest.mark.xdist_group(name=f"{group_base_name}-0") def test_py_transport_scenario(tutorial_path, tmp_path, tmp_env): fname = tutorial_path / "transport" / "py_transport_scenario.ipynb" nb, errors = run_notebook(fname, tmp_path, tmp_env) @@ -35,7 +30,7 @@ def test_py_transport_scenario(tutorial_path, tmp_path, tmp_env): assert np.isclose(get_cell_output(nb, "scen-detroit-z")["lvl"], 161.324) -@FLAKY +@pytest.mark.xdist_group(name=f"{group_base_name}-1") @pytest.mark.rixmp # TODO investigate and resolve the cause of the time outs; remove this mark @pytest.mark.skipif( @@ -47,7 +42,7 @@ def test_R_transport(tutorial_path, tmp_path, tmp_env): assert errors == [] -@FLAKY +@pytest.mark.xdist_group(name=f"{group_base_name}-1") @pytest.mark.rixmp # TODO investigate and resolve the cause of the time outs; remove this mark @pytest.mark.skipif( From d68f757b4177436e8cf1d1711a5d2829e2f06682 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Tue, 12 Mar 2024 13:05:55 +0100 Subject: [PATCH 04/28] Give every platform a test-specific name --- ixmp/tests/test_cli.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/ixmp/tests/test_cli.py b/ixmp/tests/test_cli.py index 48dd48151..15c66db1d 100644 --- a/ixmp/tests/test_cli.py +++ b/ixmp/tests/test_cli.py @@ -165,8 +165,9 @@ def call(*args, exit_0=True): assert UsageError.exit_code == r.exit_code -def test_platform_copy(ixmp_cli, tmp_path): +def test_platform_copy(ixmp_cli, tmp_path, request): """Test 'platform' command.""" + test_specific_name = request.node.name def call(*args, exit_0=True): result = ixmp_cli.invoke(["platform"] + list(map(str, args))) @@ -174,30 +175,47 @@ def call(*args, exit_0=True): return result # Add some temporary platform configuration - call("add", "p1", "jdbc", "oracle", "HOSTNAME", "USER", "PASSWORD") - call("add", "p2", "jdbc", "hsqldb", tmp_path.joinpath("p2")) + call( + "add", + f"p1-{test_specific_name}", + "jdbc", + "oracle", + "HOSTNAME", + "USER", + "PASSWORD", + ) + call( + "add", + f"p2-{test_specific_name}", + "jdbc", + "hsqldb", + tmp_path.joinpath(f"p2-{test_specific_name}"), + ) # Force connection to p2 so that files are created - ixmp_cli.invoke(["--platform=p2", "list"]) + ixmp_cli.invoke([f"--platform=p2-{test_specific_name}", "list"]) # Dry-run produces expected output - r = call("copy", "p2", "p3") - assert re.search("Copy .*p2.script → .*p3.script", r.output) + r = call("copy", f"p2-{test_specific_name}", f"p3-{test_specific_name}") + assert re.search( + f"Copy .*p2-{test_specific_name}.script → .*p3-{test_specific_name}.script", + r.output, + ) with pytest.raises(ValueError): # New platform configuration is not saved - ixmp.config.get_platform_info("p3") + ixmp.config.get_platform_info(f"p3-{test_specific_name}") # --go actually copies files, saves new platform config - r = call("copy", "--go", "p2", "p3") - assert tmp_path.joinpath("p3.script").exists() - assert ixmp.config.get_platform_info("p3") + r = call("copy", "--go", f"p2-{test_specific_name}", f"p3-{test_specific_name}") + assert tmp_path.joinpath(f"p3-{test_specific_name}.script").exists() + assert ixmp.config.get_platform_info(f"p3-{test_specific_name}") # Dry-run again with existing config and files - r = call("copy", "p2", "p3") + r = call("copy", f"p2-{test_specific_name}", f"p3-{test_specific_name}") assert "would replace existing file" in r.output # Copying a non-HyperSQL-backed platform fails with pytest.raises(AssertionError): - call("copy", "p1", "p3") + call("copy", f"p1-{test_specific_name}", f"p3-{test_specific_name}") def test_import_ts(ixmp_cli, test_mp, test_data_path): From 6a8bd547f9b3707bdf6fb2fc5718acb5c8813893 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Tue, 12 Mar 2024 13:06:47 +0100 Subject: [PATCH 05/28] Use test-specific name for scenarios --- ixmp/tests/core/test_timeseries.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ixmp/tests/core/test_timeseries.py b/ixmp/tests/core/test_timeseries.py index 50a6a6288..f7af7263b 100644 --- a/ixmp/tests/core/test_timeseries.py +++ b/ixmp/tests/core/test_timeseries.py @@ -87,7 +87,7 @@ def ts(self, request, mp): node = hash(request.node.nodeid.replace("/", " ")) # Class of object to yield cls = request.param - yield cls(mp, model=f"test-{node}", scenario="test", version="new") + yield cls(mp, model=f"test-{node}", scenario=f"test-{node}", version="new") # Initialize TimeSeries @pytest.mark.parametrize("cls", [TimeSeries, Scenario]) From c7d94ef8d281076a63a5190c2f68ede734532b65 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Tue, 12 Mar 2024 13:07:22 +0100 Subject: [PATCH 06/28] Use test-specific name for platforms * Split up test_close to receive only clean stdout --- ixmp/tests/backend/test_jdbc.py | 40 ++++++++++++++++++--------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/ixmp/tests/backend/test_jdbc.py b/ixmp/tests/backend/test_jdbc.py index b1bc5efd1..a8c937900 100644 --- a/ixmp/tests/backend/test_jdbc.py +++ b/ixmp/tests/backend/test_jdbc.py @@ -47,13 +47,7 @@ def test_jvm_warn(recwarn): assert len(recwarn) == 0, recwarn.pop().message -@pytest.mark.flaky( - reruns=5, - rerun_delay=2, - condition="GITHUB_ACTIONS" in os.environ and platform.system() == "Windows", - reason="Flaky; see iiasa/ixmp#489", -) -def test_close(test_mp_f, capfd): +def test_close_default_logging(test_mp_f, capfd): """Platform.close_db() doesn't throw needless exceptions.""" # Use the session-scoped fixture to avoid affecting other tests in this file mp = test_mp_f @@ -68,9 +62,21 @@ def test_close(test_mp_f, capfd): captured = capfd.readouterr() assert captured.out == "" - # With log level INFO, a message is printed + +def test_close_increased_logging(test_mp_f, capfd): + """Platform.close_db() doesn't throw needless exceptions.""" + # Use the session-scoped fixture to avoid affecting other tests in this file + mp = test_mp_f + + # Close once + mp.close_db() + + # Set higher log level INFO level = mp.get_log_level() mp.set_log_level(logging.INFO) + + # Close again, once already closed + # With logging.INFO, a message is printed mp.close_db() captured = capfd.readouterr() msg = "Database connection could not be closed or was already closed" @@ -237,19 +243,16 @@ def test_invalid_properties_file(test_data_path): ixmp.Platform(dbprops=test_data_path / "hsqldb.properties") -@pytest.mark.flaky( - reruns=5, - rerun_delay=2, - condition="GITHUB_ACTIONS" in os.environ and platform.system() == "Windows", - reason="Flaky; see iiasa/ixmp#489", -) -def test_connect_message(capfd, caplog): - msg = "connected to database 'jdbc:hsqldb:mem://ixmptest' (user: ixmp)..." +def test_connect_message(capfd, caplog, request): + msg = ( + f"connected to database 'jdbc:hsqldb:mem://{request.node.name}' (user: ixmp)..." + ) ixmp.Platform( + name=request.node.name, backend="jdbc", driver="hsqldb", - url="jdbc:hsqldb:mem://ixmptest", + url=f"jdbc:hsqldb:mem://{request.node.name}", log_level="INFO", ) @@ -262,9 +265,10 @@ def test_connect_message(capfd, caplog): # in the above call. Try again now that the level is INFO: ixmp.Platform( + name=request.node.name, backend="jdbc", driver="hsqldb", - url="jdbc:hsqldb:mem://ixmptest", + url=f"jdbc:hsqldb:mem://{request.node.name}", ) # Instead, log messages are printed to stdout From 38ac1745d1e09d556e5941ca53225a2c10649598 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Tue, 12 Mar 2024 13:10:27 +0100 Subject: [PATCH 07/28] Revert temporary fix from #510 --- .github/workflows/pytest.yaml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index 8df2bee22..2f0fa21d2 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -135,13 +135,12 @@ jobs: shell: Rscript {0} - name: Run test suite using pytest - # FIXME: Use --numprocesses=auto once flaky tests are fixed run: | pytest ixmp \ -m "not performance" \ --color=yes -rA --verbose \ --cov-report=xml \ - --numprocesses=1 + --numprocesses=auto --dist=loadgroup shell: bash - name: Upload test coverage to Codecov.io From b955aab23123d7aad73a5d451f23d2ad5a7ff6b7 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Tue, 12 Mar 2024 13:48:13 +0100 Subject: [PATCH 08/28] Copy model["dantzig"] properly * Adapt expected values to new scenario names --- ixmp/tests/test_integration.py | 36 ++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/ixmp/tests/test_integration.py b/ixmp/tests/test_integration.py index 38606c3b8..caf5d6b88 100644 --- a/ixmp/tests/test_integration.py +++ b/ixmp/tests/test_integration.py @@ -24,12 +24,18 @@ def test_run_clone(caplog, test_mp, request): mp = test_mp scen = make_dantzig(mp, solve=True, quiet=True, request=request) assert np.isclose(scen.var("z")["lvl"], 153.675) - assert_frame_equal(scen.timeseries(iamc=True), TS_DF) + assert_frame_equal( + scen.timeseries(iamc=True), + TS_DF.assign(scenario=[scen.scenario, scen.scenario]), + ) # cloning with `keep_solution=True` keeps all timeseries and the solution scen2 = scen.clone(keep_solution=True) assert np.isclose(scen2.var("z")["lvl"], 153.675) - assert_frame_equal(scen2.timeseries(iamc=True), TS_DF) + assert_frame_equal( + scen2.timeseries(iamc=True), + TS_DF.assign(scenario=[scen.scenario, scen.scenario]), + ) # version attribute of the clone increments the original (GitHub #211) assert scen2.version == scen.version + 1 @@ -45,14 +51,19 @@ def test_run_clone(caplog, test_mp, request): # timeseries set as `meta=True` scen3 = scen.clone(keep_solution=False) assert np.isnan(scen3.var("z")["lvl"]) - assert_frame_equal(scen3.timeseries(iamc=True), HIST_DF) + assert_frame_equal( + scen3.timeseries(iamc=True), HIST_DF.assign(scenario=scen.scenario) + ) # cloning with `keep_solution=False` and `first_model_year` # drops the solution and removes all timeseries not marked `meta=True` # in the model horizon (i.e, `year >= first_model_year`) scen4 = scen.clone(keep_solution=False, shift_first_model_year=2005) assert np.isnan(scen4.var("z")["lvl"]) - assert_frame_equal(scen4.timeseries(iamc=True), TS_DF_CLEARED) + assert_frame_equal( + scen4.timeseries(iamc=True), + TS_DF_CLEARED.assign(scenario=[scen.scenario, scen.scenario]), + ) def test_run_remove_solution(test_mp, request): @@ -70,7 +81,9 @@ def test_run_remove_solution(test_mp, request): scen2.remove_solution() assert not scen2.has_solution() assert np.isnan(scen2.var("z")["lvl"]) - assert_frame_equal(scen2.timeseries(iamc=True), HIST_DF) + assert_frame_equal( + scen2.timeseries(iamc=True), HIST_DF.assign(scenario=scen.scenario) + ) # remove the solution with a specific year as first model year, check that # variables are empty and timeseries not marked `meta=True` are removed @@ -78,7 +91,10 @@ def test_run_remove_solution(test_mp, request): scen3.remove_solution(first_model_year=2005) assert not scen3.has_solution() assert np.isnan(scen3.var("z")["lvl"]) - assert_frame_equal(scen3.timeseries(iamc=True), TS_DF_CLEARED) + assert_frame_equal( + scen3.timeseries(iamc=True), + TS_DF_CLEARED.assign(scenario=[scen.scenario, scen.scenario]), + ) def scenario_list(mp): @@ -116,7 +132,8 @@ def test_multi_db_run(tmpdir, request): # reopen the connection to the second platform and reload scenario _mp2 = ixmp.Platform(backend="jdbc", driver="hsqldb", path=tmpdir / "mp2") assert_multi_db(mp1, _mp2) - args = dict(**models["dantzig"]).update(scenario=request.node.name) + args = models["dantzig"].copy() + args.update(scenario=request.node.name) scen2 = ixmp.Scenario(_mp2, **args) # check that sets, variables and parameter were copied correctly @@ -128,7 +145,10 @@ def test_multi_db_run(tmpdir, request): # check that custom unit, region and timeseries are migrated correctly assert scen2.par("f")["value"] == 90.0 assert scen2.par("f")["unit"] == "USD/km" - assert_frame_equal(scen2.timeseries(iamc=True), TS_DF) + assert_frame_equal( + scen2.timeseries(iamc=True), + TS_DF.assign(scenario=[scen2.scenario, scen2.scenario]), + ) def test_multi_db_edit_source(tmpdir, request): From 3b629328a0e5ea3fa5ab482a585e4cdc98aa536d Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Mon, 18 Mar 2024 09:07:03 +0100 Subject: [PATCH 09/28] Avoid searching for to-be-created test-specific platforms --- ixmp/tests/backend/test_jdbc.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ixmp/tests/backend/test_jdbc.py b/ixmp/tests/backend/test_jdbc.py index a8c937900..b736c524a 100644 --- a/ixmp/tests/backend/test_jdbc.py +++ b/ixmp/tests/backend/test_jdbc.py @@ -248,8 +248,10 @@ def test_connect_message(capfd, caplog, request): f"connected to database 'jdbc:hsqldb:mem://{request.node.name}' (user: ixmp)..." ) + # TODO Specifying a name will fail because the test is looking for a platform with + # that name which doesn't exist yet. ixmp.Platform( - name=request.node.name, + # name=request.node.name, backend="jdbc", driver="hsqldb", url=f"jdbc:hsqldb:mem://{request.node.name}", @@ -265,7 +267,7 @@ def test_connect_message(capfd, caplog, request): # in the above call. Try again now that the level is INFO: ixmp.Platform( - name=request.node.name, + # name=request.node.name, backend="jdbc", driver="hsqldb", url=f"jdbc:hsqldb:mem://{request.node.name}", From 540d6134c962ee79a0d72a993152ba63796484e6 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Mon, 18 Mar 2024 09:27:27 +0100 Subject: [PATCH 10/28] Search for correct new scenario name --- ixmp/tests/report/test_reporter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ixmp/tests/report/test_reporter.py b/ixmp/tests/report/test_reporter.py index 86b413df0..22e9cdf35 100644 --- a/ixmp/tests/report/test_reporter.py +++ b/ixmp/tests/report/test_reporter.py @@ -159,7 +159,7 @@ def test_cli(ixmp_cli, test_mp, test_data_path, request) -> None: "--model", "canning problem", "--scenario", - "standard", + f"{request.node.name}", "report", "--config", str(test_data_path / "report-config-0.yaml"), From 4127f57e5ab6f7b50263563de27c1921fd0b0a82 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Mon, 18 Mar 2024 10:02:16 +0100 Subject: [PATCH 11/28] Increase default cell runtime on GHA --- ixmp/tests/test_tutorials.py | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/ixmp/tests/test_tutorials.py b/ixmp/tests/test_tutorials.py index 7bea28fa2..79da28f57 100644 --- a/ixmp/tests/test_tutorials.py +++ b/ixmp/tests/test_tutorials.py @@ -9,11 +9,23 @@ group_base_name = platform.system() + platform.python_version() +GHA = "GITHUB_ACTIONS" in os.environ + + +def default_args(): + """Default arguments for :func:`.run_notebook.""" + if GHA: + # Use a longer timeout + return dict(timeout=30) + else: + return dict() + @pytest.mark.xdist_group(name=f"{group_base_name}-0") def test_py_transport(tutorial_path, tmp_path, tmp_env): fname = tutorial_path / "transport" / "py_transport.ipynb" - nb, errors = run_notebook(fname, tmp_path, tmp_env) + args = default_args() + nb, errors = run_notebook(fname, tmp_path, tmp_env, **args) assert errors == [] # FIXME use get_cell_by_name instead of assuming cell count/order is fixed @@ -23,7 +35,8 @@ def test_py_transport(tutorial_path, tmp_path, tmp_env): @pytest.mark.xdist_group(name=f"{group_base_name}-0") def test_py_transport_scenario(tutorial_path, tmp_path, tmp_env): fname = tutorial_path / "transport" / "py_transport_scenario.ipynb" - nb, errors = run_notebook(fname, tmp_path, tmp_env) + args = default_args() + nb, errors = run_notebook(fname, tmp_path, tmp_env, **args) assert errors == [] assert np.isclose(get_cell_output(nb, "scen-z")["lvl"], 153.675) @@ -38,7 +51,8 @@ def test_py_transport_scenario(tutorial_path, tmp_path, tmp_env): ) def test_R_transport(tutorial_path, tmp_path, tmp_env): fname = tutorial_path / "transport" / "R_transport.ipynb" - nb, errors = run_notebook(fname, tmp_path, tmp_env, kernel_name="IR") + args = default_args() + nb, errors = run_notebook(fname, tmp_path, tmp_env, kernel_name="IR", **args) assert errors == [] @@ -50,5 +64,6 @@ def test_R_transport(tutorial_path, tmp_path, tmp_env): ) def test_R_transport_scenario(tutorial_path, tmp_path, tmp_env): fname = tutorial_path / "transport" / "R_transport_scenario.ipynb" - nb, errors = run_notebook(fname, tmp_path, tmp_env, kernel_name="IR") + args = default_args() + nb, errors = run_notebook(fname, tmp_path, tmp_env, kernel_name="IR", **args) assert errors == [] From 6c111d8fc32f243791eab13f4d548b0fd9cd6ba2 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Mon, 18 Mar 2024 10:49:01 +0100 Subject: [PATCH 12/28] Use more test-specific platforms --- ixmp/tests/test_access.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/ixmp/tests/test_access.py b/ixmp/tests/test_access.py index ddf3330e6..fe7d5b8ce 100644 --- a/ixmp/tests/test_access.py +++ b/ixmp/tests/test_access.py @@ -1,6 +1,4 @@ import logging -import os -import platform import sys from subprocess import Popen from time import sleep @@ -52,13 +50,7 @@ def mock(server): yield httpmock -@pytest.mark.flaky( - reruns=5, - rerun_delay=2, - condition="GITHUB_ACTIONS" in os.environ and platform.system() == "Darwin", - reason="Flaky; see iiasa/ixmp#489", -) -def test_check_single_model_access(mock, tmp_path, test_data_path): +def test_check_single_model_access(mock, tmp_path, test_data_path, request): mock.when( "POST /access/list", body='.+"test_user".+', @@ -71,7 +63,10 @@ def test_check_single_model_access(mock, tmp_path, test_data_path): ).reply("[false]", headers={"Content-Type": "application/json"}, times=FOREVER) test_props = create_test_platform( - tmp_path, test_data_path, "access", auth_url=mock.pretend_url + tmp_path, + test_data_path, + f"{request.node.name}_access", + auth_url=mock.pretend_url, ) mp = ixmp.Platform(backend="jdbc", dbprops=test_props) From 9f6afa39b8b630136a42c8079de5df8610ef4ff4 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Mon, 18 Mar 2024 12:30:23 +0100 Subject: [PATCH 13/28] Make call to to-be-tested function explicit --- ixmp/tests/backend/test_base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ixmp/tests/backend/test_base.py b/ixmp/tests/backend/test_base.py index deb83c7f0..b84920bea 100644 --- a/ixmp/tests/backend/test_base.py +++ b/ixmp/tests/backend/test_base.py @@ -138,7 +138,7 @@ def test_cache_invalidate(self, test_mp): def test_del_ts(self, test_mp, request): """Test CachingBackend.del_ts().""" # Since CachingBackend is an abstract class, test it via JDBCBackend - backend = test_mp._backend + backend: CachingBackend = test_mp._backend # type: ignore cache_size_pre = len(backend._cache) # Load data, thereby adding to the cache @@ -155,7 +155,7 @@ def test_del_ts(self, test_mp, request): s.__del__() # Force deletion of cached objects associated with `s` # Delete the object; associated cache is freed - del s + backend.del_ts(s) # Objects were invalidated/removed from cache assert cache_size_pre == len(backend._cache) From 4a6776961d9a361b0923f544d47139c788d32a6c Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Mon, 18 Mar 2024 12:55:34 +0100 Subject: [PATCH 14/28] Rename expected data files for individual tests --- ... => test_check_multi_model_access.properties} | 0 .../test_check_single_model_access.properties | 16 ++++++++++++++++ ixmp/tests/test_access.py | 6 +++--- 3 files changed, 19 insertions(+), 3 deletions(-) rename ixmp/tests/data/{access.properties => test_check_multi_model_access.properties} (100%) create mode 100644 ixmp/tests/data/test_check_single_model_access.properties diff --git a/ixmp/tests/data/access.properties b/ixmp/tests/data/test_check_multi_model_access.properties similarity index 100% rename from ixmp/tests/data/access.properties rename to ixmp/tests/data/test_check_multi_model_access.properties diff --git a/ixmp/tests/data/test_check_single_model_access.properties b/ixmp/tests/data/test_check_single_model_access.properties new file mode 100644 index 000000000..24a595ba8 --- /dev/null +++ b/ixmp/tests/data/test_check_single_model_access.properties @@ -0,0 +1,16 @@ +# Used by test_access.py + +config.name = unit_test_db@local + +jdbc.driver = org.hsqldb.jdbcDriver +jdbc.url = jdbc:hsqldb:mem:test_access +jdbc.user = ixmp +jdbc.pwd = ixmp + +application.tag = IXSE_SR15 +application.serverURL = http://localhost:8888 + +config.server.url = {auth_url} +config.server.config = DemoDB +config.server.username = service_user_dev +config.server.password = service_user_dev diff --git a/ixmp/tests/test_access.py b/ixmp/tests/test_access.py index fe7d5b8ce..42d6df993 100644 --- a/ixmp/tests/test_access.py +++ b/ixmp/tests/test_access.py @@ -65,7 +65,7 @@ def test_check_single_model_access(mock, tmp_path, test_data_path, request): test_props = create_test_platform( tmp_path, test_data_path, - f"{request.node.name}_access", + f"{request.node.name}", auth_url=mock.pretend_url, ) @@ -81,7 +81,7 @@ def test_check_single_model_access(mock, tmp_path, test_data_path, request): assert not granted -def test_check_multi_model_access(mock, tmp_path, test_data_path): +def test_check_multi_model_access(mock, tmp_path, test_data_path, request): mock.when( "POST /access/list", body='.+"test_user".+', @@ -98,7 +98,7 @@ def test_check_multi_model_access(mock, tmp_path, test_data_path): ) test_props = create_test_platform( - tmp_path, test_data_path, "access", auth_url=mock.pretend_url + tmp_path, test_data_path, f"{request.node.name}", auth_url=mock.pretend_url ) mp = ixmp.Platform(backend="jdbc", dbprops=test_props) From f3615ce13ad00806f84b997095ddc61c3152557e Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Fri, 22 Mar 2024 10:15:07 +0100 Subject: [PATCH 15/28] Call Scenario deletion function explicitly --- ixmp/tests/backend/test_jdbc.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ixmp/tests/backend/test_jdbc.py b/ixmp/tests/backend/test_jdbc.py index b736c524a..46dfa601c 100644 --- a/ixmp/tests/backend/test_jdbc.py +++ b/ixmp/tests/backend/test_jdbc.py @@ -423,7 +423,8 @@ def test_del_ts(request): s_jobj = mp._backend.jindex[s] # Now delete the Scenario object - del s + # del s # should work, but doesn't always resolve to s.__del__() + s.__del__() # Number of referenced objects decreases by 1 assert len(mp._backend.jindex) == N_obj + N - (i + 1) From 6216c02af09bae84e01b32d7dfc04ac01eeabbc3 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Fri, 22 Mar 2024 10:41:21 +0100 Subject: [PATCH 16/28] Call TimeSeries deletion function explicitly --- ixmp/tests/backend/test_jdbc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ixmp/tests/backend/test_jdbc.py b/ixmp/tests/backend/test_jdbc.py index 46dfa601c..ee68729fd 100644 --- a/ixmp/tests/backend/test_jdbc.py +++ b/ixmp/tests/backend/test_jdbc.py @@ -424,7 +424,7 @@ def test_del_ts(request): # Now delete the Scenario object # del s # should work, but doesn't always resolve to s.__del__() - s.__del__() + mp._backend.del_ts(s) # Number of referenced objects decreases by 1 assert len(mp._backend.jindex) == N_obj + N - (i + 1) From 12bfcc34a1a4e266eba4d8d8a44c4dea44db6277 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Fri, 22 Mar 2024 11:33:24 +0100 Subject: [PATCH 17/28] Add type hint for correct specific backend type --- ixmp/tests/backend/test_jdbc.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/ixmp/tests/backend/test_jdbc.py b/ixmp/tests/backend/test_jdbc.py index ee68729fd..35c275cba 100644 --- a/ixmp/tests/backend/test_jdbc.py +++ b/ixmp/tests/backend/test_jdbc.py @@ -395,8 +395,10 @@ def test_del_ts(request): url="jdbc:hsqldb:mem:test_del_ts", ) + backend: ixmp.backend.jdbc.JDBCBackend = mp._backend # type: ignore + # Number of Java objects referenced by the JDBCBackend - N_obj = len(mp._backend.jindex) + N_obj = len(backend.jindex) # Create a list of some Scenario objects N = 8 @@ -405,7 +407,7 @@ def test_del_ts(request): scenarios.append(scenarios[0].clone(scenario=f"{request.node.name} clone {i}")) # Number of referenced objects has increased by 8 - assert len(mp._backend.jindex) == N_obj + N + assert len(backend.jindex) == N_obj + N # Pop and free the objects for i in range(N): @@ -420,23 +422,23 @@ def test_del_ts(request): s_id = id(s) # Underlying Java object - s_jobj = mp._backend.jindex[s] + s_jobj = backend.jindex[s] # Now delete the Scenario object # del s # should work, but doesn't always resolve to s.__del__() - mp._backend.del_ts(s) + backend.del_ts(s) # Number of referenced objects decreases by 1 - assert len(mp._backend.jindex) == N_obj + N - (i + 1) + assert len(backend.jindex) == N_obj + N - (i + 1) # ID is no longer in JDBCBackend.jindex - assert s_id not in mp._backend.jindex + assert s_id not in backend.jindex # s_jobj is the only remaining reference to the Java object assert getrefcount(s_jobj) - 1 == 1 del s_jobj # Backend is again empty - assert len(mp._backend.jindex) == N_obj + assert len(backend.jindex) == N_obj # NB coverage is omitted because this test is not included in the standard suite From 12464ed6bee796d0dc2137a014c65fa73cbbf2cb Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Fri, 22 Mar 2024 11:34:02 +0100 Subject: [PATCH 18/28] Remove timeseries from jindex explicitly --- ixmp/backend/jdbc.py | 1 + 1 file changed, 1 insertion(+) diff --git a/ixmp/backend/jdbc.py b/ixmp/backend/jdbc.py index 35d471385..805b9054b 100644 --- a/ixmp/backend/jdbc.py +++ b/ixmp/backend/jdbc.py @@ -728,6 +728,7 @@ def del_ts(self, ts): # Aggressively free memory self.gc() + self.jindex.pop(ts, None) def check_out(self, ts, timeseries_only): with _handle_jexception(): From d469fd3e9a13c5913ad5e93d93eb41a298926359 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Wed, 11 Sep 2024 13:21:03 +0200 Subject: [PATCH 19/28] Discard tmp_path directories after each test --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index 421a53896..54e1b867e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -105,6 +105,7 @@ markers = [ "rixmp: test of the ixmp R interface.", "performance: ixmp performance test.", ] +tmp_path_retention_policy = "none" [tool.ruff.lint] select = ["C9", "E", "F", "I", "W"] From bb48e78fd87998a3fd695c1f45c64a0f533e7ed9 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Wed, 11 Sep 2024 16:28:19 +0200 Subject: [PATCH 20/28] Drop workaround for jupyter/nbclient#85 --- ixmp/testing/jupyter.py | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/ixmp/testing/jupyter.py b/ixmp/testing/jupyter.py index 3c3cdbf9e..7850dc50b 100644 --- a/ixmp/testing/jupyter.py +++ b/ixmp/testing/jupyter.py @@ -48,16 +48,6 @@ def run_notebook(nb_path, tmp_path, env=None, **kwargs): import nbformat from nbclient import NotebookClient - # Workaround for https://github.com/jupyter/nbclient/issues/85 - if ( - sys.version_info[0] == 3 - and sys.version_info[1] >= 8 - and sys.platform.startswith("win") - ): - import asyncio - - asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) - # Read the notebook nb = nbformat.read(nb_path, as_version=4) From 918298f7708fa07d5e7485b713346820f3f5fba5 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Wed, 11 Sep 2024 16:47:30 +0200 Subject: [PATCH 21/28] Make second R tutorial independent with helper function --- tutorial/transport/R_transport_scenario.ipynb | 219 +++++++++++++----- 1 file changed, 163 insertions(+), 56 deletions(-) diff --git a/tutorial/transport/R_transport_scenario.ipynb b/tutorial/transport/R_transport_scenario.ipynb index 44885e83f..76ff034a2 100644 --- a/tutorial/transport/R_transport_scenario.ipynb +++ b/tutorial/transport/R_transport_scenario.ipynb @@ -35,63 +35,109 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ "# Load reticulate, used to access the Python API from R\n", "library(reticulate)\n", "\n", "# Import ixmp and message_ix, just as in Python\n", - "ixmp <- import(\"ixmp\")" + "ixmp <- import(\"ixmp\")\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "vscode": { + "languageId": "r" + } }, "outputs": [], "source": [ "# launch the ix modeling platform using the local default database\n", - "mp <- ixmp$Platform()" + "mp <- ixmp$Platform()\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ "scen_list <- mp$scenario_list()\n", "scen_list\n", "\n", - "# TODO: the conversion of the Java output of the `scenario_list()` function to a clean R dataframe is not yet implemented" + "# TODO: the conversion of the Java output of the `scenario_list()` function to a clean R dataframe is not yet implemented\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "vscode": { + "languageId": "r" + } }, "outputs": [], "source": [ "# details for loading an existing datastructure from the IX modeling platform\n", "model <- \"transport problem\"\n", - "scenario <- \"standard\"" + "scenario <- \"standard\"\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If you have just run the first, ``R_transport`` tutorial, the existing scenario should appear, and we can load it.\n", + "Uncomment and run the following line." ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "vscode": { + "languageId": "r" + } }, "outputs": [], "source": [ "# load the default version scenario from the first tutorial\n", - "scen <- ixmp$Scenario(mp, model, scenario)" + "# scen <- ixmp$Scenario(mp, model, scenario)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If not (e.g. starting with this tutorial), we can use a function that creates the scenario from scratch in one step:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "vscode": { + "languageId": "r" + } + }, + "outputs": [], + "source": [ + "ixmp_testing <- import(\"ixmp.testing\")\n", + "scen <- ixmp_testing$make_dantzig(mp, solve = \".\")\n" ] }, { @@ -106,38 +152,50 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ "# load the distance parameter\n", - "d = scen$par(\"d\")\n", - "d" + "d <- scen$par(\"d\")\n", + "d\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ "# show only the distances for connections from Seattle\n", - "d[d['i'] == \"seattle\",]" + "d[d[\"i\"] == \"seattle\", ]\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ "# for faster access or more complex filtering,\n", "# it may be easier to only load specific parameter elements using a dictionary\n", - "ele_filter = {}\n", - "ele_filter$i = c('seattle')\n", - "ele_filter$j = c('chicago', 'topeka')\n", + "ele_filter <- {}\n", + "ele_filter$i <- c(\"seattle\")\n", + "ele_filter$j <- c(\"chicago\", \"topeka\")\n", "\n", - "d_filtered = scen$par(\"d\", ele_filter)\n", - "d_filtered" + "d_filtered <- scen$par(\"d\", ele_filter)\n", + "d_filtered\n" ] }, { @@ -154,67 +212,83 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "vscode": { + "languageId": "r" + } }, "outputs": [], "source": [ "# create a new scenario by cloning the datastructure (without keeping the solution)\n", - "scen_detroit <- scen$clone(model, 'detroit', annotation='extend the Transport problem by a new city', keep_solution=FALSE)" + "scen_detroit <- scen$clone(model, \"detroit\", annotation = \"extend the Transport problem by a new city\", keep_solution = FALSE)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "vscode": { + "languageId": "r" + } }, "outputs": [], "source": [ "# check out the datastructure to make changes\n", - "scen_detroit$check_out()" + "scen_detroit$check_out()\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ - "# reduce demand \n", - "scen_detroit$add_par('b', 'chicago', 200, 'cases')\n", + "# reduce demand\n", + "scen_detroit$add_par(\"b\", \"chicago\", 200, \"cases\")\n", "\n", "# add a new city with demand and distances\n", - "scen_detroit$add_set('j', 'detroit')\n", - "scen_detroit$add_par('b', 'detroit', 150, 'cases')\n", + "scen_detroit$add_set(\"j\", \"detroit\")\n", + "scen_detroit$add_par(\"b\", \"detroit\", 150, \"cases\")\n", "\n", - "d_add = data.frame(i = c('seattle','san-diego'), j = c('detroit','detroit'), value = c(1.7,1.9) , unit = 'cases')\n", - "scen_detroit$add_par('d', d_add)" + "d_add <- data.frame(i = c(\"seattle\", \"san-diego\"), j = c(\"detroit\", \"detroit\"), value = c(1.7, 1.9), unit = \"cases\")\n", + "scen_detroit$add_par(\"d\", d_add)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "vscode": { + "languageId": "r" + } }, "outputs": [], "source": [ - "d_add = data.frame(i = c('seattle','san-diego'), j = c('detroit','detroit'), value = c(1.7,1.9) , unit = 'cases')\n", - "scen_detroit$add_par('d', d_add)" + "d_add <- data.frame(i = c(\"seattle\", \"san-diego\"), j = c(\"detroit\", \"detroit\"), value = c(1.7, 1.9), unit = \"cases\")\n", + "scen_detroit$add_par(\"d\", d_add)\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "vscode": { + "languageId": "r" + } }, "outputs": [], "source": [ "# save changes to database\n", - "comment = \"add new city 'detroit' with demand, reduce demand in 'chicago'\"\n", - "scen_detroit$commit(comment) \n", - "scen_detroit$set_as_default()" + "comment <- \"add new city 'detroit' with demand, reduce demand in 'chicago'\"\n", + "scen_detroit$commit(comment)\n", + "scen_detroit$set_as_default()\n" ] }, { @@ -228,11 +302,14 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "vscode": { + "languageId": "r" + } }, "outputs": [], "source": [ - "scen_detroit$solve(model='dantzig')" + "scen_detroit$solve(model = \"dantzig\")\n" ] }, { @@ -247,61 +324,85 @@ { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ "# display the objective value of the solution in the baseline scenario\n", - "scen$var(\"z\")" + "scen$var(\"z\")\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ "# display the objective value of the solution in the \"detroit\" scenario\n", - "scen_detroit$var(\"z\")" + "scen_detroit$var(\"z\")\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ "# display the quantities transported from canning plants to demand locations in the baseline scenario\n", - "scen$var(\"x\")" + "scen$var(\"x\")\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ "# display the quantities transported from canning plants to demand locations in the \"detroit\" scenario\n", - "scen_detroit$var(\"x\")" + "scen_detroit$var(\"x\")\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ "# display the quantities and marginals (=shadow prices) of the demand balance constraints in the baseline scenario\n", - "scen$equ(\"demand\")" + "scen$equ(\"demand\")\n" ] }, { "cell_type": "code", "execution_count": null, - "metadata": {}, + "metadata": { + "vscode": { + "languageId": "r" + } + }, "outputs": [], "source": [ "# display the quantities and marginals (=shadow prices) of the demand balance constraints in the \"detroit\" scenario\n", - "scen_detroit$equ(\"demand\")" + "scen_detroit$equ(\"demand\")\n" ] }, { @@ -315,19 +416,25 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "vscode": { + "languageId": "r" + } }, "outputs": [], "source": [ "# close the connection of the platform instance to the local ixmp database files\n", - "mp$close_db()" + "mp$close_db()\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true + "collapsed": true, + "vscode": { + "languageId": "r" + } }, "outputs": [], "source": [] From 34f059593f7c338470371f83055c57cf2261253f Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Thu, 12 Sep 2024 08:46:37 +0200 Subject: [PATCH 22/28] Mark first tutorial tests as flaky on Windows --- ixmp/tests/test_tutorials.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/ixmp/tests/test_tutorials.py b/ixmp/tests/test_tutorials.py index 79da28f57..dc7cee6fb 100644 --- a/ixmp/tests/test_tutorials.py +++ b/ixmp/tests/test_tutorials.py @@ -12,6 +12,14 @@ GHA = "GITHUB_ACTIONS" in os.environ +FLAKY = pytest.mark.flaky( + reruns=5, + rerun_delay=2, + condition="GITHUB_ACTIONS" in os.environ and platform.system() == "Windows", + reason="Flaky; see iiasa/ixmp#543", +) + + def default_args(): """Default arguments for :func:`.run_notebook.""" if GHA: @@ -21,6 +29,7 @@ def default_args(): return dict() +@FLAKY @pytest.mark.xdist_group(name=f"{group_base_name}-0") def test_py_transport(tutorial_path, tmp_path, tmp_env): fname = tutorial_path / "transport" / "py_transport.ipynb" @@ -43,6 +52,7 @@ def test_py_transport_scenario(tutorial_path, tmp_path, tmp_env): assert np.isclose(get_cell_output(nb, "scen-detroit-z")["lvl"], 161.324) +@FLAKY @pytest.mark.xdist_group(name=f"{group_base_name}-1") @pytest.mark.rixmp # TODO investigate and resolve the cause of the time outs; remove this mark From 6a50d322e8238c3121214e7154c7d3c6d63a4947 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Thu, 12 Sep 2024 08:46:58 +0200 Subject: [PATCH 23/28] Mark test_del_ts as flaky on Windows --- ixmp/tests/backend/test_jdbc.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ixmp/tests/backend/test_jdbc.py b/ixmp/tests/backend/test_jdbc.py index 35c275cba..1b61b44a3 100644 --- a/ixmp/tests/backend/test_jdbc.py +++ b/ixmp/tests/backend/test_jdbc.py @@ -382,6 +382,12 @@ def test_verbose_exception(test_mp, exception_verbose_true): assert "at.ac.iiasa.ixmp.Platform.getScenario" in exc_msg +@pytest.mark.flaky( + reruns=5, + rerun_delay=2, + condition="GITHUB_ACTIONS" in os.environ and platform.system() == "Windows", + reason="Flaky; see iiasa/ixmp#543", +) @pytest.mark.xfail( condition=sys.version_info.minor <= 10, raises=AssertionError, From 5b30a639afd3edf165a5ab64d92e0d85b2fc3b4d Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Thu, 12 Sep 2024 10:14:28 +0200 Subject: [PATCH 24/28] Remove xfail marker for consistently xpassing test --- ixmp/tests/backend/test_jdbc.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/ixmp/tests/backend/test_jdbc.py b/ixmp/tests/backend/test_jdbc.py index 1b61b44a3..22b92d418 100644 --- a/ixmp/tests/backend/test_jdbc.py +++ b/ixmp/tests/backend/test_jdbc.py @@ -2,7 +2,6 @@ import logging import os import platform -import sys from sys import getrefcount from typing import Tuple @@ -388,12 +387,6 @@ def test_verbose_exception(test_mp, exception_verbose_true): condition="GITHUB_ACTIONS" in os.environ and platform.system() == "Windows", reason="Flaky; see iiasa/ixmp#543", ) -@pytest.mark.xfail( - condition=sys.version_info.minor <= 10, - raises=AssertionError, - # See also test_base.TestCachingBackend.test_del_ts - reason="https://github.com/iiasa/ixmp/issues/463", -) def test_del_ts(request): mp = ixmp.Platform( backend="jdbc", From fc76fab72d679cb85c3d49afdac90c1465a5734c Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Thu, 12 Sep 2024 10:43:28 +0200 Subject: [PATCH 25/28] Distinguish platforms for more stability --- ixmp/tests/backend/test_jdbc.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ixmp/tests/backend/test_jdbc.py b/ixmp/tests/backend/test_jdbc.py index 22b92d418..aebfc56bb 100644 --- a/ixmp/tests/backend/test_jdbc.py +++ b/ixmp/tests/backend/test_jdbc.py @@ -244,16 +244,14 @@ def test_invalid_properties_file(test_data_path): def test_connect_message(capfd, caplog, request): msg = ( - f"connected to database 'jdbc:hsqldb:mem://{request.node.name}' (user: ixmp)..." + f"connected to database 'jdbc:hsqldb:mem://{request.node.name}_0' " + "(user: ixmp)..." ) - # TODO Specifying a name will fail because the test is looking for a platform with - # that name which doesn't exist yet. ixmp.Platform( - # name=request.node.name, backend="jdbc", driver="hsqldb", - url=f"jdbc:hsqldb:mem://{request.node.name}", + url=f"jdbc:hsqldb:mem://{request.node.name}_0", log_level="INFO", ) @@ -264,12 +262,14 @@ def test_connect_message(capfd, caplog, request): # a previous run may have left the Java log level higher than INFO, in which # case the Java Platform object would not write to stderr before set_log_level() # in the above call. Try again now that the level is INFO: - + msg = ( + f"connected to database 'jdbc:hsqldb:mem://{request.node.name}_1' " + "(user: ixmp)..." + ) ixmp.Platform( - # name=request.node.name, backend="jdbc", driver="hsqldb", - url=f"jdbc:hsqldb:mem://{request.node.name}", + url=f"jdbc:hsqldb:mem://{request.node.name}_1", ) # Instead, log messages are printed to stdout From 61b40cbf2e9485c098e23a2f05233e955469295c Mon Sep 17 00:00:00 2001 From: Paul Natsuo Kishimoto Date: Thu, 12 Sep 2024 11:41:33 +0200 Subject: [PATCH 26/28] Expand typing in .testing.data --- ixmp/testing/data.py | 56 +++++++++++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 19 deletions(-) diff --git a/ixmp/testing/data.py b/ixmp/testing/data.py index 216e06925..9de18009c 100644 --- a/ixmp/testing/data.py +++ b/ixmp/testing/data.py @@ -1,7 +1,7 @@ # Methods are in alphabetical order from itertools import product from math import ceil -from typing import Any, List, Optional +from typing import TYPE_CHECKING, Any, Dict, List, Optional import genno import numpy as np @@ -12,8 +12,31 @@ from ixmp import Platform, Scenario, TimeSeries from ixmp.backend import IAMC_IDX +if TYPE_CHECKING: + from typing import TypedDict + + class ScenarioIdentifiers(TypedDict): + """Identifiers of a Scenario. + + Used only for type checking in this file, so version is omitted. + """ + + model: str + scenario: str + + class ScenarioKwargs(TypedDict, total=False): + """Keyword arguments to Scenario.__init__().""" + + model: str + scenario: str + version: str + scheme: str + annotation: str + with_data: Optional[bool] + + #: Common (model name, scenario name) pairs for testing. -SCEN = { +SCEN: Dict[str, "ScenarioIdentifiers"] = { "dantzig": dict(model="canning problem", scenario="standard"), "h2g2": dict(model="Douglas Adams", scenario="Hitchhiker"), } @@ -186,7 +209,7 @@ def make_dantzig( -------- .DantzigModel """ - # add custom units and region for timeseries data + # Add custom units and region for time series data try: mp.add_unit("USD/km") except Exception: @@ -194,36 +217,31 @@ def make_dantzig( pass mp.add_region("DantzigLand", "country") - # Initialize a new Scenario, and use the DantzigModel class' initialize() - # method to populate it - annot = "Dantzig's transportation problem for illustration and testing" - args = dict( + # Initialize a new Scenario, and use the DantzigModel class' initialize() method to + # populate it + args: "ScenarioKwargs" = dict( **models["dantzig"], version="new", - annotation=annot, + annotation="Dantzig's transportation problem for illustration and testing", scheme="dantzig", with_data=True, ) - if request: - # Use a distinct scenario name for a particular test - args.update(scenario=request.node.name) - scen = Scenario( - mp, - **args, # type: ignore [arg-type] - ) + # Use a distinct scenario name for a particular test + args.update({"scenario": request.node.name} if request else {}) + + scen = Scenario(mp, **args) - # commit the scenario + # Commit the scenario scen.commit("Import Dantzig's transport problem for testing.") - # set this new scenario as the default version for the model/scenario name + # Set this new scenario as the default version for the model/scenario name scen.set_as_default() if solve: # Solve the model using the GAMS code provided in the `tests` folder scen.solve(model="dantzig", case="transport_standard", quiet=quiet) - # add timeseries data for testing `clone(keep_solution=False)` - # and `remove_solution()` + # Add time series data for testing clone(keep_solution=False) and remove_solution() scen.check_out(timeseries_only=True) scen.add_timeseries(HIST_DF, meta=True) scen.add_timeseries(INP_DF) From 97f1121555a417caa2de5d3a3d83d9240790c976 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Thu, 12 Sep 2024 12:09:43 +0200 Subject: [PATCH 27/28] Remove editor-specific notebook metadata --- tutorial/transport/R_transport_scenario.ipynb | 128 ++++-------------- 1 file changed, 23 insertions(+), 105 deletions(-) diff --git a/tutorial/transport/R_transport_scenario.ipynb b/tutorial/transport/R_transport_scenario.ipynb index 76ff034a2..3346c89ec 100644 --- a/tutorial/transport/R_transport_scenario.ipynb +++ b/tutorial/transport/R_transport_scenario.ipynb @@ -35,11 +35,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "# Load reticulate, used to access the Python API from R\n", @@ -53,10 +49,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "vscode": { - "languageId": "r" - } + "collapsed": true }, "outputs": [], "source": [ @@ -67,11 +60,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "scen_list <- mp$scenario_list()\n", @@ -84,10 +73,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "vscode": { - "languageId": "r" - } + "collapsed": true }, "outputs": [], "source": [ @@ -108,10 +94,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "vscode": { - "languageId": "r" - } + "collapsed": true }, "outputs": [], "source": [ @@ -129,11 +112,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "ixmp_testing <- import(\"ixmp.testing\")\n", @@ -152,11 +131,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "# load the distance parameter\n", @@ -167,11 +142,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "# show only the distances for connections from Seattle\n", @@ -181,11 +152,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "# for faster access or more complex filtering,\n", @@ -212,10 +179,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "vscode": { - "languageId": "r" - } + "collapsed": true }, "outputs": [], "source": [ @@ -227,10 +191,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "vscode": { - "languageId": "r" - } + "collapsed": true }, "outputs": [], "source": [ @@ -241,11 +202,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "# reduce demand\n", @@ -263,10 +220,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "vscode": { - "languageId": "r" - } + "collapsed": true }, "outputs": [], "source": [ @@ -278,10 +232,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "vscode": { - "languageId": "r" - } + "collapsed": true }, "outputs": [], "source": [ @@ -302,10 +253,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "vscode": { - "languageId": "r" - } + "collapsed": true }, "outputs": [], "source": [ @@ -324,11 +272,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "# display the objective value of the solution in the baseline scenario\n", @@ -338,11 +282,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "# display the objective value of the solution in the \"detroit\" scenario\n", @@ -352,11 +292,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "# display the quantities transported from canning plants to demand locations in the baseline scenario\n", @@ -366,11 +302,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "# display the quantities transported from canning plants to demand locations in the \"detroit\" scenario\n", @@ -380,11 +312,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "# display the quantities and marginals (=shadow prices) of the demand balance constraints in the baseline scenario\n", @@ -394,11 +322,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "vscode": { - "languageId": "r" - } - }, + "metadata": {}, "outputs": [], "source": [ "# display the quantities and marginals (=shadow prices) of the demand balance constraints in the \"detroit\" scenario\n", @@ -416,10 +340,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "vscode": { - "languageId": "r" - } + "collapsed": true }, "outputs": [], "source": [ @@ -431,10 +352,7 @@ "cell_type": "code", "execution_count": null, "metadata": { - "collapsed": true, - "vscode": { - "languageId": "r" - } + "collapsed": true }, "outputs": [], "source": [] From 749709e131c983e86137c56b545d64c7910e5824 Mon Sep 17 00:00:00 2001 From: Fridolin Glatter Date: Thu, 12 Sep 2024 12:09:59 +0200 Subject: [PATCH 28/28] Address review comments --- ixmp/tests/test_tutorials.py | 42 ++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/ixmp/tests/test_tutorials.py b/ixmp/tests/test_tutorials.py index dc7cee6fb..6d82dc395 100644 --- a/ixmp/tests/test_tutorials.py +++ b/ixmp/tests/test_tutorials.py @@ -15,26 +15,23 @@ FLAKY = pytest.mark.flaky( reruns=5, rerun_delay=2, - condition="GITHUB_ACTIONS" in os.environ and platform.system() == "Windows", + condition=GHA and platform.system() == "Windows", reason="Flaky; see iiasa/ixmp#543", ) +@pytest.fixture(scope="session") def default_args(): """Default arguments for :func:`.run_notebook.""" - if GHA: - # Use a longer timeout - return dict(timeout=30) - else: - return dict() + # Use a longer timeout for GHA + return dict(timeout=30) if GHA else dict() @FLAKY @pytest.mark.xdist_group(name=f"{group_base_name}-0") -def test_py_transport(tutorial_path, tmp_path, tmp_env): +def test_py_transport(tutorial_path, tmp_path, tmp_env, default_args): fname = tutorial_path / "transport" / "py_transport.ipynb" - args = default_args() - nb, errors = run_notebook(fname, tmp_path, tmp_env, **args) + nb, errors = run_notebook(fname, tmp_path, tmp_env, **default_args) assert errors == [] # FIXME use get_cell_by_name instead of assuming cell count/order is fixed @@ -42,10 +39,9 @@ def test_py_transport(tutorial_path, tmp_path, tmp_env): @pytest.mark.xdist_group(name=f"{group_base_name}-0") -def test_py_transport_scenario(tutorial_path, tmp_path, tmp_env): +def test_py_transport_scenario(tutorial_path, tmp_path, tmp_env, default_args): fname = tutorial_path / "transport" / "py_transport_scenario.ipynb" - args = default_args() - nb, errors = run_notebook(fname, tmp_path, tmp_env, **args) + nb, errors = run_notebook(fname, tmp_path, tmp_env, **default_args) assert errors == [] assert np.isclose(get_cell_output(nb, "scen-z")["lvl"], 153.675) @@ -56,24 +52,22 @@ def test_py_transport_scenario(tutorial_path, tmp_path, tmp_env): @pytest.mark.xdist_group(name=f"{group_base_name}-1") @pytest.mark.rixmp # TODO investigate and resolve the cause of the time outs; remove this mark -@pytest.mark.skipif( - "GITHUB_ACTIONS" in os.environ and sys.platform == "linux", reason="Times out" -) -def test_R_transport(tutorial_path, tmp_path, tmp_env): +@pytest.mark.skipif(GHA and sys.platform == "linux", reason="Times out") +def test_R_transport(tutorial_path, tmp_path, tmp_env, default_args): fname = tutorial_path / "transport" / "R_transport.ipynb" - args = default_args() - nb, errors = run_notebook(fname, tmp_path, tmp_env, kernel_name="IR", **args) + nb, errors = run_notebook( + fname, tmp_path, tmp_env, kernel_name="IR", **default_args + ) assert errors == [] @pytest.mark.xdist_group(name=f"{group_base_name}-1") @pytest.mark.rixmp # TODO investigate and resolve the cause of the time outs; remove this mark -@pytest.mark.skipif( - "GITHUB_ACTIONS" in os.environ and sys.platform == "linux", reason="Times out" -) -def test_R_transport_scenario(tutorial_path, tmp_path, tmp_env): +@pytest.mark.skipif(GHA and sys.platform == "linux", reason="Times out") +def test_R_transport_scenario(tutorial_path, tmp_path, tmp_env, default_args): fname = tutorial_path / "transport" / "R_transport_scenario.ipynb" - args = default_args() - nb, errors = run_notebook(fname, tmp_path, tmp_env, kernel_name="IR", **args) + nb, errors = run_notebook( + fname, tmp_path, tmp_env, kernel_name="IR", **default_args + ) assert errors == []