diff --git a/temporal/t.rast.mapcalc/test.t.rast.mapcalc.operators.sh b/temporal/t.rast.mapcalc/test.t.rast.mapcalc.operators.sh deleted file mode 100755 index 646e36e5599..00000000000 --- a/temporal/t.rast.mapcalc/test.t.rast.mapcalc.operators.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/sh -# Test for t.rast.mapcalc -export GRASS_OVERWRITE=1 -# We need to set a specific region in the -# @preprocess step of this test. We generate -# raster with r.mapcalc and create several space time raster datasets -# The region setting should work for UTM and LL test locations -g.region s=0 n=80 w=0 e=120 b=0 t=50 res=10 res3=10 -p3 - -r.mapcalc -s expr="prec_1 = rand(0, 550)" -r.mapcalc -s expr="prec_2 = rand(0, 450)" -r.mapcalc -s expr="prec_3 = rand(0, 320)" -r.mapcalc -s expr="prec_4 = rand(0, 510)" -r.mapcalc -s expr="prec_5 = rand(0, 300)" -r.mapcalc -s expr="prec_6 = rand(0, 650)" - -t.create type=strds temporaltype=absolute output=precip_abs1 title="A test" descr="A test" -t.create type=strds temporaltype=absolute output=precip_abs2 title="A test" descr="A test" -t.register -i type=raster input=precip_abs1 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="3 months" -t.register type=raster input=precip_abs2 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 - -t.info precip_abs1 -t.info precip_abs2 - -# The first @test -t.rast.mapcalc --v -sn inputs=precip_abs1,precip_abs2 output=precip_abs3 \ - expression="if(start_time() >= 0 && end_time() >= 0, (precip_abs1*td() + precip_abs2) / td(), null()) " base=new_prec \ - method=equal nprocs=5 -t.info type=strds input=precip_abs3 - -t.rast.mapcalc --v -sn inputs=precip_abs1,precip_abs2 output=precip_abs3 \ - expression="if(start_year()>=0&&start_month()>=0&&start_day()>=0&&start_hour()>=0&&start_minute()>=0&&start_second()>=0, (precip_abs1*td() + precip_abs2) / td(), null()) " base=new_prec \ - method=equal nprocs=5 -t.info type=strds input=precip_abs3 - -t.rast.mapcalc --v -sn inputs=precip_abs1,precip_abs2 output=precip_abs3 \ - expression="if(end_year()>=0&&end_month()>=0&&end_day()>=0&&end_hour()>=0&&end_minute()>=0&&end_second()>=0, (precip_abs1*td() + precip_abs2) / td(), null()) " base=new_prec \ - method=equal nprocs=5 -t.info type=strds input=precip_abs3 - -# The first @test -t.rast.mapcalc --v -sn inputs=precip_abs1,precip_abs2 output=precip_abs3 \ - expression="if(start_doy() >= 0 && start_dow() >= 0, (precip_abs1*td() + precip_abs2) / td(), null()) " base=new_prec \ - method=equal nprocs=5 -t.info type=strds input=precip_abs3 - -# The first @test -t.rast.mapcalc --v -sn inputs=precip_abs1,precip_abs2 output=precip_abs3 \ - expression="if(end_doy() >= 0 && end_dow() >= 0, (precip_abs1*td() + precip_abs2) / td(), null()) " base=new_prec \ - method=equal nprocs=5 -t.info type=strds input=precip_abs3 - -t.rast.mapcalc --v -sn inputs=precip_abs1 output=precip_abs3 \ - expression="if(precip_abs1 == prec_1, prec_1, null())" base=new_prec - - -# @postprocess -t.remove -fr type=strds input=precip_abs1,precip_abs2,precip_abs3 diff --git a/temporal/t.rast.mapcalc/test.t.rast.mapcalc.sh b/temporal/t.rast.mapcalc/test.t.rast.mapcalc.sh deleted file mode 100755 index da6a8564d83..00000000000 --- a/temporal/t.rast.mapcalc/test.t.rast.mapcalc.sh +++ /dev/null @@ -1,56 +0,0 @@ -#!/bin/sh -# Test for t.rast.mapcalc - -export GRASS_OVERWRITE=1 - -# We need to set a specific region in the -# @preprocess step of this test. We generate -# raster with r.mapcalc and create several space time raster datasets -# The region setting should work for UTM and LL test locations -g.region s=0 n=80 w=0 e=120 b=0 t=50 res=10 res3=10 -p3 - -# Generate data -r.mapcalc expr="prec_1 = rand(0, 550)" -r.mapcalc expr="prec_2 = rand(0, 450)" -r.mapcalc expr="prec_3 = rand(0, 320)" -r.mapcalc expr="prec_4 = rand(0, 510)" -r.mapcalc expr="prec_5 = rand(0, 300)" -r.mapcalc expr="prec_6 = rand(0, 650)" - -t.create type=strds temporaltype=absolute output=precip_abs1 title="A test" descr="A test" -t.create type=strds temporaltype=absolute output=precip_abs2 title="A test" descr="A test" -t.register -i type=raster input=precip_abs1 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 start="2001-01-01" increment="3 months" -t.register type=raster input=precip_abs2 maps=prec_1,prec_2,prec_3,prec_4,prec_5,prec_6 - -t.info precip_abs1 -t.info precip_abs2 - -# The first @test -t.rast.mapcalc -n inputs=precip_abs1,precip_abs2 output=precip_abs3 \ - expression=" precip_abs1 + precip_abs2" base=new_prec \ - method=equal nprocs=5 -t.info type=strds input=precip_abs3 - -t.rast.mapcalc -s inputs=precip_abs1,precip_abs2,precip_abs3 output=precip_abs4 \ - expression=" (precip_abs1 + precip_abs2) / precip_abs2" base=new_prec \ - method=equal nprocs=5 -t.info type=strds input=precip_abs4 - -t.rast.mapcalc -s inputs=precip_abs1,precip_abs2 output=precip_abs4 \ - expression=" (precip_abs1 + precip_abs2) * null()" base=new_prec \ - method=equal nprocs=5 -t.info type=strds input=precip_abs4 - -t.rast.mapcalc -sn inputs=precip_abs1,precip_abs2 output=precip_abs4 \ - expression=" (precip_abs1 + precip_abs2) * null()" base=new_prec \ - method=equal nprocs=5 -t.info type=strds input=precip_abs4 - -# Let the test fail -g.remove -f type=raster name=prec_1 -t.rast.mapcalc -sn inputs=precip_abs1,precip_abs2 output=precip_abs4 \ - expression=" (precip_abs1 + precip_abs2) * null()" base=new_prec \ - method=equal nprocs=5 - -# @postprocess -t.remove -rf type=strds input=precip_abs1,precip_abs2,precip_abs3,precip_abs4 diff --git a/temporal/t.rast.mapcalc/tests/conftest.py b/temporal/t.rast.mapcalc/tests/conftest.py new file mode 100644 index 00000000000..780a8daddae --- /dev/null +++ b/temporal/t.rast.mapcalc/tests/conftest.py @@ -0,0 +1,71 @@ +"""Fixtures for t.rast.mapcalc tests. + +(C) 2025 by the GRASS Development Team +This program is free software under the GNU General Public +License (>=v2). Read the file COPYING that comes with GRASS +for details. +""" + +import os + +import pytest + +import grass.script as gs +from grass.tools import Tools + + +@pytest.fixture(scope="session") +def mapcalc_session(tmp_path_factory): + """GRASS session with prec_1..prec_3 rasters and precip_abs1/2 STRDS. + + Session-scoped: setup (region, rasters, STRDS, TGIS init) runs once for + the whole test run. Tests use overwrite=True for their outputs and + test_failure_on_missing_map restores its rename in a finally block, so + sharing across modules is safe. + """ + tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc") + project = tmp_path / "test" + gs.create_project(project) + with gs.setup.init(project, env=os.environ.copy()) as session: + tools = Tools(session=session, overwrite=True) + tools.g_gisenv(set="TGIS_USE_CURRENT_MAPSET=1") + tools.g_region(s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10) + for name, val in [("prec_1", 550), ("prec_2", 450), ("prec_3", 320)]: + tools.r_mapcalc(expression=f"{name} = rand(0, {val})", seed=1) + for name in ("precip_abs1", "precip_abs2"): + tools.t_create( + type="strds", + temporaltype="absolute", + output=name, + title="A test", + description="A test", + ) + tools.t_register( + flags="i", + type="raster", + input="precip_abs1", + maps="prec_1,prec_2,prec_3", + start="2001-01-01", + increment="3 months", + ) + tools.t_register( + type="raster", + input="precip_abs2", + maps="prec_1,prec_2,prec_3", + ) + yield session + + +@pytest.fixture +def assert_tinfo(): + """Return a helper that checks t.info -g output against expected key/value pairs.""" + + def _assert(tools, strds_name, expected): + actual = tools.t_info(type="strds", input=strds_name, flags="g").keyval + for key, value in expected.items(): + assert key in actual, f"missing key {key!r} in t.info output" + assert actual[key] == value, ( + f"{key}: expected {value!r}, got {actual[key]!r}" + ) + + return _assert diff --git a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py new file mode 100644 index 00000000000..c8ff99a41af --- /dev/null +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py @@ -0,0 +1,134 @@ +"""Test t.rast.mapcalc basic functionality. + +(C) 2025 by the GRASS Development Team +This program is free software under the GNU General Public +License (>=v2). Read the file COPYING that comes with GRASS +for details. +""" + +import pytest + +import grass.exceptions +from grass.tools import Tools + + +def test_basic_addition(mapcalc_session, assert_tinfo): + """Test basic addition of two STRDS with -n flag (register null maps).""" + tools = Tools(session=mapcalc_session, overwrite=True) + tools.t_rast_mapcalc( + flags="n", + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression="precip_abs1 + precip_abs2", + basename="new_prec", + method="equal", + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + { + "number_of_maps": 3, + "temporal_type": "absolute", + "name": "precip_abs3", + }, + ) + + +def test_division_with_three_inputs(mapcalc_session, assert_tinfo): + """Test division expression across three input STRDS. + + First creates precip_abs3 = precip_abs1 + precip_abs2, then runs the + test expression (precip_abs1 + precip_abs2) / precip_abs2 over all + three STRDS with the -s (sampling) flag. + """ + tools = Tools(session=mapcalc_session, overwrite=True) + tools.t_rast_mapcalc( + flags="n", + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression="precip_abs1 + precip_abs2", + basename="new_prec", + method="equal", + nprocs=3, + ) + tools.t_rast_mapcalc( + flags="s", + inputs="precip_abs1,precip_abs2,precip_abs3", + output="precip_abs4", + expression="(precip_abs1 + precip_abs2) / precip_abs2", + basename="new_prec", + method="equal", + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs4", + { + "number_of_maps": 3, + "temporal_type": "absolute", + "name": "precip_abs4", + }, + ) + + +def test_null_multiplication(mapcalc_session, assert_tinfo): + """Test multiplication with null() function using -s flag.""" + tools = Tools(session=mapcalc_session, overwrite=True) + tools.t_rast_mapcalc( + flags="s", + inputs="precip_abs1,precip_abs2", + output="precip_abs4", + expression="(precip_abs1 + precip_abs2) * null()", + basename="new_prec", + method="equal", + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs4", + {"name": "precip_abs4", "number_of_maps": 0}, + ) + + +def test_null_multiplication_with_null_flag(mapcalc_session, assert_tinfo): + """Test multiplication with null() function using -sn flags.""" + tools = Tools(session=mapcalc_session, overwrite=True) + tools.t_rast_mapcalc( + flags="sn", + inputs="precip_abs1,precip_abs2", + output="precip_abs4", + expression="(precip_abs1 + precip_abs2) * null()", + basename="new_prec", + method="equal", + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs4", + {"name": "precip_abs4", "number_of_maps": 3}, + ) + + +def test_failure_on_missing_map(mapcalc_session): + """Test that the module fails when a registered raster is missing. + + Hides prec_1 by renaming it so that the STRDS still references the name + but the raster does not resolve. The rename is restored in a finally + block so subsequent tests in the shared session see prec_1. + """ + tools = Tools(session=mapcalc_session, overwrite=True) + tools.g_rename(raster="prec_1,prec_1_hidden") + try: + with pytest.raises(grass.exceptions.CalledModuleError): + tools.t_rast_mapcalc( + flags="sn", + inputs="precip_abs1,precip_abs2", + output="precip_abs4", + expression="(precip_abs1 + precip_abs2) * null()", + basename="new_prec", + method="equal", + nprocs=3, + ) + finally: + tools.g_rename(raster="prec_1_hidden,prec_1") diff --git a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py new file mode 100644 index 00000000000..c953f3160d1 --- /dev/null +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py @@ -0,0 +1,125 @@ +"""Test t.rast.mapcalc temporal operators. + +(C) 2025 by the GRASS Development Team +This program is free software under the GNU General Public +License (>=v2). Read the file COPYING that comes with GRASS +for details. +""" + +from grass.tools import Tools + + +def test_start_time_end_time_operators(mapcalc_session, assert_tinfo): + """Test start_time() and end_time() temporal operators.""" + tools = Tools(session=mapcalc_session, overwrite=True) + tools.t_rast_mapcalc( + flags="sn", + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression="if(start_time() >= 0 && end_time() >= 0, (precip_abs1*td() + precip_abs2) / td(), null())", + basename="new_prec", + method="equal", + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + { + "number_of_maps": 3, + "temporal_type": "absolute", + "name": "precip_abs3", + }, + ) + + +def test_start_time_components(mapcalc_session, assert_tinfo): + """Test start_year(), start_month(), start_day(), start_hour(), start_minute(), start_second() operators.""" + tools = Tools(session=mapcalc_session, overwrite=True) + tools.t_rast_mapcalc( + flags="sn", + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression="if(start_year()>=0&&start_month()>=0&&start_day()>=0&&start_hour()>=0&&start_minute()>=0&&start_second()>=0, (precip_abs1*td() + precip_abs2) / td(), null())", + basename="new_prec", + method="equal", + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + {"number_of_maps": 3, "name": "precip_abs3"}, + ) + + +def test_end_time_components(mapcalc_session, assert_tinfo): + """Test end_year(), end_month(), end_day(), end_hour(), end_minute(), end_second() operators.""" + tools = Tools(session=mapcalc_session, overwrite=True) + tools.t_rast_mapcalc( + flags="sn", + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression="if(end_year()>=0&&end_month()>=0&&end_day()>=0&&end_hour()>=0&&end_minute()>=0&&end_second()>=0, (precip_abs1*td() + precip_abs2) / td(), null())", + basename="new_prec", + method="equal", + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + {"number_of_maps": 3, "name": "precip_abs3"}, + ) + + +def test_start_doy_dow_operators(mapcalc_session, assert_tinfo): + """Test start_doy() and start_dow() temporal operators.""" + tools = Tools(session=mapcalc_session, overwrite=True) + tools.t_rast_mapcalc( + flags="sn", + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression="if(start_doy() >= 0 && start_dow() >= 0, (precip_abs1*td() + precip_abs2) / td(), null())", + basename="new_prec", + method="equal", + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + {"number_of_maps": 3, "name": "precip_abs3"}, + ) + + +def test_end_doy_dow_operators(mapcalc_session, assert_tinfo): + """Test end_doy() and end_dow() temporal operators.""" + tools = Tools(session=mapcalc_session, overwrite=True) + tools.t_rast_mapcalc( + flags="sn", + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression="if(end_doy() >= 0 && end_dow() >= 0, (precip_abs1*td() + precip_abs2) / td(), null())", + basename="new_prec", + method="equal", + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + {"number_of_maps": 3, "name": "precip_abs3"}, + ) + + +def test_map_comparison_operator(mapcalc_session, assert_tinfo): + """Test comparison operator with map names.""" + tools = Tools(session=mapcalc_session, overwrite=True) + tools.t_rast_mapcalc( + flags="sn", + inputs="precip_abs1", + output="precip_abs3", + expression="if(precip_abs1 == prec_1, prec_1, null())", + basename="new_prec", + ) + assert_tinfo( + tools, + "precip_abs3", + {"name": "precip_abs3", "number_of_maps": 3}, + )