diff --git a/pyproject.toml b/pyproject.toml index fb2681eda28..7ca68e6f4b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -522,6 +522,7 @@ docstring-code-format = true [tool.pytest.ini_options] addopts = """ + --import-mode=importlib --ignore-glob='dist.*' --ignore-glob='bin.*' --ignore-glob='*/tests/data/*' diff --git a/python/grass/temporal/core.py b/python/grass/temporal/core.py index efec4d8b66d..67d26a8b1d6 100644 --- a/python/grass/temporal/core.py +++ b/python/grass/temporal/core.py @@ -596,6 +596,7 @@ def init(raise_fatal_error: bool = False, skip_db_version_check: bool = False): global raise_on_error # noqa: FURB154 global enable_mapset_check, enable_timestamp_write # noqa: FURB154 global current_mapset, current_location, current_gisdbase # noqa: FURB154 + global message_interface, c_library_interface # noqa: FURB154 raise_on_error = raise_fatal_error @@ -604,10 +605,30 @@ def init(raise_fatal_error: bool = False, skip_db_version_check: bool = False): gs.run_command("t.connect", flags="c") grassenv = gs.gisenv() + new_mapset = grassenv["MAPSET"] + new_location = grassenv["LOCATION_NAME"] + new_gisdbase = grassenv["GISDBASE"] + + # The message and C-library subprocesses inherit GISRC at spawn time, + # so a session change leaves them bound to the old (possibly deleted) + # session. Stop them so the _init_* helpers below respawn them in the + # current environment. + if current_mapset is not None and ( + current_mapset != new_mapset + or current_location != new_location + or current_gisdbase != new_gisdbase + ): + if message_interface is not None: + message_interface.stop() + message_interface = None + if c_library_interface is not None: + c_library_interface.stop() + c_library_interface = None + # Set the global variable for faster access - current_mapset = grassenv["MAPSET"] - current_location = grassenv["LOCATION_NAME"] - current_gisdbase = grassenv["GISDBASE"] + current_mapset = new_mapset + current_location = new_location + current_gisdbase = new_gisdbase # Check environment variable GRASS_TGIS_RAISE_ON_ERROR if ( diff --git a/python/grass/temporal/tests/grass_temporal_core_init_session_change_test.py b/python/grass/temporal/tests/grass_temporal_core_init_session_change_test.py new file mode 100644 index 00000000000..e07fd019014 --- /dev/null +++ b/python/grass/temporal/tests/grass_temporal_core_init_session_change_test.py @@ -0,0 +1,47 @@ +# SPDX-License-Identifier: GPL-2.0-or-later + +"""Regression test for tgis.init() across a GRASS session change. + +tgis.init() spawns message and C-library subprocesses that capture +GISRC at spawn time. When the parent process switches to a different +project (different LOCATION_NAME or GISDBASE) and calls tgis.init() +again, those subprocesses must be restarted; otherwise they remain +bound to the previous session's GISRC, which may have been deleted. +""" + +import pytest + +from grass.grassdb.create import create_mapset +import grass.script as gs +import grass.temporal as tgis + + +@pytest.fixture +def two_projects(tmp_path): + """Create two empty projects and yield their paths.""" + project_a = tmp_path / "a" + project_b = tmp_path / "b" + gs.create_project(project_a) + create_mapset(project_a / "a") + gs.create_project(project_b) + create_mapset(project_b / "b") + return project_a, project_b + + +def test_init_restarts_subprocesses_on_session_change(two_projects): + """tgis.init() must respawn its subprocesses after a session change.""" + project_a, project_b = two_projects + with gs.setup.init(project_a / "a"): + tgis.init() + first_ciface = tgis.get_tgis_c_library_interface() + assert first_ciface.available_mapsets() == ["a", "PERMANENT"] + + with gs.setup.init(project_b / "b"): + tgis.init() + second_ciface = tgis.get_tgis_c_library_interface() + + # The C-library interface must have been replaced. Without this, + # the subprocess is still bound to session A's deleted GISRC and + # the call below fails. + assert second_ciface is not first_ciface + assert second_ciface.available_mapsets() == ["b", "PERMANENT"]