Skip to content
Open
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
55 changes: 51 additions & 4 deletions python/grass/app/tests/grass_app_cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,20 +138,63 @@ def test_subcommand_run_with_crs_as_pack_subprocess(pack_raster_file4x5_rows, ca


def test_create_lock_unlock(tmp_path):
"""Check that we can create, lock, unlock (smoke test)"""
"""Check that locking creates the lock file and unlocking removes it"""
project = tmp_path / "test_1"
lock_file = project / "PERMANENT" / ".gislock"
assert main(["project", "create", str(project)]) == 0
assert not lock_file.exists()
assert main(["mapset", "lock", str(project / "PERMANENT")]) == 0
assert lock_file.exists()
assert main(["mapset", "unlock", str(project / "PERMANENT")]) == 0
assert not lock_file.exists()


def test_create_mapset_lock_unlock(tmp_path):
"""Check that we can create, lock, unlock (smoke test)"""
"""Check that a created mapset can be locked and unlocked"""
project = tmp_path / "test_1"
lock_file = project / "data_1" / ".gislock"
assert main(["project", "create", str(project)]) == 0
assert main(["mapset", "create", str(project / "data_1")]) == 0
assert main(["mapset", "lock", str(project / "data_1")]) == 0
assert lock_file.exists()
assert main(["mapset", "unlock", str(project / "data_1")]) == 0
assert not lock_file.exists()


def test_lock_mapset_already_locked(tmp_path, capsys):
"""Check that locking a locked mapset fails and keeps the existing lock"""
project = tmp_path / "test_1"
mapset = project / "PERMANENT"
assert main(["project", "create", str(project)]) == 0
assert main(["mapset", "lock", str(mapset)]) == 0
capsys.readouterr()
assert main(["mapset", "lock", str(mapset), "--timeout", "0"]) == 1
assert "currently being used by another GRASS session" in capsys.readouterr().err
assert (mapset / ".gislock").exists()


def test_lock_mapset_force_remove_lock(tmp_path, capsys):
"""Check that an existing lock can be forced to be removed and is reported"""
project = tmp_path / "test_1"
mapset = project / "PERMANENT"
assert main(["project", "create", str(project)]) == 0
assert main(["mapset", "lock", str(mapset)]) == 0
capsys.readouterr()
assert main(["mapset", "lock", str(mapset), "--force-remove-lock"]) == 0
assert "Removing lock in the selected mapset" in capsys.readouterr().out


def test_lock_mapset_missing_mapset(tmp_path, capsys):
"""Check that locking a mapset which does not exist is reported"""
assert main(["mapset", "lock", str(tmp_path / "missing")]) == 1
assert "doesn't exist" in capsys.readouterr().err


def test_unlock_mapset_without_lock(tmp_path):
"""Check that unlocking a mapset which is not locked is not an error"""
project = tmp_path / "test_1"
assert main(["project", "create", str(project)]) == 0
assert main(["mapset", "unlock", str(project / "PERMANENT")]) == 0


def test_create_mapset(tmp_path):
Expand Down Expand Up @@ -239,21 +282,25 @@ def test_create_mapset(tmp_path):
assert region["cols"] == 1


def test_mapset_create_exists(tmp_path):
def test_mapset_create_exists(tmp_path, capsys):
"""Check that creating mapset fails unless --overwrite is used"""
project = tmp_path / "test_1"
mapset = project / "data_1"
assert main(["project", "create", str(project)]) == 0
assert main(["mapset", "create", str(mapset)]) == 0
capsys.readouterr()
assert main(["mapset", "create", str(mapset)]) == 1
assert "Error creating mapset" in capsys.readouterr().err
assert main(["mapset", "create", "--overwrite", str(mapset)]) == 0


def test_create_overwrite(tmp_path):
def test_create_overwrite(tmp_path, capsys):
"""Check that creating when project exists fails unless overwrite is True"""
project = tmp_path / "test_1"
assert main(["project", "create", str(project)]) == 0
capsys.readouterr()
assert main(["project", "create", str(project)]) == 1
assert "Error creating project" in capsys.readouterr().err
assert main(["project", "create", "--overwrite", str(project)]) == 0


Expand Down
17 changes: 9 additions & 8 deletions python/grass/script/raster.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,14 +583,15 @@ def deactivate(self):
self.env["WIND_OVERRIDE"] = self._original_value
else:
self.env.pop("WIND_OVERRIDE", None)
run_command(
"g.remove",
flags="f",
quiet=True,
type="region",
name=self.region_name,
env=self.env,
)

run_command(
"g.remove",
flags="f",
quiet=True,
type="region",
name=self.region_name,
env=self.env,
)
self._active = False

def __enter__(self):
Expand Down
52 changes: 52 additions & 0 deletions python/grass/script/tests/grass_script_raster_info_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
"""Test grass.script.raster_info"""

import pytest

import grass.script as gs


def test_raster_info_extent_and_resolution_are_floats(session_2x2):
"""Test the extent and the resolution are numbers, not text"""
info = gs.raster_info("ones", env=session_2x2.env)
assert info["north"] == pytest.approx(1.0)
assert info["south"] == pytest.approx(0.0)
assert info["east"] == pytest.approx(1.0)
assert info["west"] == pytest.approx(0.0)
assert info["nsres"] == pytest.approx(0.5)
assert info["ewres"] == pytest.approx(0.5)


def test_raster_info_range_of_integer_raster(session_2x2):
"""Test the minimum and the maximum of an integer raster are floats"""
info = gs.raster_info("ones", env=session_2x2.env)
assert info["min"] == pytest.approx(1.0)
assert info["max"] == pytest.approx(1.0)
assert isinstance(info["min"], float)
assert isinstance(info["max"], float)


def test_raster_info_range_of_floating_point_raster(session_2x2):
"""Test the minimum and the maximum of a floating point raster"""
gs.mapcalc("quarters = float(row()) / 4", env=session_2x2.env)
info = gs.raster_info("quarters", env=session_2x2.env)
assert info["min"] == pytest.approx(0.25)
assert info["max"] == pytest.approx(0.5)
assert info["datatype"] == "FCELL"


def test_raster_info_range_of_raster_with_only_nulls(session_2x2):
"""Test the minimum and the maximum are None when there are no values"""
gs.mapcalc("only_nulls = null()", env=session_2x2.env)
info = gs.raster_info("only_nulls", env=session_2x2.env)
assert info["min"] is None
assert info["max"] is None


def test_raster_info_other_values_are_text(session_2x2):
"""Test the values which are not converted to numbers"""
info = gs.raster_info("ones", env=session_2x2.env)
assert info["datatype"] == "CELL"
assert info["rows"] == "2"
assert info["cols"] == "2"
assert info["map"] == "ones"
assert info["mapset"] == "PERMANENT"
9 changes: 9 additions & 0 deletions python/grass/script/tests/grass_script_raster_mask_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -258,3 +258,12 @@ def test_mask_manager_activate_deactivate(session_2x2):
manager.activate()
assert "GRASS_MASK" in session_2x2.env
manager.deactivate()


def test_mask_manager_restores_previous_mask_name(session_2x2):
"""Test MaskManager restores the value of GRASS_MASK set before it started."""
env = session_2x2.env
env["GRASS_MASK"] = "previous_mask"
with gs.MaskManager(mask_name="temporary_mask", remove=False, env=env):
assert env["GRASS_MASK"] == "temporary_mask"
assert env["GRASS_MASK"] == "previous_mask"
54 changes: 54 additions & 0 deletions python/grass/script/tests/grass_script_raster_region_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,57 @@ def test_region_manager_env_save(session_2x2):
region = gs.region(env=session_2x2.env)
assert region["rows"] == 6
assert region["cols"] == 6


def test_region_manager_removes_temporary_region(session_2x2):
"""Test RegionManager does not leave the region it saved in the mapset."""
env = session_2x2.env
with gs.RegionManager(env=env) as manager:
assert any(
name.startswith(manager.region_name)
for name in gs.list_strings("region", env=env)
)
assert not any(
name.startswith(manager.region_name)
for name in gs.list_strings("region", env=env)
)


def test_region_manager_nested(session_2x2):
"""Test the outer region is active again after a nested RegionManager ends."""
env = session_2x2.env
with gs.RegionManager(n=4, s=0, e=4, w=0, res=1, env=env) as outer:
with gs.RegionManager(n=6, s=0, e=6, w=0, res=1, env=env) as inner:
assert env["WIND_OVERRIDE"] == inner.region_name
assert gs.region(env=env)["rows"] == 6
assert env["WIND_OVERRIDE"] == outer.region_name
assert gs.region(env=env)["rows"] == 4
assert "WIND_OVERRIDE" not in env
assert gs.region(env=env)["rows"] == 2


def test_region_manager_nested_removes_temporary_regions(session_2x2):
"""Test a nested RegionManager does not leave its saved region in the mapset."""
env = session_2x2.env
with (
gs.RegionManager(env=env) as outer,
gs.RegionManager(env=env) as inner,
):
pass
saved = gs.list_strings("region", env=env)
assert not any(name.startswith(outer.region_name) for name in saved)
assert not any(name.startswith(inner.region_name) for name in saved)


def test_region_manager_env_nested(session_2x2):
"""Test the outer region is active again after a nested RegionManagerEnv ends."""
env = session_2x2.env
with gs.RegionManagerEnv(n=4, s=0, e=4, w=0, res=1, env=env):
outer_region = env["GRASS_REGION"]
with gs.RegionManagerEnv(n=6, s=0, e=6, w=0, res=1, env=env):
assert env["GRASS_REGION"] != outer_region
assert gs.region(env=env)["rows"] == 6
assert env["GRASS_REGION"] == outer_region
assert gs.region(env=env)["rows"] == 4
assert "GRASS_REGION" not in env
assert gs.region(env=env)["rows"] == 2
55 changes: 55 additions & 0 deletions python/grass/temporal/tests/grass_temporal_datetime_math_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from grass.temporal.datetime_math import (
adjust_datetime_to_granularity,
decrement_datetime_by_string,
increment_datetime_by_string,
)

Expand Down Expand Up @@ -48,6 +49,14 @@ def test_increment_datetime_by_string(start, increment, expected) -> None:
("2 weeks", datetime(2001, 8, 6, 0, 0)),
("6 months", datetime(2001, 8, 1, 0, 0)),
("2 years", datetime(2001, 1, 1, 0, 0)),
# The singular unit names work the same way.
("1 second", datetime(2001, 8, 8, 12, 30, 30)),
("1 minute", datetime(2001, 8, 8, 12, 30)),
("1 hour", datetime(2001, 8, 8, 12, 0)),
("1 day", datetime(2001, 8, 8, 0, 0)),
("1 week", datetime(2001, 8, 6, 0, 0)),
("1 month", datetime(2001, 8, 1, 0, 0)),
("1 year", datetime(2001, 1, 1, 0, 0)),
# With several units the finest one present wins, because truncating to
# it already satisfies all the coarser ones.
(
Expand All @@ -63,3 +72,49 @@ def test_adjust_datetime_to_granularity(granularity, expected) -> None:
adjust_datetime_to_granularity(datetime(2001, 8, 8, 12, 30, 30), granularity)
== expected
)


@pytest.mark.parametrize(
("increment", "expected"),
[
# Each unit is accepted in singular and plural form.
("1 second", datetime(2001, 1, 1, 0, 0, 1)),
("9 seconds", datetime(2001, 1, 1, 0, 0, 9)),
("1 minute", datetime(2001, 1, 1, 0, 1)),
("7 minutes", datetime(2001, 1, 1, 0, 7)),
("1 hour", datetime(2001, 1, 1, 1)),
("5 hours", datetime(2001, 1, 1, 5)),
("1 day", datetime(2001, 1, 2)),
("2 days", datetime(2001, 1, 3)),
# A week is seven days, also for counts other than one.
("1 week", datetime(2001, 1, 8)),
("2 weeks", datetime(2001, 1, 15)),
("1 month", datetime(2001, 2, 1)),
("1 year", datetime(2002, 1, 1)),
("3 years", datetime(2004, 1, 1)),
],
)
def test_increment_datetime_by_string_single_unit(increment, expected) -> None:
assert increment_datetime_by_string(datetime(2001, 1, 1), increment) == expected


@pytest.mark.parametrize(
("decrement", "expected"),
[
("1 second", datetime(2002, 3, 1, 12, 30, 29)),
("1 minute", datetime(2002, 3, 1, 12, 29, 30)),
("1 hour", datetime(2002, 3, 1, 11, 30, 30)),
("1 day", datetime(2002, 2, 28, 12, 30, 30)),
("2 days", datetime(2002, 2, 27, 12, 30, 30)),
("1 week", datetime(2002, 2, 22, 12, 30, 30)),
("2 weeks", datetime(2002, 2, 15, 12, 30, 30)),
("1 month", datetime(2002, 2, 1, 12, 30, 30)),
# More than twelve months has to roll the year back.
("13 months", datetime(2001, 2, 1, 12, 30, 30)),
("1 year", datetime(2001, 3, 1, 12, 30, 30)),
("3 years", datetime(1999, 3, 1, 12, 30, 30)),
],
)
def test_decrement_datetime_by_string(decrement, expected) -> None:
start = datetime(2002, 3, 1, 12, 30, 30)
assert decrement_datetime_by_string(start, decrement) == expected
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
from datetime import datetime

import pytest

from grass.temporal.datetime_math import compute_datetime_delta, datetime_delta


Expand Down Expand Up @@ -287,3 +290,44 @@ def test_compute_datetime_delta_month_full() -> None:
assert comp == datetime_delta(
year=0, month=0, day=30, hour=0, minute=0, second=0, max_days=30
)


@pytest.mark.parametrize(
("start", "end", "expected"),
[
# 28 hours: the hour of the end is before the one of the start.
(
datetime(2001, 1, 1, 22, 0),
datetime(2001, 1, 3, 2, 0),
datetime_delta(
year=0, month=0, day=1, hour=28, minute=0, second=0, max_days=1
),
),
# 26.5 hours: the minutes are counted over the hours.
(
datetime(2001, 1, 1, 10, 0),
datetime(2001, 1, 2, 12, 30),
datetime_delta(
year=0, month=0, day=1, hour=26, minute=1590, second=0, max_days=1
),
),
# Two days with the same minute: the minutes are counted over the days.
(
datetime(2001, 1, 1, 0, 15),
datetime(2001, 1, 3, 0, 15),
datetime_delta(
year=0, month=0, day=2, hour=0, minute=2880, second=0, max_days=2
),
),
# 26 hours with the same minute.
(
datetime(2001, 1, 1, 1, 15),
datetime(2001, 1, 2, 3, 15),
datetime_delta(
year=0, month=0, day=1, hour=26, minute=1560, second=0, max_days=1
),
),
],
)
def test_compute_datetime_delta_spanning_days(start, end, expected) -> None:
assert compute_datetime_delta(start, end) == expected
Loading