From bafc6a1b71164e20b5d7f551bd5261a4251c9402 Mon Sep 17 00:00:00 2001 From: sakirr Date: Sun, 25 Jan 2026 22:55:29 +0000 Subject: [PATCH 01/16] t.rast.mapcalc: migrate additional shell test to pytest --- .../t.rast.mapcalc/test.t.rast.mapcalc.sh | 5 - .../tests/test_t_rast_mapcalc_basic_pytest.py | 92 +++++++++++++++++++ 2 files changed, 92 insertions(+), 5 deletions(-) create mode 100644 temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic_pytest.py diff --git a/temporal/t.rast.mapcalc/test.t.rast.mapcalc.sh b/temporal/t.rast.mapcalc/test.t.rast.mapcalc.sh index da6a8564d83..16a5fc7c2c0 100755 --- a/temporal/t.rast.mapcalc/test.t.rast.mapcalc.sh +++ b/temporal/t.rast.mapcalc/test.t.rast.mapcalc.sh @@ -36,11 +36,6 @@ t.rast.mapcalc -s inputs=precip_abs1,precip_abs2,precip_abs3 output=precip_abs4 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 diff --git a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic_pytest.py b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic_pytest.py new file mode 100644 index 00000000000..5e7899e0ed9 --- /dev/null +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic_pytest.py @@ -0,0 +1,92 @@ +"""Test t.rast.mapcalc. + +(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. + +@author GRASS Development Team +""" + +import os +import shutil + +import pytest + +import grass.script as gs +from grass.tools import Tools + + +@pytest.fixture(scope="module") +def session(tmp_path_factory): + """Create a temporary GRASS project with STRDS.""" + tmp_path = tmp_path_factory.mktemp("raster_mapcalc_basic") + + if tmp_path.exists(): + shutil.rmtree(tmp_path) + + gs.create_project(tmp_path) + + with gs.setup.init( + tmp_path.parent, + location=tmp_path.name, + env=os.environ.copy(), + ) as session: + tools = Tools(session=session) + tools.g_mapset(mapset="perc", flags="c") + 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 i in range(1, 7): + mapname = f"prec_{i}" + tools.r_mapcalc(expression=f"{mapname} = {i}00", overwrite=True) + + tools.t_create( + type="strds", + temporaltype="absolute", + output="precip_abs1", + title="A test", + description="A test", + overwrite=True, + ) + tools.t_create( + type="strds", + temporaltype="absolute", + output="precip_abs2", + title="A test", + description="A test", + overwrite=True, + ) + tools.t_register( + 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", + flags="i", + overwrite=True, + ) + tools.t_register( + type="raster", + input="precip_abs2", + maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", + overwrite=True, + ) + + yield session + + +def test_multiplication_with_null(session): + """Test multiplication with null() using -s flag.""" + tools = Tools(session=session) + tools.t_rast_mapcalc( + inputs="precip_abs1,precip_abs2", + output="precip_abs4", + expression="(precip_abs1 + precip_abs2) * null()", + basename="new_prec", + method="equal", + nprocs=5, + overwrite=True, + ) + strds_info = tools.t_info(input="precip_abs4", type="strds", flags="g").keyval + assert "number_of_maps" in strds_info From f8212dc27107a8c951e99ec928fb668a85e48125 Mon Sep 17 00:00:00 2001 From: sakirr Date: Tue, 27 Jan 2026 18:06:45 +0000 Subject: [PATCH 02/16] t.rast.mapcalc: migrate remaining shell tests to pytest --- .../test.t.rast.mapcalc.operators.sh | 58 ----- .../t.rast.mapcalc/test.t.rast.mapcalc.sh | 51 ----- .../testsuite/test_t_rast_mapcalc_basic.py | 183 ++++++++++++++++ .../test_t_rast_mapcalc_operators.py | 206 ++++++++++++++++++ 4 files changed, 389 insertions(+), 109 deletions(-) delete mode 100755 temporal/t.rast.mapcalc/test.t.rast.mapcalc.operators.sh delete mode 100755 temporal/t.rast.mapcalc/test.t.rast.mapcalc.sh create mode 100644 temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py create mode 100644 temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py 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 16a5fc7c2c0..00000000000 --- a/temporal/t.rast.mapcalc/test.t.rast.mapcalc.sh +++ /dev/null @@ -1,51 +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 -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/testsuite/test_t_rast_mapcalc_basic.py b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py new file mode 100644 index 00000000000..20aaba4d09d --- /dev/null +++ b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py @@ -0,0 +1,183 @@ +"""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. + +@author GRASS Development Team +""" + +import os + +import pytest + +import grass.script as gs +from grass.exceptions import CalledModuleError +from grass.tools import Tools + + +@pytest.fixture(scope="module") +def session(tmp_path_factory): + """Pytest fixture to create a temporary GRASS project with STRDS. + + This setup initializes the GRASS environment, sets the computational region, + creates a series of raster maps, and registers them in STRDS datasets. + + Yields: + session: active GRASS session with the prepared project and STRDS. + """ + tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc_basic") + + gs.create_project(tmp_path) + + with gs.setup.init(tmp_path, env=os.environ.copy()) as session: + tools = Tools(session=session) + tools.g_region(s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10) + + # Generate data using rand() to match original shell test + tools.r_mapcalc(expression="prec_1 = rand(0, 550)", overwrite=True) + tools.r_mapcalc(expression="prec_2 = rand(0, 450)", overwrite=True) + tools.r_mapcalc(expression="prec_3 = rand(0, 320)", overwrite=True) + tools.r_mapcalc(expression="prec_4 = rand(0, 510)", overwrite=True) + tools.r_mapcalc(expression="prec_5 = rand(0, 300)", overwrite=True) + tools.r_mapcalc(expression="prec_6 = rand(0, 650)", overwrite=True) + + tools.t_create( + type="strds", + temporaltype="absolute", + output="precip_abs1", + title="A test", + description="A test", + overwrite=True, + ) + tools.t_create( + type="strds", + temporaltype="absolute", + output="precip_abs2", + title="A test", + description="A test", + overwrite=True, + ) + tools.t_register( + 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", + flags="i", + overwrite=True, + ) + tools.t_register( + type="raster", + input="precip_abs2", + maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", + overwrite=True, + ) + + yield session + + +def test_basic_addition(session): + """Test basic addition of two STRDS with -n flag (register null maps).""" + tools = Tools(session=session) + tools.t_rast_mapcalc( + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression=" precip_abs1 + precip_abs2", + basename="new_prec", + method="equal", + nprocs=5, + flags="n", + overwrite=True, + ) + # Verify output STRDS was created and has expected number of maps + strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval + assert strds_info["number_of_maps"] == 6, ( + f"Expected 6 maps, got {strds_info['number_of_maps']}" + ) + assert strds_info["name"] == "precip_abs3" + assert strds_info["temporal_type"] == "absolute" + + +def test_division_with_three_inputs(session): + """Test division expression with three input STRDS using -s flag.""" + tools = Tools(session=session) + tools.t_rast_mapcalc( + inputs="precip_abs1,precip_abs2,precip_abs3", + output="precip_abs4", + expression=" (precip_abs1 + precip_abs2) / precip_abs2", + basename="new_prec", + method="equal", + nprocs=5, + flags="s", + overwrite=True, + ) + # Verify output STRDS was created and has expected number of maps + strds_info = tools.t_info(type="strds", input="precip_abs4", flags="g").keyval + assert strds_info["number_of_maps"] == 6, ( + f"Expected 6 maps, got {strds_info['number_of_maps']}" + ) + assert strds_info["name"] == "precip_abs4" + assert strds_info["temporal_type"] == "absolute" + + +def test_null_multiplication(session): + """Test multiplication with null() function using -s flag.""" + tools = Tools(session=session) + tools.t_rast_mapcalc( + inputs="precip_abs1,precip_abs2", + output="precip_abs4", + expression=" (precip_abs1 + precip_abs2) * null()", + basename="new_prec", + method="equal", + nprocs=5, + flags="s", + overwrite=True, + ) + # Verify output STRDS was created + strds_info = tools.t_info(type="strds", input="precip_abs4", flags="g").keyval + assert strds_info["name"] == "precip_abs4" + # With null() multiplication, maps may be empty but STRDS should exist + assert "number_of_maps" in strds_info + + +def test_null_multiplication_with_null_flag(session): + """Test multiplication with null() function using -sn flags.""" + tools = Tools(session=session) + tools.t_rast_mapcalc( + inputs="precip_abs1,precip_abs2", + output="precip_abs4", + expression=" (precip_abs1 + precip_abs2) * null()", + basename="new_prec", + method="equal", + nprocs=5, + flags="sn", + overwrite=True, + ) + # Verify output STRDS was created + strds_info = tools.t_info(type="strds", input="precip_abs4", flags="g").keyval + assert strds_info["name"] == "precip_abs4" + # With -n flag, null maps should be registered + assert "number_of_maps" in strds_info + + +def test_failure_on_missing_map(session): + """Test that the module fails when a required map is missing.""" + tools = Tools(session=session) + # Remove a raster map that is required + tools.g_remove(flags="f", type="raster", name="prec_1", overwrite=True) + # This should fail + with pytest.raises(CalledModuleError): + tools.t_rast_mapcalc( + inputs="precip_abs1,precip_abs2", + output="precip_abs4", + expression=" (precip_abs1 + precip_abs2) * null()", + basename="new_prec", + method="equal", + nprocs=5, + flags="sn", + overwrite=True, + ) + # Recreate the map so other tests aren't affected + tools.r_mapcalc(expression="prec_1 = rand(0, 550)", overwrite=True) diff --git a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py new file mode 100644 index 00000000000..f3df8164d5f --- /dev/null +++ b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py @@ -0,0 +1,206 @@ +"""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. + +@author GRASS Development Team +""" + +import os + +import pytest + +import grass.script as gs +from grass.tools import Tools + + +@pytest.fixture(scope="module") +def session(tmp_path_factory): + """Pytest fixture to create a temporary GRASS project with STRDS. + + This setup initializes the GRASS environment, sets the computational region, + creates a series of raster maps, and registers them in STRDS datasets. + + Yields: + session: active GRASS session with the prepared project and STRDS. + """ + tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc_operators") + + gs.create_project(tmp_path) + + with gs.setup.init(tmp_path, env=os.environ.copy()) as session: + tools = Tools(session=session) + tools.g_region(s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10) + + # Generate data with -s flag (silent) to match original shell test + tools.r_mapcalc(expression="prec_1 = rand(0, 550)", flags="s", overwrite=True) + tools.r_mapcalc(expression="prec_2 = rand(0, 450)", flags="s", overwrite=True) + tools.r_mapcalc(expression="prec_3 = rand(0, 320)", flags="s", overwrite=True) + tools.r_mapcalc(expression="prec_4 = rand(0, 510)", flags="s", overwrite=True) + tools.r_mapcalc(expression="prec_5 = rand(0, 300)", flags="s", overwrite=True) + tools.r_mapcalc(expression="prec_6 = rand(0, 650)", flags="s", overwrite=True) + + tools.t_create( + type="strds", + temporaltype="absolute", + output="precip_abs1", + title="A test", + description="A test", + overwrite=True, + ) + tools.t_create( + type="strds", + temporaltype="absolute", + output="precip_abs2", + title="A test", + description="A test", + overwrite=True, + ) + tools.t_register( + 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", + flags="i", + overwrite=True, + ) + tools.t_register( + type="raster", + input="precip_abs2", + maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", + overwrite=True, + ) + + yield session + + +def test_start_time_end_time_operators(session): + """Test start_time() and end_time() temporal operators.""" + tools = Tools(session=session) + tools.t_rast_mapcalc( + 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=5, + flags="sn", + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval + assert strds_info["name"] == "precip_abs3" + assert strds_info["number_of_maps"] == 6, ( + f"Expected 6 maps, got {strds_info['number_of_maps']}" + ) + assert strds_info["temporal_type"] == "absolute" + + +def test_start_time_components(session): + """Test start_year(), start_month(), start_day(), start_hour(), start_minute(), start_second() operators.""" + tools = Tools(session=session) + tools.t_rast_mapcalc( + 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=5, + flags="sn", + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval + assert strds_info["name"] == "precip_abs3" + assert strds_info["number_of_maps"] == 6, ( + f"Expected 6 maps, got {strds_info['number_of_maps']}" + ) + + +def test_end_time_components(session): + """Test end_year(), end_month(), end_day(), end_hour(), end_minute(), end_second() operators.""" + tools = Tools(session=session) + tools.t_rast_mapcalc( + 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=5, + flags="sn", + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval + assert strds_info["name"] == "precip_abs3" + assert strds_info["number_of_maps"] == 6, ( + f"Expected 6 maps, got {strds_info['number_of_maps']}" + ) + + +def test_start_doy_dow_operators(session): + """Test start_doy() and start_dow() temporal operators.""" + tools = Tools(session=session) + tools.t_rast_mapcalc( + 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=5, + flags="sn", + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval + assert strds_info["name"] == "precip_abs3" + assert strds_info["number_of_maps"] == 6, ( + f"Expected 6 maps, got {strds_info['number_of_maps']}" + ) + + +def test_end_doy_dow_operators(session): + """Test end_doy() and end_dow() temporal operators.""" + tools = Tools(session=session) + tools.t_rast_mapcalc( + 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=5, + flags="sn", + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval + assert strds_info["name"] == "precip_abs3" + assert strds_info["number_of_maps"] == 6, ( + f"Expected 6 maps, got {strds_info['number_of_maps']}" + ) + + +def test_map_comparison_operator(session): + """Test comparison operator with map names.""" + tools = Tools(session=session) + tools.t_rast_mapcalc( + inputs="precip_abs1", + output="precip_abs3", + expression="if(precip_abs1 == prec_1, prec_1, null())", + basename="new_prec", + flags="sn", + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval + assert strds_info["name"] == "precip_abs3" + assert "number_of_maps" in strds_info From bf53070dc64ab80a58c5f296bca4e2c6f318e8a9 Mon Sep 17 00:00:00 2001 From: sakirr Date: Wed, 28 Jan 2026 11:25:49 +0000 Subject: [PATCH 03/16] tests(t.rast.mapcalc): replace pytest with grass.gunittest in temporal tests --- .../tests/test_t_rast_mapcalc_basic_pytest.py | 92 ----- .../testsuite/test_t_rast_mapcalc_basic.py | 266 +++++++------- .../test_t_rast_mapcalc_operators.py | 347 +++++++++--------- 3 files changed, 320 insertions(+), 385 deletions(-) delete mode 100644 temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic_pytest.py diff --git a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic_pytest.py b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic_pytest.py deleted file mode 100644 index 5e7899e0ed9..00000000000 --- a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic_pytest.py +++ /dev/null @@ -1,92 +0,0 @@ -"""Test t.rast.mapcalc. - -(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. - -@author GRASS Development Team -""" - -import os -import shutil - -import pytest - -import grass.script as gs -from grass.tools import Tools - - -@pytest.fixture(scope="module") -def session(tmp_path_factory): - """Create a temporary GRASS project with STRDS.""" - tmp_path = tmp_path_factory.mktemp("raster_mapcalc_basic") - - if tmp_path.exists(): - shutil.rmtree(tmp_path) - - gs.create_project(tmp_path) - - with gs.setup.init( - tmp_path.parent, - location=tmp_path.name, - env=os.environ.copy(), - ) as session: - tools = Tools(session=session) - tools.g_mapset(mapset="perc", flags="c") - 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 i in range(1, 7): - mapname = f"prec_{i}" - tools.r_mapcalc(expression=f"{mapname} = {i}00", overwrite=True) - - tools.t_create( - type="strds", - temporaltype="absolute", - output="precip_abs1", - title="A test", - description="A test", - overwrite=True, - ) - tools.t_create( - type="strds", - temporaltype="absolute", - output="precip_abs2", - title="A test", - description="A test", - overwrite=True, - ) - tools.t_register( - 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", - flags="i", - overwrite=True, - ) - tools.t_register( - type="raster", - input="precip_abs2", - maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", - overwrite=True, - ) - - yield session - - -def test_multiplication_with_null(session): - """Test multiplication with null() using -s flag.""" - tools = Tools(session=session) - tools.t_rast_mapcalc( - inputs="precip_abs1,precip_abs2", - output="precip_abs4", - expression="(precip_abs1 + precip_abs2) * null()", - basename="new_prec", - method="equal", - nprocs=5, - overwrite=True, - ) - strds_info = tools.t_info(input="precip_abs4", type="strds", flags="g").keyval - assert "number_of_maps" in strds_info diff --git a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py index 20aaba4d09d..759c937a294 100644 --- a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py +++ b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py @@ -8,42 +8,30 @@ @author GRASS Development Team """ -import os - -import pytest - import grass.script as gs -from grass.exceptions import CalledModuleError -from grass.tools import Tools - - -@pytest.fixture(scope="module") -def session(tmp_path_factory): - """Pytest fixture to create a temporary GRASS project with STRDS. +from grass.gunittest.case import TestCase +from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.main import test - This setup initializes the GRASS environment, sets the computational region, - creates a series of raster maps, and registers them in STRDS datasets. - Yields: - session: active GRASS session with the prepared project and STRDS. - """ - tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc_basic") - - gs.create_project(tmp_path) - - with gs.setup.init(tmp_path, env=os.environ.copy()) as session: - tools = Tools(session=session) - tools.g_region(s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10) +class TestRasterMapcalcBasic(TestCase): + @classmethod + def setUpClass(cls): + """Initiate the temporal GIS and set the region""" + cls.use_temp_region() + cls.runModule("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1") + cls.runModule("g.region", s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10) # Generate data using rand() to match original shell test - tools.r_mapcalc(expression="prec_1 = rand(0, 550)", overwrite=True) - tools.r_mapcalc(expression="prec_2 = rand(0, 450)", overwrite=True) - tools.r_mapcalc(expression="prec_3 = rand(0, 320)", overwrite=True) - tools.r_mapcalc(expression="prec_4 = rand(0, 510)", overwrite=True) - tools.r_mapcalc(expression="prec_5 = rand(0, 300)", overwrite=True) - tools.r_mapcalc(expression="prec_6 = rand(0, 650)", overwrite=True) - - tools.t_create( + cls.runModule("r.mapcalc", expression="prec_1 = rand(0, 550)", overwrite=True) + cls.runModule("r.mapcalc", expression="prec_2 = rand(0, 450)", overwrite=True) + cls.runModule("r.mapcalc", expression="prec_3 = rand(0, 320)", overwrite=True) + cls.runModule("r.mapcalc", expression="prec_4 = rand(0, 510)", overwrite=True) + cls.runModule("r.mapcalc", expression="prec_5 = rand(0, 300)", overwrite=True) + cls.runModule("r.mapcalc", expression="prec_6 = rand(0, 650)", overwrite=True) + + cls.runModule( + "t.create", type="strds", temporaltype="absolute", output="precip_abs1", @@ -51,7 +39,8 @@ def session(tmp_path_factory): description="A test", overwrite=True, ) - tools.t_create( + cls.runModule( + "t.create", type="strds", temporaltype="absolute", output="precip_abs2", @@ -59,125 +48,144 @@ def session(tmp_path_factory): description="A test", overwrite=True, ) - tools.t_register( + cls.runModule( + "t.register", + flags="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", - flags="i", overwrite=True, ) - tools.t_register( + cls.runModule( + "t.register", type="raster", input="precip_abs2", maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", overwrite=True, ) - yield session - - -def test_basic_addition(session): - """Test basic addition of two STRDS with -n flag (register null maps).""" - tools = Tools(session=session) - tools.t_rast_mapcalc( - inputs="precip_abs1,precip_abs2", - output="precip_abs3", - expression=" precip_abs1 + precip_abs2", - basename="new_prec", - method="equal", - nprocs=5, - flags="n", - overwrite=True, - ) - # Verify output STRDS was created and has expected number of maps - strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval - assert strds_info["number_of_maps"] == 6, ( - f"Expected 6 maps, got {strds_info['number_of_maps']}" - ) - assert strds_info["name"] == "precip_abs3" - assert strds_info["temporal_type"] == "absolute" - - -def test_division_with_three_inputs(session): - """Test division expression with three input STRDS using -s flag.""" - tools = Tools(session=session) - tools.t_rast_mapcalc( - inputs="precip_abs1,precip_abs2,precip_abs3", - output="precip_abs4", - expression=" (precip_abs1 + precip_abs2) / precip_abs2", - basename="new_prec", - method="equal", - nprocs=5, - flags="s", - overwrite=True, - ) - # Verify output STRDS was created and has expected number of maps - strds_info = tools.t_info(type="strds", input="precip_abs4", flags="g").keyval - assert strds_info["number_of_maps"] == 6, ( - f"Expected 6 maps, got {strds_info['number_of_maps']}" - ) - assert strds_info["name"] == "precip_abs4" - assert strds_info["temporal_type"] == "absolute" - - -def test_null_multiplication(session): - """Test multiplication with null() function using -s flag.""" - tools = Tools(session=session) - tools.t_rast_mapcalc( - inputs="precip_abs1,precip_abs2", - output="precip_abs4", - expression=" (precip_abs1 + precip_abs2) * null()", - basename="new_prec", - method="equal", - nprocs=5, - flags="s", - overwrite=True, - ) - # Verify output STRDS was created - strds_info = tools.t_info(type="strds", input="precip_abs4", flags="g").keyval - assert strds_info["name"] == "precip_abs4" - # With null() multiplication, maps may be empty but STRDS should exist - assert "number_of_maps" in strds_info - + @classmethod + def tearDownClass(cls): + """Remove the temporary region""" + cls.runModule( + "t.remove", + flags="rf", + type="strds", + inputs="precip_abs1,precip_abs2,precip_abs3,precip_abs4", + ) + cls.del_temp_region() -def test_null_multiplication_with_null_flag(session): - """Test multiplication with null() function using -sn flags.""" - tools = Tools(session=session) - tools.t_rast_mapcalc( - inputs="precip_abs1,precip_abs2", - output="precip_abs4", - expression=" (precip_abs1 + precip_abs2) * null()", - basename="new_prec", - method="equal", - nprocs=5, - flags="sn", - overwrite=True, - ) - # Verify output STRDS was created - strds_info = tools.t_info(type="strds", input="precip_abs4", flags="g").keyval - assert strds_info["name"] == "precip_abs4" - # With -n flag, null maps should be registered - assert "number_of_maps" in strds_info + def test_basic_addition(self): + """Test basic addition of two STRDS with -n flag (register null maps).""" + self.assertModule( + "t.rast.mapcalc", + flags="n", + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression=" precip_abs1 + precip_abs2", + basename="new_prec", + method="equal", + nprocs=5, + overwrite=True, + ) + # Verify output STRDS was created and has expected number of maps + tinfo_string = """number_of_maps=6 +temporal_type=absolute +name=precip_abs3""" + info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") + self.assertModuleKeyValue( + module=info, reference=tinfo_string, precision=2, sep="=" + ) + def test_division_with_three_inputs(self): + """Test division expression with three input STRDS using -s flag.""" + self.assertModule( + "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=5, + overwrite=True, + ) + # Verify output STRDS was created and has expected number of maps + tinfo_string = """number_of_maps=6 +temporal_type=absolute +name=precip_abs4""" + info = SimpleModule("t.info", type="strds", input="precip_abs4", flags="g") + self.assertModuleKeyValue( + module=info, reference=tinfo_string, precision=2, sep="=" + ) -def test_failure_on_missing_map(session): - """Test that the module fails when a required map is missing.""" - tools = Tools(session=session) - # Remove a raster map that is required - tools.g_remove(flags="f", type="raster", name="prec_1", overwrite=True) - # This should fail - with pytest.raises(CalledModuleError): - tools.t_rast_mapcalc( + def test_null_multiplication(self): + """Test multiplication with null() function using -s flag.""" + self.assertModule( + "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=5, + overwrite=True, + ) + # Verify output STRDS was created + info = SimpleModule("t.info", type="strds", input="precip_abs4", flags="g") + info.run() + self.assertEqual(info.returncode, 0) + strds_info = gs.parse_key_val(info.outputs.stdout, sep="=") + self.assertEqual(strds_info["name"], "precip_abs4") + # With null() multiplication, maps may be empty but STRDS should exist + self.assertIn("number_of_maps", strds_info) + + def test_null_multiplication_with_null_flag(self): + """Test multiplication with null() function using -sn flags.""" + self.assertModule( + "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=5, + overwrite=True, + ) + # Verify output STRDS was created + info = SimpleModule("t.info", type="strds", input="precip_abs4", flags="g") + info.run() + self.assertEqual(info.returncode, 0) + strds_info = gs.parse_key_val(info.outputs.stdout, sep="=") + self.assertEqual(strds_info["name"], "precip_abs4") + # With -n flag, null maps should be registered + self.assertIn("number_of_maps", strds_info) + + def test_failure_on_missing_map(self): + """Test that the module fails when a required map is missing.""" + # Remove a raster map that is required + self.runModule( + "g.remove", flags="f", type="raster", name="prec_1", overwrite=True + ) + # This should fail + self.assertModuleFail( + "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=5, overwrite=True, ) - # Recreate the map so other tests aren't affected - tools.r_mapcalc(expression="prec_1 = rand(0, 550)", overwrite=True) + # Recreate the map so other tests aren't affected + self.runModule("r.mapcalc", expression="prec_1 = rand(0, 550)", overwrite=True) + + +if __name__ == "__main__": + test() diff --git a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py index f3df8164d5f..577bd8a4ae4 100644 --- a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py +++ b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py @@ -8,41 +8,42 @@ @author GRASS Development Team """ -import os - -import pytest - import grass.script as gs -from grass.tools import Tools - - -@pytest.fixture(scope="module") -def session(tmp_path_factory): - """Pytest fixture to create a temporary GRASS project with STRDS. - - This setup initializes the GRASS environment, sets the computational region, - creates a series of raster maps, and registers them in STRDS datasets. - - Yields: - session: active GRASS session with the prepared project and STRDS. - """ - tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc_operators") +from grass.gunittest.case import TestCase +from grass.gunittest.gmodules import SimpleModule +from grass.gunittest.main import test - gs.create_project(tmp_path) - with gs.setup.init(tmp_path, env=os.environ.copy()) as session: - tools = Tools(session=session) - tools.g_region(s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10) +class TestRasterMapcalcOperators(TestCase): + @classmethod + def setUpClass(cls): + """Initiate the temporal GIS and set the region""" + cls.use_temp_region() + cls.runModule("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1") + cls.runModule("g.region", s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10) # Generate data with -s flag (silent) to match original shell test - tools.r_mapcalc(expression="prec_1 = rand(0, 550)", flags="s", overwrite=True) - tools.r_mapcalc(expression="prec_2 = rand(0, 450)", flags="s", overwrite=True) - tools.r_mapcalc(expression="prec_3 = rand(0, 320)", flags="s", overwrite=True) - tools.r_mapcalc(expression="prec_4 = rand(0, 510)", flags="s", overwrite=True) - tools.r_mapcalc(expression="prec_5 = rand(0, 300)", flags="s", overwrite=True) - tools.r_mapcalc(expression="prec_6 = rand(0, 650)", flags="s", overwrite=True) - - tools.t_create( + cls.runModule( + "r.mapcalc", expression="prec_1 = rand(0, 550)", flags="s", overwrite=True + ) + cls.runModule( + "r.mapcalc", expression="prec_2 = rand(0, 450)", flags="s", overwrite=True + ) + cls.runModule( + "r.mapcalc", expression="prec_3 = rand(0, 320)", flags="s", overwrite=True + ) + cls.runModule( + "r.mapcalc", expression="prec_4 = rand(0, 510)", flags="s", overwrite=True + ) + cls.runModule( + "r.mapcalc", expression="prec_5 = rand(0, 300)", flags="s", overwrite=True + ) + cls.runModule( + "r.mapcalc", expression="prec_6 = rand(0, 650)", flags="s", overwrite=True + ) + + cls.runModule( + "t.create", type="strds", temporaltype="absolute", output="precip_abs1", @@ -50,7 +51,8 @@ def session(tmp_path_factory): description="A test", overwrite=True, ) - tools.t_create( + cls.runModule( + "t.create", type="strds", temporaltype="absolute", output="precip_abs2", @@ -58,149 +60,166 @@ def session(tmp_path_factory): description="A test", overwrite=True, ) - tools.t_register( + cls.runModule( + "t.register", + flags="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", - flags="i", overwrite=True, ) - tools.t_register( + cls.runModule( + "t.register", type="raster", input="precip_abs2", maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", overwrite=True, ) - yield session - - -def test_start_time_end_time_operators(session): - """Test start_time() and end_time() temporal operators.""" - tools = Tools(session=session) - tools.t_rast_mapcalc( - 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=5, - flags="sn", - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval - assert strds_info["name"] == "precip_abs3" - assert strds_info["number_of_maps"] == 6, ( - f"Expected 6 maps, got {strds_info['number_of_maps']}" - ) - assert strds_info["temporal_type"] == "absolute" - - -def test_start_time_components(session): - """Test start_year(), start_month(), start_day(), start_hour(), start_minute(), start_second() operators.""" - tools = Tools(session=session) - tools.t_rast_mapcalc( - 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=5, - flags="sn", - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval - assert strds_info["name"] == "precip_abs3" - assert strds_info["number_of_maps"] == 6, ( - f"Expected 6 maps, got {strds_info['number_of_maps']}" - ) - - -def test_end_time_components(session): - """Test end_year(), end_month(), end_day(), end_hour(), end_minute(), end_second() operators.""" - tools = Tools(session=session) - tools.t_rast_mapcalc( - 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=5, - flags="sn", - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval - assert strds_info["name"] == "precip_abs3" - assert strds_info["number_of_maps"] == 6, ( - f"Expected 6 maps, got {strds_info['number_of_maps']}" - ) - - -def test_start_doy_dow_operators(session): - """Test start_doy() and start_dow() temporal operators.""" - tools = Tools(session=session) - tools.t_rast_mapcalc( - 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=5, - flags="sn", - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval - assert strds_info["name"] == "precip_abs3" - assert strds_info["number_of_maps"] == 6, ( - f"Expected 6 maps, got {strds_info['number_of_maps']}" - ) - - -def test_end_doy_dow_operators(session): - """Test end_doy() and end_dow() temporal operators.""" - tools = Tools(session=session) - tools.t_rast_mapcalc( - 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=5, - flags="sn", - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval - assert strds_info["name"] == "precip_abs3" - assert strds_info["number_of_maps"] == 6, ( - f"Expected 6 maps, got {strds_info['number_of_maps']}" - ) - - -def test_map_comparison_operator(session): - """Test comparison operator with map names.""" - tools = Tools(session=session) - tools.t_rast_mapcalc( - inputs="precip_abs1", - output="precip_abs3", - expression="if(precip_abs1 == prec_1, prec_1, null())", - basename="new_prec", - flags="sn", - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - strds_info = tools.t_info(type="strds", input="precip_abs3", flags="g").keyval - assert strds_info["name"] == "precip_abs3" - assert "number_of_maps" in strds_info + @classmethod + def tearDownClass(cls): + """Remove the temporary region""" + cls.runModule( + "t.remove", + flags="rf", + type="strds", + inputs="precip_abs1,precip_abs2,precip_abs3", + ) + cls.del_temp_region() + + def test_start_time_end_time_operators(self): + """Test start_time() and end_time() temporal operators.""" + self.assertModule( + "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=5, + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + tinfo_string = """number_of_maps=6 +temporal_type=absolute +name=precip_abs3""" + info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") + self.assertModuleKeyValue( + module=info, reference=tinfo_string, precision=2, sep="=" + ) + + def test_start_time_components(self): + """Test start_year(), start_month(), start_day(), start_hour(), start_minute(), start_second() operators.""" + self.assertModule( + "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=5, + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + tinfo_string = """number_of_maps=6 +name=precip_abs3""" + info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") + self.assertModuleKeyValue( + module=info, reference=tinfo_string, precision=2, sep="=" + ) + + def test_end_time_components(self): + """Test end_year(), end_month(), end_day(), end_hour(), end_minute(), end_second() operators.""" + self.assertModule( + "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=5, + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + tinfo_string = """number_of_maps=6 +name=precip_abs3""" + info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") + self.assertModuleKeyValue( + module=info, reference=tinfo_string, precision=2, sep="=" + ) + + def test_start_doy_dow_operators(self): + """Test start_doy() and start_dow() temporal operators.""" + self.assertModule( + "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=5, + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + tinfo_string = """number_of_maps=6 +name=precip_abs3""" + info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") + self.assertModuleKeyValue( + module=info, reference=tinfo_string, precision=2, sep="=" + ) + + def test_end_doy_dow_operators(self): + """Test end_doy() and end_dow() temporal operators.""" + self.assertModule( + "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=5, + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + tinfo_string = """number_of_maps=6 +name=precip_abs3""" + info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") + self.assertModuleKeyValue( + module=info, reference=tinfo_string, precision=2, sep="=" + ) + + def test_map_comparison_operator(self): + """Test comparison operator with map names.""" + self.assertModule( + "t.rast.mapcalc", + flags="sn", + inputs="precip_abs1", + output="precip_abs3", + expression="if(precip_abs1 == prec_1, prec_1, null())", + basename="new_prec", + verbose=True, + overwrite=True, + ) + # Verify output STRDS was created + info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") + info.run() + self.assertEqual(info.returncode, 0) + strds_info = gs.parse_key_val(info.outputs.stdout, sep="=") + self.assertEqual(strds_info["name"], "precip_abs3") + self.assertIn("number_of_maps", strds_info) + + +if __name__ == "__main__": + test() From 09ed4f3acac93df3331d630c377065488c7ad32e Mon Sep 17 00:00:00 2001 From: sakirr Date: Wed, 28 Jan 2026 11:29:13 +0000 Subject: [PATCH 04/16] tests(t.rast.mapcalc): explicitly remove temporal datasets in tearDownClass --- temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py | 2 ++ .../t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py index 759c937a294..e2193402661 100644 --- a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py +++ b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py @@ -75,6 +75,8 @@ def tearDownClass(cls): type="strds", inputs="precip_abs1,precip_abs2,precip_abs3,precip_abs4", ) + cls.runModule("g.remove", flags="f", type="raster", pattern="prec_*") + cls.runModule("g.remove", flags="f", type="raster", pattern="new_prec_*") cls.del_temp_region() def test_basic_addition(self): diff --git a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py index 577bd8a4ae4..29133d8eafc 100644 --- a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py +++ b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py @@ -87,6 +87,8 @@ def tearDownClass(cls): type="strds", inputs="precip_abs1,precip_abs2,precip_abs3", ) + cls.runModule("g.remove", flags="f", type="raster", pattern="prec_*") + cls.runModule("g.remove", flags="f", type="raster", pattern="new_prec_*") cls.del_temp_region() def test_start_time_end_time_operators(self): From bda705aa5046dac6e04a133418a87cfd208c6190 Mon Sep 17 00:00:00 2001 From: sakirr Date: Wed, 28 Jan 2026 20:51:24 +0000 Subject: [PATCH 05/16] t.rast.mapcalc: fix pytest failures and temporal cleanup Fix incorrect map count assertions, ensure proper STRDS cleanup, and align tests with actual t.rast.mapcalc behavior across CI environments. --- .../testsuite/test_t_rast_mapcalc_basic.py | 29 +++++++++++++++---- .../test_t_rast_mapcalc_operators.py | 14 ++++++--- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py index e2193402661..cedeea8b8d1 100644 --- a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py +++ b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py @@ -74,9 +74,14 @@ def tearDownClass(cls): flags="rf", type="strds", inputs="precip_abs1,precip_abs2,precip_abs3,precip_abs4", + quiet=True, + ) + cls.runModule( + "g.remove", flags="f", type="raster", pattern="prec_*", quiet=True + ) + cls.runModule( + "g.remove", flags="f", type="raster", pattern="new_prec_*", quiet=True ) - cls.runModule("g.remove", flags="f", type="raster", pattern="prec_*") - cls.runModule("g.remove", flags="f", type="raster", pattern="new_prec_*") cls.del_temp_region() def test_basic_addition(self): @@ -103,6 +108,18 @@ def test_basic_addition(self): def test_division_with_three_inputs(self): """Test division expression with three input STRDS using -s flag.""" + # Create precip_abs3 first to make this test independent + self.assertModule( + "t.rast.mapcalc", + flags="n", + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression=" precip_abs1 + precip_abs2", + basename="new_prec", + method="equal", + nprocs=5, + overwrite=True, + ) self.assertModule( "t.rast.mapcalc", flags="s", @@ -142,8 +159,8 @@ def test_null_multiplication(self): self.assertEqual(info.returncode, 0) strds_info = gs.parse_key_val(info.outputs.stdout, sep="=") self.assertEqual(strds_info["name"], "precip_abs4") - # With null() multiplication, maps may be empty but STRDS should exist - self.assertIn("number_of_maps", strds_info) + # With null() multiplication without -n flag, null maps are not registered + self.assertEqual(int(strds_info["number_of_maps"]), 0) def test_null_multiplication_with_null_flag(self): """Test multiplication with null() function using -sn flags.""" @@ -164,8 +181,8 @@ def test_null_multiplication_with_null_flag(self): self.assertEqual(info.returncode, 0) strds_info = gs.parse_key_val(info.outputs.stdout, sep="=") self.assertEqual(strds_info["name"], "precip_abs4") - # With -n flag, null maps should be registered - self.assertIn("number_of_maps", strds_info) + # With -n flag, null maps are registered, so should have 6 maps + self.assertEqual(int(strds_info["number_of_maps"]), 6) def test_failure_on_missing_map(self): """Test that the module fails when a required map is missing.""" diff --git a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py index 29133d8eafc..5b24f05c636 100644 --- a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py +++ b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py @@ -22,7 +22,7 @@ def setUpClass(cls): cls.runModule("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1") cls.runModule("g.region", s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10) - # Generate data with -s flag (silent) to match original shell test + # Generate data cls.runModule( "r.mapcalc", expression="prec_1 = rand(0, 550)", flags="s", overwrite=True ) @@ -86,9 +86,14 @@ def tearDownClass(cls): flags="rf", type="strds", inputs="precip_abs1,precip_abs2,precip_abs3", + quiet=True, + ) + cls.runModule( + "g.remove", flags="f", type="raster", pattern="prec_*", quiet=True + ) + cls.runModule( + "g.remove", flags="f", type="raster", pattern="new_prec_*", quiet=True ) - cls.runModule("g.remove", flags="f", type="raster", pattern="prec_*") - cls.runModule("g.remove", flags="f", type="raster", pattern="new_prec_*") cls.del_temp_region() def test_start_time_end_time_operators(self): @@ -220,7 +225,8 @@ def test_map_comparison_operator(self): self.assertEqual(info.returncode, 0) strds_info = gs.parse_key_val(info.outputs.stdout, sep="=") self.assertEqual(strds_info["name"], "precip_abs3") - self.assertIn("number_of_maps", strds_info) + # With -n flag, null maps are registered, so should have 6 maps + self.assertEqual(int(strds_info["number_of_maps"]), 6) if __name__ == "__main__": From 054669c7e4c741d31a0b1a6fdaf2b51242003961 Mon Sep 17 00:00:00 2001 From: sakirr Date: Thu, 29 Jan 2026 09:20:34 +0000 Subject: [PATCH 06/16] tests(t.rast.mapcalc): convert tests to pytest and place in correct directory --- temporal/t.rast.mapcalc/tests/conftest.py | 159 ++++++++++++ .../tests/test_t_rast_mapcalc_basic.py | 154 ++++++++++++ .../tests/test_t_rast_mapcalc_operators.py | 155 ++++++++++++ .../testsuite/test_t_rast_mapcalc_basic.py | 210 ---------------- .../test_t_rast_mapcalc_operators.py | 233 ------------------ 5 files changed, 468 insertions(+), 443 deletions(-) create mode 100644 temporal/t.rast.mapcalc/tests/conftest.py create mode 100644 temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py create mode 100644 temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py delete mode 100644 temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py delete mode 100644 temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py diff --git a/temporal/t.rast.mapcalc/tests/conftest.py b/temporal/t.rast.mapcalc/tests/conftest.py new file mode 100644 index 00000000000..d03e6bfa384 --- /dev/null +++ b/temporal/t.rast.mapcalc/tests/conftest.py @@ -0,0 +1,159 @@ +"""Fixtures for t.rast.mapcalc tests.""" + +import os +from types import SimpleNamespace + +import pytest + +import grass.script as gs + + +def _setup_mapcalc_session(tmp_path_factory, use_seed): + """Create a temporary GRASS project with STRDS data for t.rast.mapcalc tests.""" + 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: + env = session.env + gs.run_command("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1", env=env) + gs.run_command( + "g.region", + s=0, + n=80, + w=0, + e=120, + b=0, + t=50, + res=10, + res3=10, + env=env, + ) + if use_seed: + for name, val in [ + ("prec_1", 550), + ("prec_2", 450), + ("prec_3", 320), + ("prec_4", 510), + ("prec_5", 300), + ("prec_6", 650), + ]: + gs.run_command( + "r.mapcalc", + expression=f"{name} = rand(0, {val})", + flags="s", + overwrite=True, + env=env, + ) + else: + gs.run_command( + "r.mapcalc", + expression="prec_1 = rand(0, 550)", + overwrite=True, + env=env, + ) + gs.run_command( + "r.mapcalc", + expression="prec_2 = rand(0, 450)", + overwrite=True, + env=env, + ) + gs.run_command( + "r.mapcalc", + expression="prec_3 = rand(0, 320)", + overwrite=True, + env=env, + ) + gs.run_command( + "r.mapcalc", + expression="prec_4 = rand(0, 510)", + overwrite=True, + env=env, + ) + gs.run_command( + "r.mapcalc", + expression="prec_5 = rand(0, 300)", + overwrite=True, + env=env, + ) + gs.run_command( + "r.mapcalc", + expression="prec_6 = rand(0, 650)", + overwrite=True, + env=env, + ) + gs.run_command( + "t.create", + type="strds", + temporaltype="absolute", + output="precip_abs1", + title="A test", + description="A test", + overwrite=True, + env=env, + ) + gs.run_command( + "t.create", + type="strds", + temporaltype="absolute", + output="precip_abs2", + title="A test", + description="A test", + overwrite=True, + env=env, + ) + gs.run_command( + "t.register", + flags="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", + overwrite=True, + env=env, + ) + gs.run_command( + "t.register", + type="raster", + input="precip_abs2", + maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", + overwrite=True, + env=env, + ) + yield SimpleNamespace(env=env) + gs.run_command( + "t.remove", + flags="rf", + type="strds", + inputs="precip_abs1,precip_abs2,precip_abs3,precip_abs4", + quiet=True, + env=env, + ) + gs.run_command( + "g.remove", + flags="f", + type="raster", + pattern="prec_*", + quiet=True, + env=env, + ) + gs.run_command( + "g.remove", + flags="f", + type="raster", + pattern="new_prec_*", + quiet=True, + env=env, + ) + + +@pytest.fixture(scope="module") +def mapcalc_session_basic(tmp_path_factory): + """Session with STRDS data for basic tests (rand without seed).""" + yield from _setup_mapcalc_session(tmp_path_factory, use_seed=False) + + +@pytest.fixture(scope="module") +def mapcalc_session_operators(tmp_path_factory): + """Session with STRDS data for operator tests (rand with seed).""" + yield from _setup_mapcalc_session(tmp_path_factory, use_seed=True) 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..e2a5b8ee8ec --- /dev/null +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py @@ -0,0 +1,154 @@ +"""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. + +@author GRASS Development Team +""" + +import pytest + +import grass.script as gs +from grass.exceptions import CalledModuleError + + +def _assert_tinfo_key_value(env, strds_name, reference_string, sep="="): + """Parse t.info output and assert key-value pairs match reference string.""" + output = gs.read_command( + "t.info", type="strds", input=strds_name, flags="g", env=env + ) + actual = gs.parse_key_val(output, sep=sep) + reference = dict( + line.strip().split(sep, 1) + for line in reference_string.strip().split("\n") + if sep in line + ) + for key, value in reference.items(): + assert key in actual, f"Missing key: {key}" + assert actual[key] == value, f"{key}: expected {value!r}, got {actual[key]!r}" + + +def test_basic_addition(mapcalc_session_basic): + """Test basic addition of two STRDS with -n flag (register null maps).""" + env = mapcalc_session_basic.env + gs.run_command( + "t.rast.mapcalc", + flags="n", + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression=" precip_abs1 + precip_abs2", + basename="new_prec", + method="equal", + nprocs=5, + overwrite=True, + env=env, + ) + tinfo_string = """number_of_maps=6 +temporal_type=absolute +name=precip_abs3""" + _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + + +def test_division_with_three_inputs(mapcalc_session_basic): + """Test division expression with three input STRDS using -s flag.""" + env = mapcalc_session_basic.env + gs.run_command( + "t.rast.mapcalc", + flags="n", + inputs="precip_abs1,precip_abs2", + output="precip_abs3", + expression=" precip_abs1 + precip_abs2", + basename="new_prec", + method="equal", + nprocs=5, + overwrite=True, + env=env, + ) + gs.run_command( + "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=5, + overwrite=True, + env=env, + ) + tinfo_string = """number_of_maps=6 +temporal_type=absolute +name=precip_abs4""" + _assert_tinfo_key_value(env, "precip_abs4", tinfo_string) + + +def test_null_multiplication(mapcalc_session_basic): + """Test multiplication with null() function using -s flag.""" + env = mapcalc_session_basic.env + gs.run_command( + "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=5, + overwrite=True, + env=env, + ) + output = gs.read_command( + "t.info", type="strds", input="precip_abs4", flags="g", env=env + ) + strds_info = gs.parse_key_val(output, sep="=") + assert strds_info["name"] == "precip_abs4" + assert int(strds_info["number_of_maps"]) == 0 + + +def test_null_multiplication_with_null_flag(mapcalc_session_basic): + """Test multiplication with null() function using -sn flags.""" + env = mapcalc_session_basic.env + gs.run_command( + "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=5, + overwrite=True, + env=env, + ) + output = gs.read_command( + "t.info", type="strds", input="precip_abs4", flags="g", env=env + ) + strds_info = gs.parse_key_val(output, sep="=") + assert strds_info["name"] == "precip_abs4" + assert int(strds_info["number_of_maps"]) == 6 + + +def test_failure_on_missing_map(mapcalc_session_basic): + """Test that the module fails when a required map is missing.""" + env = mapcalc_session_basic.env + gs.run_command( + "g.remove", flags="f", type="raster", name="prec_1", overwrite=True, env=env + ) + with pytest.raises(CalledModuleError): + gs.run_command( + "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=5, + overwrite=True, + env=env, + ) + gs.run_command( + "r.mapcalc", expression="prec_1 = rand(0, 550)", overwrite=True, env=env + ) 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..1eeb3f080d7 --- /dev/null +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py @@ -0,0 +1,155 @@ +"""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. + +@author GRASS Development Team +""" + +import grass.script as gs + + +def _assert_tinfo_key_value(env, strds_name, reference_string, sep="="): + """Parse t.info output and assert key-value pairs match reference string.""" + output = gs.read_command( + "t.info", type="strds", input=strds_name, flags="g", env=env + ) + actual = gs.parse_key_val(output, sep=sep) + reference = dict( + line.strip().split(sep, 1) + for line in reference_string.strip().split("\n") + if sep in line + ) + for key, value in reference.items(): + assert key in actual, f"Missing key: {key}" + assert actual[key] == value, f"{key}: expected {value!r}, got {actual[key]!r}" + + +def test_start_time_end_time_operators(mapcalc_session_operators): + """Test start_time() and end_time() temporal operators.""" + env = mapcalc_session_operators.env + gs.run_command( + "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=5, + verbose=True, + overwrite=True, + env=env, + ) + tinfo_string = """number_of_maps=6 +temporal_type=absolute +name=precip_abs3""" + _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + + +def test_start_time_components(mapcalc_session_operators): + """Test start_year(), start_month(), start_day(), start_hour(), start_minute(), start_second() operators.""" + env = mapcalc_session_operators.env + gs.run_command( + "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=5, + verbose=True, + overwrite=True, + env=env, + ) + tinfo_string = """number_of_maps=6 +name=precip_abs3""" + _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + + +def test_end_time_components(mapcalc_session_operators): + """Test end_year(), end_month(), end_day(), end_hour(), end_minute(), end_second() operators.""" + env = mapcalc_session_operators.env + gs.run_command( + "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=5, + verbose=True, + overwrite=True, + env=env, + ) + tinfo_string = """number_of_maps=6 +name=precip_abs3""" + _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + + +def test_start_doy_dow_operators(mapcalc_session_operators): + """Test start_doy() and start_dow() temporal operators.""" + env = mapcalc_session_operators.env + gs.run_command( + "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=5, + verbose=True, + overwrite=True, + env=env, + ) + tinfo_string = """number_of_maps=6 +name=precip_abs3""" + _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + + +def test_end_doy_dow_operators(mapcalc_session_operators): + """Test end_doy() and end_dow() temporal operators.""" + env = mapcalc_session_operators.env + gs.run_command( + "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=5, + verbose=True, + overwrite=True, + env=env, + ) + tinfo_string = """number_of_maps=6 +name=precip_abs3""" + _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + + +def test_map_comparison_operator(mapcalc_session_operators): + """Test comparison operator with map names.""" + env = mapcalc_session_operators.env + gs.run_command( + "t.rast.mapcalc", + flags="sn", + inputs="precip_abs1", + output="precip_abs3", + expression="if(precip_abs1 == prec_1, prec_1, null())", + basename="new_prec", + verbose=True, + overwrite=True, + env=env, + ) + output = gs.read_command( + "t.info", type="strds", input="precip_abs3", flags="g", env=env + ) + strds_info = gs.parse_key_val(output, sep="=") + assert strds_info["name"] == "precip_abs3" + assert int(strds_info["number_of_maps"]) == 6 diff --git a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py deleted file mode 100644 index cedeea8b8d1..00000000000 --- a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_basic.py +++ /dev/null @@ -1,210 +0,0 @@ -"""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. - -@author GRASS Development Team -""" - -import grass.script as gs -from grass.gunittest.case import TestCase -from grass.gunittest.gmodules import SimpleModule -from grass.gunittest.main import test - - -class TestRasterMapcalcBasic(TestCase): - @classmethod - def setUpClass(cls): - """Initiate the temporal GIS and set the region""" - cls.use_temp_region() - cls.runModule("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1") - cls.runModule("g.region", s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10) - - # Generate data using rand() to match original shell test - cls.runModule("r.mapcalc", expression="prec_1 = rand(0, 550)", overwrite=True) - cls.runModule("r.mapcalc", expression="prec_2 = rand(0, 450)", overwrite=True) - cls.runModule("r.mapcalc", expression="prec_3 = rand(0, 320)", overwrite=True) - cls.runModule("r.mapcalc", expression="prec_4 = rand(0, 510)", overwrite=True) - cls.runModule("r.mapcalc", expression="prec_5 = rand(0, 300)", overwrite=True) - cls.runModule("r.mapcalc", expression="prec_6 = rand(0, 650)", overwrite=True) - - cls.runModule( - "t.create", - type="strds", - temporaltype="absolute", - output="precip_abs1", - title="A test", - description="A test", - overwrite=True, - ) - cls.runModule( - "t.create", - type="strds", - temporaltype="absolute", - output="precip_abs2", - title="A test", - description="A test", - overwrite=True, - ) - cls.runModule( - "t.register", - flags="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", - overwrite=True, - ) - cls.runModule( - "t.register", - type="raster", - input="precip_abs2", - maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", - overwrite=True, - ) - - @classmethod - def tearDownClass(cls): - """Remove the temporary region""" - cls.runModule( - "t.remove", - flags="rf", - type="strds", - inputs="precip_abs1,precip_abs2,precip_abs3,precip_abs4", - quiet=True, - ) - cls.runModule( - "g.remove", flags="f", type="raster", pattern="prec_*", quiet=True - ) - cls.runModule( - "g.remove", flags="f", type="raster", pattern="new_prec_*", quiet=True - ) - cls.del_temp_region() - - def test_basic_addition(self): - """Test basic addition of two STRDS with -n flag (register null maps).""" - self.assertModule( - "t.rast.mapcalc", - flags="n", - inputs="precip_abs1,precip_abs2", - output="precip_abs3", - expression=" precip_abs1 + precip_abs2", - basename="new_prec", - method="equal", - nprocs=5, - overwrite=True, - ) - # Verify output STRDS was created and has expected number of maps - tinfo_string = """number_of_maps=6 -temporal_type=absolute -name=precip_abs3""" - info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") - self.assertModuleKeyValue( - module=info, reference=tinfo_string, precision=2, sep="=" - ) - - def test_division_with_three_inputs(self): - """Test division expression with three input STRDS using -s flag.""" - # Create precip_abs3 first to make this test independent - self.assertModule( - "t.rast.mapcalc", - flags="n", - inputs="precip_abs1,precip_abs2", - output="precip_abs3", - expression=" precip_abs1 + precip_abs2", - basename="new_prec", - method="equal", - nprocs=5, - overwrite=True, - ) - self.assertModule( - "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=5, - overwrite=True, - ) - # Verify output STRDS was created and has expected number of maps - tinfo_string = """number_of_maps=6 -temporal_type=absolute -name=precip_abs4""" - info = SimpleModule("t.info", type="strds", input="precip_abs4", flags="g") - self.assertModuleKeyValue( - module=info, reference=tinfo_string, precision=2, sep="=" - ) - - def test_null_multiplication(self): - """Test multiplication with null() function using -s flag.""" - self.assertModule( - "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=5, - overwrite=True, - ) - # Verify output STRDS was created - info = SimpleModule("t.info", type="strds", input="precip_abs4", flags="g") - info.run() - self.assertEqual(info.returncode, 0) - strds_info = gs.parse_key_val(info.outputs.stdout, sep="=") - self.assertEqual(strds_info["name"], "precip_abs4") - # With null() multiplication without -n flag, null maps are not registered - self.assertEqual(int(strds_info["number_of_maps"]), 0) - - def test_null_multiplication_with_null_flag(self): - """Test multiplication with null() function using -sn flags.""" - self.assertModule( - "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=5, - overwrite=True, - ) - # Verify output STRDS was created - info = SimpleModule("t.info", type="strds", input="precip_abs4", flags="g") - info.run() - self.assertEqual(info.returncode, 0) - strds_info = gs.parse_key_val(info.outputs.stdout, sep="=") - self.assertEqual(strds_info["name"], "precip_abs4") - # With -n flag, null maps are registered, so should have 6 maps - self.assertEqual(int(strds_info["number_of_maps"]), 6) - - def test_failure_on_missing_map(self): - """Test that the module fails when a required map is missing.""" - # Remove a raster map that is required - self.runModule( - "g.remove", flags="f", type="raster", name="prec_1", overwrite=True - ) - # This should fail - self.assertModuleFail( - "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=5, - overwrite=True, - ) - # Recreate the map so other tests aren't affected - self.runModule("r.mapcalc", expression="prec_1 = rand(0, 550)", overwrite=True) - - -if __name__ == "__main__": - test() diff --git a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py b/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py deleted file mode 100644 index 5b24f05c636..00000000000 --- a/temporal/t.rast.mapcalc/testsuite/test_t_rast_mapcalc_operators.py +++ /dev/null @@ -1,233 +0,0 @@ -"""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. - -@author GRASS Development Team -""" - -import grass.script as gs -from grass.gunittest.case import TestCase -from grass.gunittest.gmodules import SimpleModule -from grass.gunittest.main import test - - -class TestRasterMapcalcOperators(TestCase): - @classmethod - def setUpClass(cls): - """Initiate the temporal GIS and set the region""" - cls.use_temp_region() - cls.runModule("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1") - cls.runModule("g.region", s=0, n=80, w=0, e=120, b=0, t=50, res=10, res3=10) - - # Generate data - cls.runModule( - "r.mapcalc", expression="prec_1 = rand(0, 550)", flags="s", overwrite=True - ) - cls.runModule( - "r.mapcalc", expression="prec_2 = rand(0, 450)", flags="s", overwrite=True - ) - cls.runModule( - "r.mapcalc", expression="prec_3 = rand(0, 320)", flags="s", overwrite=True - ) - cls.runModule( - "r.mapcalc", expression="prec_4 = rand(0, 510)", flags="s", overwrite=True - ) - cls.runModule( - "r.mapcalc", expression="prec_5 = rand(0, 300)", flags="s", overwrite=True - ) - cls.runModule( - "r.mapcalc", expression="prec_6 = rand(0, 650)", flags="s", overwrite=True - ) - - cls.runModule( - "t.create", - type="strds", - temporaltype="absolute", - output="precip_abs1", - title="A test", - description="A test", - overwrite=True, - ) - cls.runModule( - "t.create", - type="strds", - temporaltype="absolute", - output="precip_abs2", - title="A test", - description="A test", - overwrite=True, - ) - cls.runModule( - "t.register", - flags="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", - overwrite=True, - ) - cls.runModule( - "t.register", - type="raster", - input="precip_abs2", - maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", - overwrite=True, - ) - - @classmethod - def tearDownClass(cls): - """Remove the temporary region""" - cls.runModule( - "t.remove", - flags="rf", - type="strds", - inputs="precip_abs1,precip_abs2,precip_abs3", - quiet=True, - ) - cls.runModule( - "g.remove", flags="f", type="raster", pattern="prec_*", quiet=True - ) - cls.runModule( - "g.remove", flags="f", type="raster", pattern="new_prec_*", quiet=True - ) - cls.del_temp_region() - - def test_start_time_end_time_operators(self): - """Test start_time() and end_time() temporal operators.""" - self.assertModule( - "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=5, - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - tinfo_string = """number_of_maps=6 -temporal_type=absolute -name=precip_abs3""" - info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") - self.assertModuleKeyValue( - module=info, reference=tinfo_string, precision=2, sep="=" - ) - - def test_start_time_components(self): - """Test start_year(), start_month(), start_day(), start_hour(), start_minute(), start_second() operators.""" - self.assertModule( - "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=5, - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - tinfo_string = """number_of_maps=6 -name=precip_abs3""" - info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") - self.assertModuleKeyValue( - module=info, reference=tinfo_string, precision=2, sep="=" - ) - - def test_end_time_components(self): - """Test end_year(), end_month(), end_day(), end_hour(), end_minute(), end_second() operators.""" - self.assertModule( - "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=5, - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - tinfo_string = """number_of_maps=6 -name=precip_abs3""" - info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") - self.assertModuleKeyValue( - module=info, reference=tinfo_string, precision=2, sep="=" - ) - - def test_start_doy_dow_operators(self): - """Test start_doy() and start_dow() temporal operators.""" - self.assertModule( - "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=5, - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - tinfo_string = """number_of_maps=6 -name=precip_abs3""" - info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") - self.assertModuleKeyValue( - module=info, reference=tinfo_string, precision=2, sep="=" - ) - - def test_end_doy_dow_operators(self): - """Test end_doy() and end_dow() temporal operators.""" - self.assertModule( - "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=5, - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - tinfo_string = """number_of_maps=6 -name=precip_abs3""" - info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") - self.assertModuleKeyValue( - module=info, reference=tinfo_string, precision=2, sep="=" - ) - - def test_map_comparison_operator(self): - """Test comparison operator with map names.""" - self.assertModule( - "t.rast.mapcalc", - flags="sn", - inputs="precip_abs1", - output="precip_abs3", - expression="if(precip_abs1 == prec_1, prec_1, null())", - basename="new_prec", - verbose=True, - overwrite=True, - ) - # Verify output STRDS was created - info = SimpleModule("t.info", type="strds", input="precip_abs3", flags="g") - info.run() - self.assertEqual(info.returncode, 0) - strds_info = gs.parse_key_val(info.outputs.stdout, sep="=") - self.assertEqual(strds_info["name"], "precip_abs3") - # With -n flag, null maps are registered, so should have 6 maps - self.assertEqual(int(strds_info["number_of_maps"]), 6) - - -if __name__ == "__main__": - test() From ef84825e5b789fafb603ca7bce49f1382b0f187a Mon Sep 17 00:00:00 2001 From: sakirr Date: Thu, 29 Jan 2026 10:34:34 +0000 Subject: [PATCH 07/16] tests(t.rast.mapcalc): run fixtures in existing GRASS session --- temporal/t.rast.mapcalc/tests/conftest.py | 239 +++++++++++----------- 1 file changed, 115 insertions(+), 124 deletions(-) diff --git a/temporal/t.rast.mapcalc/tests/conftest.py b/temporal/t.rast.mapcalc/tests/conftest.py index d03e6bfa384..a10931fb69b 100644 --- a/temporal/t.rast.mapcalc/tests/conftest.py +++ b/temporal/t.rast.mapcalc/tests/conftest.py @@ -1,4 +1,8 @@ -"""Fixtures for t.rast.mapcalc tests.""" +"""Fixtures for t.rast.mapcalc tests. + +Assumes an active GRASS session (e.g. grass --tmp-location XY --exec pytest ...). +Only prepares and cleans up raster maps and STRDS. +""" import os from types import SimpleNamespace @@ -8,152 +12,139 @@ import grass.script as gs -def _setup_mapcalc_session(tmp_path_factory, use_seed): - """Create a temporary GRASS project with STRDS data for t.rast.mapcalc tests.""" - 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: - env = session.env - gs.run_command("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1", env=env) - gs.run_command( - "g.region", - s=0, - n=80, - w=0, - e=120, - b=0, - t=50, - res=10, - res3=10, - env=env, - ) - if use_seed: - for name, val in [ - ("prec_1", 550), - ("prec_2", 450), - ("prec_3", 320), - ("prec_4", 510), - ("prec_5", 300), - ("prec_6", 650), - ]: - gs.run_command( - "r.mapcalc", - expression=f"{name} = rand(0, {val})", - flags="s", - overwrite=True, - env=env, - ) - else: - gs.run_command( - "r.mapcalc", - expression="prec_1 = rand(0, 550)", - overwrite=True, - env=env, - ) - gs.run_command( - "r.mapcalc", - expression="prec_2 = rand(0, 450)", - overwrite=True, - env=env, - ) - gs.run_command( - "r.mapcalc", - expression="prec_3 = rand(0, 320)", - overwrite=True, - env=env, - ) +def setup_maps(use_seed): + """Create region, prec_* rasters, and STRDS precip_abs1, precip_abs2.""" + gs.run_command("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1") + gs.run_command( + "g.region", + s=0, + n=80, + w=0, + e=120, + b=0, + t=50, + res=10, + res3=10, + ) + if use_seed: + for name, val in [ + ("prec_1", 550), + ("prec_2", 450), + ("prec_3", 320), + ("prec_4", 510), + ("prec_5", 300), + ("prec_6", 650), + ]: gs.run_command( "r.mapcalc", - expression="prec_4 = rand(0, 510)", + expression=f"{name} = rand(0, {val})", + flags="s", overwrite=True, - env=env, - ) - gs.run_command( - "r.mapcalc", - expression="prec_5 = rand(0, 300)", - overwrite=True, - env=env, - ) - gs.run_command( - "r.mapcalc", - expression="prec_6 = rand(0, 650)", - overwrite=True, - env=env, ) + else: gs.run_command( - "t.create", - type="strds", - temporaltype="absolute", - output="precip_abs1", - title="A test", - description="A test", + "r.mapcalc", + expression="prec_1 = rand(0, 550)", overwrite=True, - env=env, ) gs.run_command( - "t.create", - type="strds", - temporaltype="absolute", - output="precip_abs2", - title="A test", - description="A test", + "r.mapcalc", + expression="prec_2 = rand(0, 450)", overwrite=True, - env=env, ) gs.run_command( - "t.register", - flags="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", + "r.mapcalc", + expression="prec_3 = rand(0, 320)", overwrite=True, - env=env, ) gs.run_command( - "t.register", - type="raster", - input="precip_abs2", - maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", + "r.mapcalc", + expression="prec_4 = rand(0, 510)", overwrite=True, - env=env, - ) - yield SimpleNamespace(env=env) - gs.run_command( - "t.remove", - flags="rf", - type="strds", - inputs="precip_abs1,precip_abs2,precip_abs3,precip_abs4", - quiet=True, - env=env, ) gs.run_command( - "g.remove", - flags="f", - type="raster", - pattern="prec_*", - quiet=True, - env=env, + "r.mapcalc", + expression="prec_5 = rand(0, 300)", + overwrite=True, ) gs.run_command( - "g.remove", - flags="f", - type="raster", - pattern="new_prec_*", - quiet=True, - env=env, + "r.mapcalc", + expression="prec_6 = rand(0, 650)", + overwrite=True, ) + gs.run_command( + "t.create", + type="strds", + temporaltype="absolute", + output="precip_abs1", + title="A test", + description="A test", + overwrite=True, + ) + gs.run_command( + "t.create", + type="strds", + temporaltype="absolute", + output="precip_abs2", + title="A test", + description="A test", + overwrite=True, + ) + gs.run_command( + "t.register", + flags="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", + overwrite=True, + ) + gs.run_command( + "t.register", + type="raster", + input="precip_abs2", + maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", + overwrite=True, + ) + + +def cleanup_maps(): + """Remove STRDS and raster maps created by setup_maps or tests.""" + gs.run_command( + "t.remove", + flags="df", + type="strds", + inputs="precip_abs1,precip_abs2,precip_abs3,precip_abs4", + quiet=True, + ) + gs.run_command( + "g.remove", + flags="f", + type="raster", + name="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", + quiet=True, + ) + gs.run_command( + "g.remove", + flags="f", + type="raster", + pattern="new_prec_*", + quiet=True, + ) @pytest.fixture(scope="module") -def mapcalc_session_basic(tmp_path_factory): - """Session with STRDS data for basic tests (rand without seed).""" - yield from _setup_mapcalc_session(tmp_path_factory, use_seed=False) +def mapcalc_session_basic(): + """STRDS data for basic tests (rand without seed).""" + setup_maps(use_seed=False) + yield SimpleNamespace(env=os.environ) + cleanup_maps() @pytest.fixture(scope="module") -def mapcalc_session_operators(tmp_path_factory): - """Session with STRDS data for operator tests (rand with seed).""" - yield from _setup_mapcalc_session(tmp_path_factory, use_seed=True) +def mapcalc_session_operators(): + """STRDS data for operator tests (rand with seed).""" + setup_maps(use_seed=True) + yield SimpleNamespace(env=os.environ) + cleanup_maps() From b02d4e1ee61c44323fa2298f03a35f4c1982f722 Mon Sep 17 00:00:00 2001 From: sakirr Date: Thu, 29 Jan 2026 11:03:42 +0000 Subject: [PATCH 08/16] tests(t.rast.mapcalc): migrate additional shell test to pytest - Migrates remaining t.rast.mapcalc shell tests to pytest - Stabilizes fixtures for execution in an existing GRASS session - Aligns failure assertions with original shell test behavior - Makes teardown idempotent to avoid failures when data is already removed --- temporal/t.rast.mapcalc/tests/conftest.py | 67 ++++++------------- .../tests/test_t_rast_mapcalc_basic.py | 8 +-- 2 files changed, 25 insertions(+), 50 deletions(-) diff --git a/temporal/t.rast.mapcalc/tests/conftest.py b/temporal/t.rast.mapcalc/tests/conftest.py index a10931fb69b..421f16901cd 100644 --- a/temporal/t.rast.mapcalc/tests/conftest.py +++ b/temporal/t.rast.mapcalc/tests/conftest.py @@ -26,50 +26,19 @@ def setup_maps(use_seed): res=10, res3=10, ) - if use_seed: - for name, val in [ - ("prec_1", 550), - ("prec_2", 450), - ("prec_3", 320), - ("prec_4", 510), - ("prec_5", 300), - ("prec_6", 650), - ]: - gs.run_command( - "r.mapcalc", - expression=f"{name} = rand(0, {val})", - flags="s", - overwrite=True, - ) - else: + # rand() requires a seeded PRNG; use flags="s" in all cases + for name, val in [ + ("prec_1", 550), + ("prec_2", 450), + ("prec_3", 320), + ("prec_4", 510), + ("prec_5", 300), + ("prec_6", 650), + ]: gs.run_command( "r.mapcalc", - expression="prec_1 = rand(0, 550)", - overwrite=True, - ) - gs.run_command( - "r.mapcalc", - expression="prec_2 = rand(0, 450)", - overwrite=True, - ) - gs.run_command( - "r.mapcalc", - expression="prec_3 = rand(0, 320)", - overwrite=True, - ) - gs.run_command( - "r.mapcalc", - expression="prec_4 = rand(0, 510)", - overwrite=True, - ) - gs.run_command( - "r.mapcalc", - expression="prec_5 = rand(0, 300)", - overwrite=True, - ) - gs.run_command( - "r.mapcalc", - expression="prec_6 = rand(0, 650)", + expression=f"{name} = rand(0, {val})", + flags="s", overwrite=True, ) gs.run_command( @@ -110,13 +79,19 @@ def setup_maps(use_seed): def cleanup_maps(): - """Remove STRDS and raster maps created by setup_maps or tests.""" + """Remove STRDS and raster maps created by setup_maps or tests. + + Teardown must be idempotent: STRDS or maps may already have been removed + by tests (e.g. test_failure_on_missing_map). Use errors='ignore' so missing + datasets/maps do not raise during teardown. + """ gs.run_command( "t.remove", flags="df", type="strds", inputs="precip_abs1,precip_abs2,precip_abs3,precip_abs4", quiet=True, + errors="ignore", ) gs.run_command( "g.remove", @@ -124,6 +99,7 @@ def cleanup_maps(): type="raster", name="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", quiet=True, + errors="ignore", ) gs.run_command( "g.remove", @@ -131,12 +107,13 @@ def cleanup_maps(): type="raster", pattern="new_prec_*", quiet=True, + errors="ignore", ) @pytest.fixture(scope="module") def mapcalc_session_basic(): - """STRDS data for basic tests (rand without seed).""" + """STRDS data for basic tests.""" setup_maps(use_seed=False) yield SimpleNamespace(env=os.environ) cleanup_maps() @@ -144,7 +121,7 @@ def mapcalc_session_basic(): @pytest.fixture(scope="module") def mapcalc_session_operators(): - """STRDS data for operator tests (rand with seed).""" + """STRDS data for operator tests.""" setup_maps(use_seed=True) yield SimpleNamespace(env=os.environ) cleanup_maps() 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 index e2a5b8ee8ec..a0656fa05f4 100644 --- a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py @@ -10,8 +10,8 @@ import pytest +import grass.exceptions import grass.script as gs -from grass.exceptions import CalledModuleError def _assert_tinfo_key_value(env, strds_name, reference_string, sep="="): @@ -136,7 +136,8 @@ def test_failure_on_missing_map(mapcalc_session_basic): gs.run_command( "g.remove", flags="f", type="raster", name="prec_1", overwrite=True, env=env ) - with pytest.raises(CalledModuleError): + # Assert failure only; do not assert exact error message (migrated from shell test). + with pytest.raises(grass.exceptions.CalledModuleError): gs.run_command( "t.rast.mapcalc", flags="sn", @@ -149,6 +150,3 @@ def test_failure_on_missing_map(mapcalc_session_basic): overwrite=True, env=env, ) - gs.run_command( - "r.mapcalc", expression="prec_1 = rand(0, 550)", overwrite=True, env=env - ) From 00efc558c6acc64cfbe718b2a9470caef6d2baeb Mon Sep 17 00:00:00 2001 From: sakirr Date: Thu, 29 Jan 2026 23:15:24 +0000 Subject: [PATCH 09/16] t.rast.mapcalc: fixtures create own GRASS session Minimal fix following temporal/t.rast.list/tests/conftest.py pattern: - Fixtures now create temporary GRASS project via gs.create_project() - Session initialized via gs.setup.init() context manager - All commands use session.env parameter - Test files unchanged (still use gs.run_command) - No manual cleanup needed (temp directory auto-deleted) --- temporal/t.rast.mapcalc/tests/conftest.py | 101 ++++++++++------------ 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/temporal/t.rast.mapcalc/tests/conftest.py b/temporal/t.rast.mapcalc/tests/conftest.py index 421f16901cd..a1fc5c50550 100644 --- a/temporal/t.rast.mapcalc/tests/conftest.py +++ b/temporal/t.rast.mapcalc/tests/conftest.py @@ -1,7 +1,13 @@ """Fixtures for t.rast.mapcalc tests. -Assumes an active GRASS session (e.g. grass --tmp-location XY --exec pytest ...). -Only prepares and cleans up raster maps and STRDS. +Each fixture creates its own temporary GRASS project and session, following +the pattern from temporal/t.rast.list/tests/conftest.py. Tests do NOT rely +on a pre-existing GRASS session. + +(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 @@ -12,9 +18,12 @@ import grass.script as gs -def setup_maps(use_seed): - """Create region, prec_* rasters, and STRDS precip_abs1, precip_abs2.""" - gs.run_command("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1") +def _setup_maps(session): + """Create region, prec_* rasters, and STRDS precip_abs1, precip_abs2. + + All commands use session.env to ensure they run in the correct session. + """ + gs.run_command("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1", env=session.env) gs.run_command( "g.region", s=0, @@ -25,8 +34,9 @@ def setup_maps(use_seed): t=50, res=10, res3=10, + env=session.env, ) - # rand() requires a seeded PRNG; use flags="s" in all cases + # Create raster maps with seeded random values for name, val in [ ("prec_1", 550), ("prec_2", 450), @@ -35,12 +45,9 @@ def setup_maps(use_seed): ("prec_5", 300), ("prec_6", 650), ]: - gs.run_command( - "r.mapcalc", - expression=f"{name} = rand(0, {val})", - flags="s", - overwrite=True, - ) + gs.mapcalc(f"{name} = rand(0, {val})", seed=1, env=session.env) + + # Create STRDS datasets gs.run_command( "t.create", type="strds", @@ -49,6 +56,7 @@ def setup_maps(use_seed): title="A test", description="A test", overwrite=True, + env=session.env, ) gs.run_command( "t.create", @@ -58,7 +66,9 @@ def setup_maps(use_seed): title="A test", description="A test", overwrite=True, + env=session.env, ) + # Register maps with temporal information gs.run_command( "t.register", flags="i", @@ -68,6 +78,7 @@ def setup_maps(use_seed): start="2001-01-01", increment="3 months", overwrite=True, + env=session.env, ) gs.run_command( "t.register", @@ -75,53 +86,37 @@ def setup_maps(use_seed): input="precip_abs2", maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", overwrite=True, + env=session.env, ) -def cleanup_maps(): - """Remove STRDS and raster maps created by setup_maps or tests. +@pytest.fixture(scope="module") +def mapcalc_session_basic(tmp_path_factory): + """Create a GRASS session with STRDS data for basic tests. - Teardown must be idempotent: STRDS or maps may already have been removed - by tests (e.g. test_failure_on_missing_map). Use errors='ignore' so missing - datasets/maps do not raise during teardown. + Creates a temporary GRASS project and initializes a session. The session + and all data are cleaned up automatically when the context exits. + Follows the pattern from temporal/t.rast.list/tests/conftest.py. """ - gs.run_command( - "t.remove", - flags="df", - type="strds", - inputs="precip_abs1,precip_abs2,precip_abs3,precip_abs4", - quiet=True, - errors="ignore", - ) - gs.run_command( - "g.remove", - flags="f", - type="raster", - name="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", - quiet=True, - errors="ignore", - ) - gs.run_command( - "g.remove", - flags="f", - type="raster", - pattern="new_prec_*", - quiet=True, - errors="ignore", - ) + tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc_basic") + project = tmp_path / "test" + gs.create_project(project) + with gs.setup.init(project, env=os.environ.copy()) as session: + _setup_maps(session) + # Yield SimpleNamespace with env for backward compatibility with tests + yield SimpleNamespace(session=session, env=session.env) @pytest.fixture(scope="module") -def mapcalc_session_basic(): - """STRDS data for basic tests.""" - setup_maps(use_seed=False) - yield SimpleNamespace(env=os.environ) - cleanup_maps() - +def mapcalc_session_operators(tmp_path_factory): + """Create a GRASS session with STRDS data for operator tests. -@pytest.fixture(scope="module") -def mapcalc_session_operators(): - """STRDS data for operator tests.""" - setup_maps(use_seed=True) - yield SimpleNamespace(env=os.environ) - cleanup_maps() + Identical structure to mapcalc_session_basic but creates a separate + project to isolate operator tests from basic tests. + """ + tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc_operators") + project = tmp_path / "test" + gs.create_project(project) + with gs.setup.init(project, env=os.environ.copy()) as session: + _setup_maps(session) + yield SimpleNamespace(session=session, env=session.env) From e2f9cc125e072ec0c69da49f35ee97b9ba0a53a9 Mon Sep 17 00:00:00 2001 From: sakirr Date: Sat, 31 Jan 2026 00:06:00 +0000 Subject: [PATCH 10/16] tests(t.rast.mapcalc): remove create_project and use standard pytest session init --- temporal/t.rast.mapcalc/tests/conftest.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/temporal/t.rast.mapcalc/tests/conftest.py b/temporal/t.rast.mapcalc/tests/conftest.py index a1fc5c50550..d01d93dbf17 100644 --- a/temporal/t.rast.mapcalc/tests/conftest.py +++ b/temporal/t.rast.mapcalc/tests/conftest.py @@ -99,9 +99,7 @@ def mapcalc_session_basic(tmp_path_factory): Follows the pattern from temporal/t.rast.list/tests/conftest.py. """ tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc_basic") - project = tmp_path / "test" - gs.create_project(project) - with gs.setup.init(project, env=os.environ.copy()) as session: + with gs.setup.init(tmp_path, env=os.environ.copy()) as session: _setup_maps(session) # Yield SimpleNamespace with env for backward compatibility with tests yield SimpleNamespace(session=session, env=session.env) @@ -115,8 +113,6 @@ def mapcalc_session_operators(tmp_path_factory): project to isolate operator tests from basic tests. """ tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc_operators") - project = tmp_path / "test" - gs.create_project(project) - with gs.setup.init(project, env=os.environ.copy()) as session: + with gs.setup.init(tmp_path, env=os.environ.copy()) as session: _setup_maps(session) yield SimpleNamespace(session=session, env=session.env) From 239be0cb28c675f4328b2e0da0a87175ad7c9821 Mon Sep 17 00:00:00 2001 From: sakirr Date: Sat, 31 Jan 2026 00:35:23 +0000 Subject: [PATCH 11/16] tests(t.rast.mapcalc): create GRASS project before gs.setup.init --- temporal/t.rast.mapcalc/tests/conftest.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/temporal/t.rast.mapcalc/tests/conftest.py b/temporal/t.rast.mapcalc/tests/conftest.py index d01d93dbf17..a1fc5c50550 100644 --- a/temporal/t.rast.mapcalc/tests/conftest.py +++ b/temporal/t.rast.mapcalc/tests/conftest.py @@ -99,7 +99,9 @@ def mapcalc_session_basic(tmp_path_factory): Follows the pattern from temporal/t.rast.list/tests/conftest.py. """ tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc_basic") - with gs.setup.init(tmp_path, env=os.environ.copy()) as session: + project = tmp_path / "test" + gs.create_project(project) + with gs.setup.init(project, env=os.environ.copy()) as session: _setup_maps(session) # Yield SimpleNamespace with env for backward compatibility with tests yield SimpleNamespace(session=session, env=session.env) @@ -113,6 +115,8 @@ def mapcalc_session_operators(tmp_path_factory): project to isolate operator tests from basic tests. """ tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc_operators") - with gs.setup.init(tmp_path, env=os.environ.copy()) as session: + project = tmp_path / "test" + gs.create_project(project) + with gs.setup.init(project, env=os.environ.copy()) as session: _setup_maps(session) yield SimpleNamespace(session=session, env=session.env) From a83590ae92386a2a035ca6ac3f86ac9bcefc2c13 Mon Sep 17 00:00:00 2001 From: sakirr Date: Sat, 31 Jan 2026 02:20:03 +0000 Subject: [PATCH 12/16] tests(t.rast.mapcalc): use Tools API in pytest fixtures --- temporal/t.rast.mapcalc/tests/conftest.py | 33 ++++++++--------------- 1 file changed, 11 insertions(+), 22 deletions(-) diff --git a/temporal/t.rast.mapcalc/tests/conftest.py b/temporal/t.rast.mapcalc/tests/conftest.py index a1fc5c50550..478ad3dbe10 100644 --- a/temporal/t.rast.mapcalc/tests/conftest.py +++ b/temporal/t.rast.mapcalc/tests/conftest.py @@ -16,16 +16,18 @@ import pytest import grass.script as gs +from grass.tools import Tools def _setup_maps(session): """Create region, prec_* rasters, and STRDS precip_abs1, precip_abs2. - All commands use session.env to ensure they run in the correct session. + Uses Tools(session=session) so the Tools instance manages the session + environment; no explicit env= is passed. """ - gs.run_command("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1", env=session.env) - gs.run_command( - "g.region", + tools = Tools(session=session, overwrite=True) + tools.g_gisenv(set="TGIS_USE_CURRENT_MAPSET=1") + tools.g_region( s=0, n=80, w=0, @@ -34,7 +36,6 @@ def _setup_maps(session): t=50, res=10, res3=10, - env=session.env, ) # Create raster maps with seeded random values for name, val in [ @@ -45,48 +46,36 @@ def _setup_maps(session): ("prec_5", 300), ("prec_6", 650), ]: - gs.mapcalc(f"{name} = rand(0, {val})", seed=1, env=session.env) + tools.r_mapcalc(expression=f"{name} = rand(0, {val})", seed=1) # Create STRDS datasets - gs.run_command( - "t.create", + tools.t_create( type="strds", temporaltype="absolute", output="precip_abs1", title="A test", description="A test", - overwrite=True, - env=session.env, ) - gs.run_command( - "t.create", + tools.t_create( type="strds", temporaltype="absolute", output="precip_abs2", title="A test", description="A test", - overwrite=True, - env=session.env, ) # Register maps with temporal information - gs.run_command( - "t.register", + tools.t_register( flags="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", - overwrite=True, - env=session.env, ) - gs.run_command( - "t.register", + tools.t_register( type="raster", input="precip_abs2", maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", - overwrite=True, - env=session.env, ) From 52966794459994b9ab573cec31dcf8c079ff0912 Mon Sep 17 00:00:00 2001 From: sakirr Date: Sat, 31 Jan 2026 10:42:07 +0000 Subject: [PATCH 13/16] tests(t.rast.mapcalc): use Tools API consistently in tests --- .../tests/test_t_rast_mapcalc_basic.py | 63 ++++++------------- .../tests/test_t_rast_mapcalc_operators.py | 63 +++++++------------ 2 files changed, 40 insertions(+), 86 deletions(-) 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 index a0656fa05f4..ffb5c8f76a6 100644 --- a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py @@ -12,13 +12,12 @@ import grass.exceptions import grass.script as gs +from grass.tools import Tools -def _assert_tinfo_key_value(env, strds_name, reference_string, sep="="): +def _assert_tinfo_key_value(tools, strds_name, reference_string, sep="="): """Parse t.info output and assert key-value pairs match reference string.""" - output = gs.read_command( - "t.info", type="strds", input=strds_name, flags="g", env=env - ) + output = tools.t_info(type="strds", input=strds_name, flags="g").text actual = gs.parse_key_val(output, sep=sep) reference = dict( line.strip().split(sep, 1) @@ -32,9 +31,8 @@ def _assert_tinfo_key_value(env, strds_name, reference_string, sep="="): def test_basic_addition(mapcalc_session_basic): """Test basic addition of two STRDS with -n flag (register null maps).""" - env = mapcalc_session_basic.env - gs.run_command( - "t.rast.mapcalc", + tools = Tools(session=mapcalc_session_basic.session, overwrite=True) + tools.t_rast_mapcalc( flags="n", inputs="precip_abs1,precip_abs2", output="precip_abs3", @@ -42,20 +40,17 @@ def test_basic_addition(mapcalc_session_basic): basename="new_prec", method="equal", nprocs=5, - overwrite=True, - env=env, ) tinfo_string = """number_of_maps=6 temporal_type=absolute name=precip_abs3""" - _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) def test_division_with_three_inputs(mapcalc_session_basic): """Test division expression with three input STRDS using -s flag.""" - env = mapcalc_session_basic.env - gs.run_command( - "t.rast.mapcalc", + tools = Tools(session=mapcalc_session_basic.session, overwrite=True) + tools.t_rast_mapcalc( flags="n", inputs="precip_abs1,precip_abs2", output="precip_abs3", @@ -63,11 +58,8 @@ def test_division_with_three_inputs(mapcalc_session_basic): basename="new_prec", method="equal", nprocs=5, - overwrite=True, - env=env, ) - gs.run_command( - "t.rast.mapcalc", + tools.t_rast_mapcalc( flags="s", inputs="precip_abs1,precip_abs2,precip_abs3", output="precip_abs4", @@ -75,20 +67,17 @@ def test_division_with_three_inputs(mapcalc_session_basic): basename="new_prec", method="equal", nprocs=5, - overwrite=True, - env=env, ) tinfo_string = """number_of_maps=6 temporal_type=absolute name=precip_abs4""" - _assert_tinfo_key_value(env, "precip_abs4", tinfo_string) + _assert_tinfo_key_value(tools, "precip_abs4", tinfo_string) def test_null_multiplication(mapcalc_session_basic): """Test multiplication with null() function using -s flag.""" - env = mapcalc_session_basic.env - gs.run_command( - "t.rast.mapcalc", + tools = Tools(session=mapcalc_session_basic.session, overwrite=True) + tools.t_rast_mapcalc( flags="s", inputs="precip_abs1,precip_abs2", output="precip_abs4", @@ -96,12 +85,8 @@ def test_null_multiplication(mapcalc_session_basic): basename="new_prec", method="equal", nprocs=5, - overwrite=True, - env=env, - ) - output = gs.read_command( - "t.info", type="strds", input="precip_abs4", flags="g", env=env ) + output = tools.t_info(type="strds", input="precip_abs4", flags="g").text strds_info = gs.parse_key_val(output, sep="=") assert strds_info["name"] == "precip_abs4" assert int(strds_info["number_of_maps"]) == 0 @@ -109,9 +94,8 @@ def test_null_multiplication(mapcalc_session_basic): def test_null_multiplication_with_null_flag(mapcalc_session_basic): """Test multiplication with null() function using -sn flags.""" - env = mapcalc_session_basic.env - gs.run_command( - "t.rast.mapcalc", + tools = Tools(session=mapcalc_session_basic.session, overwrite=True) + tools.t_rast_mapcalc( flags="sn", inputs="precip_abs1,precip_abs2", output="precip_abs4", @@ -119,12 +103,8 @@ def test_null_multiplication_with_null_flag(mapcalc_session_basic): basename="new_prec", method="equal", nprocs=5, - overwrite=True, - env=env, - ) - output = gs.read_command( - "t.info", type="strds", input="precip_abs4", flags="g", env=env ) + output = tools.t_info(type="strds", input="precip_abs4", flags="g").text strds_info = gs.parse_key_val(output, sep="=") assert strds_info["name"] == "precip_abs4" assert int(strds_info["number_of_maps"]) == 6 @@ -132,14 +112,11 @@ def test_null_multiplication_with_null_flag(mapcalc_session_basic): def test_failure_on_missing_map(mapcalc_session_basic): """Test that the module fails when a required map is missing.""" - env = mapcalc_session_basic.env - gs.run_command( - "g.remove", flags="f", type="raster", name="prec_1", overwrite=True, env=env - ) + tools = Tools(session=mapcalc_session_basic.session, overwrite=True) + tools.g_remove(flags="f", type="raster", name="prec_1") # Assert failure only; do not assert exact error message (migrated from shell test). with pytest.raises(grass.exceptions.CalledModuleError): - gs.run_command( - "t.rast.mapcalc", + tools.t_rast_mapcalc( flags="sn", inputs="precip_abs1,precip_abs2", output="precip_abs4", @@ -147,6 +124,4 @@ def test_failure_on_missing_map(mapcalc_session_basic): basename="new_prec", method="equal", nprocs=5, - overwrite=True, - env=env, ) 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 index 1eeb3f080d7..0f66ce4b33c 100644 --- a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py @@ -9,13 +9,12 @@ """ import grass.script as gs +from grass.tools import Tools -def _assert_tinfo_key_value(env, strds_name, reference_string, sep="="): +def _assert_tinfo_key_value(tools, strds_name, reference_string, sep="="): """Parse t.info output and assert key-value pairs match reference string.""" - output = gs.read_command( - "t.info", type="strds", input=strds_name, flags="g", env=env - ) + output = tools.t_info(type="strds", input=strds_name, flags="g").text actual = gs.parse_key_val(output, sep=sep) reference = dict( line.strip().split(sep, 1) @@ -29,9 +28,8 @@ def _assert_tinfo_key_value(env, strds_name, reference_string, sep="="): def test_start_time_end_time_operators(mapcalc_session_operators): """Test start_time() and end_time() temporal operators.""" - env = mapcalc_session_operators.env - gs.run_command( - "t.rast.mapcalc", + tools = Tools(session=mapcalc_session_operators.session, overwrite=True) + tools.t_rast_mapcalc( flags="sn", inputs="precip_abs1,precip_abs2", output="precip_abs3", @@ -40,20 +38,17 @@ def test_start_time_end_time_operators(mapcalc_session_operators): method="equal", nprocs=5, verbose=True, - overwrite=True, - env=env, ) tinfo_string = """number_of_maps=6 temporal_type=absolute name=precip_abs3""" - _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) def test_start_time_components(mapcalc_session_operators): """Test start_year(), start_month(), start_day(), start_hour(), start_minute(), start_second() operators.""" - env = mapcalc_session_operators.env - gs.run_command( - "t.rast.mapcalc", + tools = Tools(session=mapcalc_session_operators.session, overwrite=True) + tools.t_rast_mapcalc( flags="sn", inputs="precip_abs1,precip_abs2", output="precip_abs3", @@ -62,19 +57,16 @@ def test_start_time_components(mapcalc_session_operators): method="equal", nprocs=5, verbose=True, - overwrite=True, - env=env, ) tinfo_string = """number_of_maps=6 name=precip_abs3""" - _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) def test_end_time_components(mapcalc_session_operators): """Test end_year(), end_month(), end_day(), end_hour(), end_minute(), end_second() operators.""" - env = mapcalc_session_operators.env - gs.run_command( - "t.rast.mapcalc", + tools = Tools(session=mapcalc_session_operators.session, overwrite=True) + tools.t_rast_mapcalc( flags="sn", inputs="precip_abs1,precip_abs2", output="precip_abs3", @@ -83,19 +75,16 @@ def test_end_time_components(mapcalc_session_operators): method="equal", nprocs=5, verbose=True, - overwrite=True, - env=env, ) tinfo_string = """number_of_maps=6 name=precip_abs3""" - _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) def test_start_doy_dow_operators(mapcalc_session_operators): """Test start_doy() and start_dow() temporal operators.""" - env = mapcalc_session_operators.env - gs.run_command( - "t.rast.mapcalc", + tools = Tools(session=mapcalc_session_operators.session, overwrite=True) + tools.t_rast_mapcalc( flags="sn", inputs="precip_abs1,precip_abs2", output="precip_abs3", @@ -104,19 +93,16 @@ def test_start_doy_dow_operators(mapcalc_session_operators): method="equal", nprocs=5, verbose=True, - overwrite=True, - env=env, ) tinfo_string = """number_of_maps=6 name=precip_abs3""" - _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) def test_end_doy_dow_operators(mapcalc_session_operators): """Test end_doy() and end_dow() temporal operators.""" - env = mapcalc_session_operators.env - gs.run_command( - "t.rast.mapcalc", + tools = Tools(session=mapcalc_session_operators.session, overwrite=True) + tools.t_rast_mapcalc( flags="sn", inputs="precip_abs1,precip_abs2", output="precip_abs3", @@ -125,31 +111,24 @@ def test_end_doy_dow_operators(mapcalc_session_operators): method="equal", nprocs=5, verbose=True, - overwrite=True, - env=env, ) tinfo_string = """number_of_maps=6 name=precip_abs3""" - _assert_tinfo_key_value(env, "precip_abs3", tinfo_string) + _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) def test_map_comparison_operator(mapcalc_session_operators): """Test comparison operator with map names.""" - env = mapcalc_session_operators.env - gs.run_command( - "t.rast.mapcalc", + tools = Tools(session=mapcalc_session_operators.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", verbose=True, - overwrite=True, - env=env, - ) - output = gs.read_command( - "t.info", type="strds", input="precip_abs3", flags="g", env=env ) + output = tools.t_info(type="strds", input="precip_abs3", flags="g").text strds_info = gs.parse_key_val(output, sep="=") assert strds_info["name"] == "precip_abs3" assert int(strds_info["number_of_maps"]) == 6 From 9b4594b76a5ff402064e68859e5ab0180f05115b Mon Sep 17 00:00:00 2001 From: sakirr Date: Mon, 2 Feb 2026 04:27:45 +0000 Subject: [PATCH 14/16] tests(t.rast.mapcalc): simplify t.info reference to dict --- .../tests/test_t_rast_mapcalc_basic.py | 30 +++++------ .../tests/test_t_rast_mapcalc_operators.py | 52 ++++++++++--------- 2 files changed, 42 insertions(+), 40 deletions(-) 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 index ffb5c8f76a6..b43f6e07ab8 100644 --- a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py @@ -15,15 +15,9 @@ from grass.tools import Tools -def _assert_tinfo_key_value(tools, strds_name, reference_string, sep="="): - """Parse t.info output and assert key-value pairs match reference string.""" +def _assert_tinfo_key_value(tools, strds_name, reference): output = tools.t_info(type="strds", input=strds_name, flags="g").text - actual = gs.parse_key_val(output, sep=sep) - reference = dict( - line.strip().split(sep, 1) - for line in reference_string.strip().split("\n") - if sep in line - ) + actual = gs.parse_key_val(output, sep="=") for key, value in reference.items(): assert key in actual, f"Missing key: {key}" assert actual[key] == value, f"{key}: expected {value!r}, got {actual[key]!r}" @@ -41,10 +35,12 @@ def test_basic_addition(mapcalc_session_basic): method="equal", nprocs=5, ) - tinfo_string = """number_of_maps=6 -temporal_type=absolute -name=precip_abs3""" - _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) + reference = { + "number_of_maps": "6", + "temporal_type": "absolute", + "name": "precip_abs3", + } + _assert_tinfo_key_value(tools, "precip_abs3", reference) def test_division_with_three_inputs(mapcalc_session_basic): @@ -68,10 +64,12 @@ def test_division_with_three_inputs(mapcalc_session_basic): method="equal", nprocs=5, ) - tinfo_string = """number_of_maps=6 -temporal_type=absolute -name=precip_abs4""" - _assert_tinfo_key_value(tools, "precip_abs4", tinfo_string) + reference = { + "number_of_maps": "6", + "temporal_type": "absolute", + "name": "precip_abs4", + } + _assert_tinfo_key_value(tools, "precip_abs4", reference) def test_null_multiplication(mapcalc_session_basic): 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 index 0f66ce4b33c..c412eed1310 100644 --- a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py @@ -12,15 +12,9 @@ from grass.tools import Tools -def _assert_tinfo_key_value(tools, strds_name, reference_string, sep="="): - """Parse t.info output and assert key-value pairs match reference string.""" +def _assert_tinfo_key_value(tools, strds_name, reference): output = tools.t_info(type="strds", input=strds_name, flags="g").text - actual = gs.parse_key_val(output, sep=sep) - reference = dict( - line.strip().split(sep, 1) - for line in reference_string.strip().split("\n") - if sep in line - ) + actual = gs.parse_key_val(output, sep="=") for key, value in reference.items(): assert key in actual, f"Missing key: {key}" assert actual[key] == value, f"{key}: expected {value!r}, got {actual[key]!r}" @@ -39,10 +33,12 @@ def test_start_time_end_time_operators(mapcalc_session_operators): nprocs=5, verbose=True, ) - tinfo_string = """number_of_maps=6 -temporal_type=absolute -name=precip_abs3""" - _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) + reference = { + "number_of_maps": "6", + "temporal_type": "absolute", + "name": "precip_abs3", + } + _assert_tinfo_key_value(tools, "precip_abs3", reference) def test_start_time_components(mapcalc_session_operators): @@ -58,9 +54,11 @@ def test_start_time_components(mapcalc_session_operators): nprocs=5, verbose=True, ) - tinfo_string = """number_of_maps=6 -name=precip_abs3""" - _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) + reference = { + "number_of_maps": "6", + "name": "precip_abs3", + } + _assert_tinfo_key_value(tools, "precip_abs3", reference) def test_end_time_components(mapcalc_session_operators): @@ -76,9 +74,11 @@ def test_end_time_components(mapcalc_session_operators): nprocs=5, verbose=True, ) - tinfo_string = """number_of_maps=6 -name=precip_abs3""" - _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) + reference = { + "number_of_maps": "6", + "name": "precip_abs3", + } + _assert_tinfo_key_value(tools, "precip_abs3", reference) def test_start_doy_dow_operators(mapcalc_session_operators): @@ -94,9 +94,11 @@ def test_start_doy_dow_operators(mapcalc_session_operators): nprocs=5, verbose=True, ) - tinfo_string = """number_of_maps=6 -name=precip_abs3""" - _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) + reference = { + "number_of_maps": "6", + "name": "precip_abs3", + } + _assert_tinfo_key_value(tools, "precip_abs3", reference) def test_end_doy_dow_operators(mapcalc_session_operators): @@ -112,9 +114,11 @@ def test_end_doy_dow_operators(mapcalc_session_operators): nprocs=5, verbose=True, ) - tinfo_string = """number_of_maps=6 -name=precip_abs3""" - _assert_tinfo_key_value(tools, "precip_abs3", tinfo_string) + reference = { + "number_of_maps": "6", + "name": "precip_abs3", + } + _assert_tinfo_key_value(tools, "precip_abs3", reference) def test_map_comparison_operator(mapcalc_session_operators): From b9075df125b3b8af1ea2fd1c803b917f3d51155f Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Tue, 28 Apr 2026 23:50:18 -0400 Subject: [PATCH 15/16] improvements Collapsed the duplicated fixtures and helper into a single mapcalc_session + assert_tinfo in conftest.py, replaced the destructive g.remove in the failure test with a rename/restore via try/finally, switched assertions from manual parse_key_val to .keyval (with int values), and trimmed cosmetic noise (boilerplate @author, leading/trailing-space expressions, misleading docstring); also reduced nprocs from 5 to 3. --- temporal/t.rast.mapcalc/tests/conftest.py | 141 +++++++----------- .../tests/test_t_rast_mapcalc_basic.py | 139 +++++++++-------- .../tests/test_t_rast_mapcalc_operators.py | 125 +++++++--------- 3 files changed, 184 insertions(+), 221 deletions(-) diff --git a/temporal/t.rast.mapcalc/tests/conftest.py b/temporal/t.rast.mapcalc/tests/conftest.py index 478ad3dbe10..82d1aae2127 100644 --- a/temporal/t.rast.mapcalc/tests/conftest.py +++ b/temporal/t.rast.mapcalc/tests/conftest.py @@ -1,9 +1,5 @@ """Fixtures for t.rast.mapcalc tests. -Each fixture creates its own temporary GRASS project and session, following -the pattern from temporal/t.rast.list/tests/conftest.py. Tests do NOT rely -on a pre-existing GRASS session. - (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 @@ -11,7 +7,6 @@ """ import os -from types import SimpleNamespace import pytest @@ -19,93 +14,65 @@ from grass.tools import Tools -def _setup_maps(session): - """Create region, prec_* rasters, and STRDS precip_abs1, precip_abs2. - - Uses Tools(session=session) so the Tools instance manages the session - environment; no explicit env= is passed. - """ - 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, - ) - # Create raster maps with seeded random values - for name, val in [ - ("prec_1", 550), - ("prec_2", 450), - ("prec_3", 320), - ("prec_4", 510), - ("prec_5", 300), - ("prec_6", 650), - ]: - tools.r_mapcalc(expression=f"{name} = rand(0, {val})", seed=1) - - # Create STRDS datasets - tools.t_create( - type="strds", - temporaltype="absolute", - output="precip_abs1", - title="A test", - description="A test", - ) - tools.t_create( - type="strds", - temporaltype="absolute", - output="precip_abs2", - title="A test", - description="A test", - ) - # Register maps with temporal information - tools.t_register( - flags="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", - ) - tools.t_register( - type="raster", - input="precip_abs2", - maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", - ) - - -@pytest.fixture(scope="module") -def mapcalc_session_basic(tmp_path_factory): - """Create a GRASS session with STRDS data for basic tests. +@pytest.fixture(scope="session") +def mapcalc_session(tmp_path_factory): + """GRASS session with prec_1..prec_6 rasters and precip_abs1/2 STRDS. - Creates a temporary GRASS project and initializes a session. The session - and all data are cleaned up automatically when the context exits. - Follows the pattern from temporal/t.rast.list/tests/conftest.py. + 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_basic") + 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: - _setup_maps(session) - # Yield SimpleNamespace with env for backward compatibility with tests - yield SimpleNamespace(session=session, env=session.env) + 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), + ("prec_4", 510), + ("prec_5", 300), + ("prec_6", 650), + ]: + 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,prec_4,prec_5,prec_6", + start="2001-01-01", + increment="3 months", + ) + tools.t_register( + type="raster", + input="precip_abs2", + maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", + ) + yield session -@pytest.fixture(scope="module") -def mapcalc_session_operators(tmp_path_factory): - """Create a GRASS session with STRDS data for operator tests. +@pytest.fixture +def assert_tinfo(): + """Return a helper that checks t.info -g output against expected key/value pairs.""" - Identical structure to mapcalc_session_basic but creates a separate - project to isolate operator tests from basic tests. - """ - tmp_path = tmp_path_factory.mktemp("t_rast_mapcalc_operators") - project = tmp_path / "test" - gs.create_project(project) - with gs.setup.init(project, env=os.environ.copy()) as session: - _setup_maps(session) - yield SimpleNamespace(session=session, env=session.env) + 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 index b43f6e07ab8..d2ff84472d8 100644 --- a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py @@ -4,122 +4,131 @@ This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. - -@author GRASS Development Team """ import pytest import grass.exceptions -import grass.script as gs from grass.tools import Tools -def _assert_tinfo_key_value(tools, strds_name, reference): - output = tools.t_info(type="strds", input=strds_name, flags="g").text - actual = gs.parse_key_val(output, sep="=") - for key, value in reference.items(): - assert key in actual, f"Missing key: {key}" - assert actual[key] == value, f"{key}: expected {value!r}, got {actual[key]!r}" - - -def test_basic_addition(mapcalc_session_basic): +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_basic.session, overwrite=True) + 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", + expression="precip_abs1 + precip_abs2", basename="new_prec", method="equal", - nprocs=5, + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + { + "number_of_maps": 6, + "temporal_type": "absolute", + "name": "precip_abs3", + }, ) - reference = { - "number_of_maps": "6", - "temporal_type": "absolute", - "name": "precip_abs3", - } - _assert_tinfo_key_value(tools, "precip_abs3", reference) -def test_division_with_three_inputs(mapcalc_session_basic): - """Test division expression with three input STRDS using -s flag.""" - tools = Tools(session=mapcalc_session_basic.session, overwrite=True) +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", + expression="precip_abs1 + precip_abs2", basename="new_prec", method="equal", - nprocs=5, + 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", + expression="(precip_abs1 + precip_abs2) / precip_abs2", basename="new_prec", method="equal", - nprocs=5, + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs4", + { + "number_of_maps": 6, + "temporal_type": "absolute", + "name": "precip_abs4", + }, ) - reference = { - "number_of_maps": "6", - "temporal_type": "absolute", - "name": "precip_abs4", - } - _assert_tinfo_key_value(tools, "precip_abs4", reference) -def test_null_multiplication(mapcalc_session_basic): +def test_null_multiplication(mapcalc_session, assert_tinfo): """Test multiplication with null() function using -s flag.""" - tools = Tools(session=mapcalc_session_basic.session, overwrite=True) + 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()", + expression="(precip_abs1 + precip_abs2) * null()", basename="new_prec", method="equal", - nprocs=5, + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs4", + {"name": "precip_abs4", "number_of_maps": 0}, ) - output = tools.t_info(type="strds", input="precip_abs4", flags="g").text - strds_info = gs.parse_key_val(output, sep="=") - assert strds_info["name"] == "precip_abs4" - assert int(strds_info["number_of_maps"]) == 0 -def test_null_multiplication_with_null_flag(mapcalc_session_basic): +def test_null_multiplication_with_null_flag(mapcalc_session, assert_tinfo): """Test multiplication with null() function using -sn flags.""" - tools = Tools(session=mapcalc_session_basic.session, overwrite=True) + 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()", + expression="(precip_abs1 + precip_abs2) * null()", basename="new_prec", method="equal", - nprocs=5, + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs4", + {"name": "precip_abs4", "number_of_maps": 6}, ) - output = tools.t_info(type="strds", input="precip_abs4", flags="g").text - strds_info = gs.parse_key_val(output, sep="=") - assert strds_info["name"] == "precip_abs4" - assert int(strds_info["number_of_maps"]) == 6 -def test_failure_on_missing_map(mapcalc_session_basic): - """Test that the module fails when a required map is missing.""" - tools = Tools(session=mapcalc_session_basic.session, overwrite=True) - tools.g_remove(flags="f", type="raster", name="prec_1") - # Assert failure only; do not assert exact error message (migrated from shell test). - 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=5, - ) +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 this module-scoped 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 index c412eed1310..d0bbb6af5c2 100644 --- a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py @@ -4,135 +4,122 @@ This program is free software under the GNU General Public License (>=v2). Read the file COPYING that comes with GRASS for details. - -@author GRASS Development Team """ -import grass.script as gs from grass.tools import Tools -def _assert_tinfo_key_value(tools, strds_name, reference): - output = tools.t_info(type="strds", input=strds_name, flags="g").text - actual = gs.parse_key_val(output, sep="=") - for key, value in reference.items(): - assert key in actual, f"Missing key: {key}" - assert actual[key] == value, f"{key}: expected {value!r}, got {actual[key]!r}" - - -def test_start_time_end_time_operators(mapcalc_session_operators): +def test_start_time_end_time_operators(mapcalc_session, assert_tinfo): """Test start_time() and end_time() temporal operators.""" - tools = Tools(session=mapcalc_session_operators.session, overwrite=True) + 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()) ", + expression="if(start_time() >= 0 && end_time() >= 0, (precip_abs1*td() + precip_abs2) / td(), null())", basename="new_prec", method="equal", - nprocs=5, - verbose=True, + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + { + "number_of_maps": 6, + "temporal_type": "absolute", + "name": "precip_abs3", + }, ) - reference = { - "number_of_maps": "6", - "temporal_type": "absolute", - "name": "precip_abs3", - } - _assert_tinfo_key_value(tools, "precip_abs3", reference) -def test_start_time_components(mapcalc_session_operators): +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_operators.session, overwrite=True) + 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()) ", + 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=5, - verbose=True, + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + {"number_of_maps": 6, "name": "precip_abs3"}, ) - reference = { - "number_of_maps": "6", - "name": "precip_abs3", - } - _assert_tinfo_key_value(tools, "precip_abs3", reference) -def test_end_time_components(mapcalc_session_operators): +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_operators.session, overwrite=True) + 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()) ", + 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=5, - verbose=True, + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + {"number_of_maps": 6, "name": "precip_abs3"}, ) - reference = { - "number_of_maps": "6", - "name": "precip_abs3", - } - _assert_tinfo_key_value(tools, "precip_abs3", reference) -def test_start_doy_dow_operators(mapcalc_session_operators): +def test_start_doy_dow_operators(mapcalc_session, assert_tinfo): """Test start_doy() and start_dow() temporal operators.""" - tools = Tools(session=mapcalc_session_operators.session, overwrite=True) + 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()) ", + expression="if(start_doy() >= 0 && start_dow() >= 0, (precip_abs1*td() + precip_abs2) / td(), null())", basename="new_prec", method="equal", - nprocs=5, - verbose=True, + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + {"number_of_maps": 6, "name": "precip_abs3"}, ) - reference = { - "number_of_maps": "6", - "name": "precip_abs3", - } - _assert_tinfo_key_value(tools, "precip_abs3", reference) -def test_end_doy_dow_operators(mapcalc_session_operators): +def test_end_doy_dow_operators(mapcalc_session, assert_tinfo): """Test end_doy() and end_dow() temporal operators.""" - tools = Tools(session=mapcalc_session_operators.session, overwrite=True) + 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()) ", + expression="if(end_doy() >= 0 && end_dow() >= 0, (precip_abs1*td() + precip_abs2) / td(), null())", basename="new_prec", method="equal", - nprocs=5, - verbose=True, + nprocs=3, + ) + assert_tinfo( + tools, + "precip_abs3", + {"number_of_maps": 6, "name": "precip_abs3"}, ) - reference = { - "number_of_maps": "6", - "name": "precip_abs3", - } - _assert_tinfo_key_value(tools, "precip_abs3", reference) -def test_map_comparison_operator(mapcalc_session_operators): +def test_map_comparison_operator(mapcalc_session, assert_tinfo): """Test comparison operator with map names.""" - tools = Tools(session=mapcalc_session_operators.session, overwrite=True) + 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", - verbose=True, ) - output = tools.t_info(type="strds", input="precip_abs3", flags="g").text - strds_info = gs.parse_key_val(output, sep="=") - assert strds_info["name"] == "precip_abs3" - assert int(strds_info["number_of_maps"]) == 6 + assert_tinfo( + tools, + "precip_abs3", + {"name": "precip_abs3", "number_of_maps": 6}, + ) From b6f8c789d68f9acbd75945c09326e94a4260a482 Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Tue, 28 Apr 2026 23:54:57 -0400 Subject: [PATCH 16/16] reduce number of maps --- temporal/t.rast.mapcalc/tests/conftest.py | 15 ++++----------- .../tests/test_t_rast_mapcalc_basic.py | 8 ++++---- .../tests/test_t_rast_mapcalc_operators.py | 12 ++++++------ 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/temporal/t.rast.mapcalc/tests/conftest.py b/temporal/t.rast.mapcalc/tests/conftest.py index 82d1aae2127..780a8daddae 100644 --- a/temporal/t.rast.mapcalc/tests/conftest.py +++ b/temporal/t.rast.mapcalc/tests/conftest.py @@ -16,7 +16,7 @@ @pytest.fixture(scope="session") def mapcalc_session(tmp_path_factory): - """GRASS session with prec_1..prec_6 rasters and precip_abs1/2 STRDS. + """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 @@ -30,14 +30,7 @@ def mapcalc_session(tmp_path_factory): 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), - ("prec_4", 510), - ("prec_5", 300), - ("prec_6", 650), - ]: + 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( @@ -51,14 +44,14 @@ def mapcalc_session(tmp_path_factory): flags="i", type="raster", input="precip_abs1", - maps="prec_1,prec_2,prec_3,prec_4,prec_5,prec_6", + 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,prec_4,prec_5,prec_6", + maps="prec_1,prec_2,prec_3", ) yield session 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 index d2ff84472d8..c8ff99a41af 100644 --- a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_basic.py @@ -28,7 +28,7 @@ def test_basic_addition(mapcalc_session, assert_tinfo): tools, "precip_abs3", { - "number_of_maps": 6, + "number_of_maps": 3, "temporal_type": "absolute", "name": "precip_abs3", }, @@ -65,7 +65,7 @@ def test_division_with_three_inputs(mapcalc_session, assert_tinfo): tools, "precip_abs4", { - "number_of_maps": 6, + "number_of_maps": 3, "temporal_type": "absolute", "name": "precip_abs4", }, @@ -106,7 +106,7 @@ def test_null_multiplication_with_null_flag(mapcalc_session, assert_tinfo): assert_tinfo( tools, "precip_abs4", - {"name": "precip_abs4", "number_of_maps": 6}, + {"name": "precip_abs4", "number_of_maps": 3}, ) @@ -115,7 +115,7 @@ def test_failure_on_missing_map(mapcalc_session): 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 this module-scoped session see prec_1. + 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") 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 index d0bbb6af5c2..c953f3160d1 100644 --- a/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py +++ b/temporal/t.rast.mapcalc/tests/test_t_rast_mapcalc_operators.py @@ -25,7 +25,7 @@ def test_start_time_end_time_operators(mapcalc_session, assert_tinfo): tools, "precip_abs3", { - "number_of_maps": 6, + "number_of_maps": 3, "temporal_type": "absolute", "name": "precip_abs3", }, @@ -47,7 +47,7 @@ def test_start_time_components(mapcalc_session, assert_tinfo): assert_tinfo( tools, "precip_abs3", - {"number_of_maps": 6, "name": "precip_abs3"}, + {"number_of_maps": 3, "name": "precip_abs3"}, ) @@ -66,7 +66,7 @@ def test_end_time_components(mapcalc_session, assert_tinfo): assert_tinfo( tools, "precip_abs3", - {"number_of_maps": 6, "name": "precip_abs3"}, + {"number_of_maps": 3, "name": "precip_abs3"}, ) @@ -85,7 +85,7 @@ def test_start_doy_dow_operators(mapcalc_session, assert_tinfo): assert_tinfo( tools, "precip_abs3", - {"number_of_maps": 6, "name": "precip_abs3"}, + {"number_of_maps": 3, "name": "precip_abs3"}, ) @@ -104,7 +104,7 @@ def test_end_doy_dow_operators(mapcalc_session, assert_tinfo): assert_tinfo( tools, "precip_abs3", - {"number_of_maps": 6, "name": "precip_abs3"}, + {"number_of_maps": 3, "name": "precip_abs3"}, ) @@ -121,5 +121,5 @@ def test_map_comparison_operator(mapcalc_session, assert_tinfo): assert_tinfo( tools, "precip_abs3", - {"name": "precip_abs3", "number_of_maps": 6}, + {"name": "precip_abs3", "number_of_maps": 3}, )