Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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/*'
Expand Down
27 changes: 24 additions & 3 deletions python/grass/temporal/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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 (
Expand Down
Original file line number Diff line number Diff line change
@@ -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"]
Loading