From 554ab909821c481582c4632d736f50a1d0cd3c0d Mon Sep 17 00:00:00 2001 From: Sumit Chintanwar Date: Thu, 29 Jan 2026 03:50:29 +0530 Subject: [PATCH 1/5] r.watershed: Add basic tests for map reuse functionality --- .../testsuite/test_r_watershed_reuse.py | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 raster/r.watershed/testsuite/test_r_watershed_reuse.py diff --git a/raster/r.watershed/testsuite/test_r_watershed_reuse.py b/raster/r.watershed/testsuite/test_r_watershed_reuse.py new file mode 100644 index 00000000000..af0dc5dad8c --- /dev/null +++ b/raster/r.watershed/testsuite/test_r_watershed_reuse.py @@ -0,0 +1,100 @@ +""" +Name: r_watershed_reuse_test +Purpose: Unit tests for r.watershed's ability to reuse existing accumulation + and drainage maps instead of recalculating them. + +Author: Sumit Chintanwar +Copyright: (C) 2026 by Sumit Chintanwar and the GRASS Development Team +Licence: This program is free software under the GNU General Public + License (>=v2). Read the file COPYING that comes with GRASS + for details. +""" + +from grass.gunittest.case import TestCase +from grass.gunittest.main import test + + +class TestWatershedReuse(TestCase): + """Test r.watershed input map reuse functionality""" + + accumulation = "reuse_accum" + drainage = "reuse_drain" + elevation = "reuse_elevation" + + @classmethod + def setUpClass(cls): + cls.use_temp_region() + + cls.runModule( + "r.mapcalc", + expression=f"{cls.elevation} = row() + col()", + ) + + cls.runModule("g.region", raster=cls.elevation, res=10) + + @classmethod + def tearDownClass(cls): + cls.runModule("g.remove", flags="f", type="raster", name=cls.elevation) + cls.del_temp_region() + + def tearDown(self): + self.runModule( + "g.remove", + flags="f", + type="raster", + pattern="reuse_*", + exclude=self.elevation, + ) + + def test_reuse_workflow(self): + """Test complete reuse workflow: generate, reuse for accumulation and RUSLE""" + self.assertModule( + "r.watershed", + elevation=self.elevation, + accumulation=self.accumulation, + drainage=self.drainage, + threshold=1000, + ) + + self.assertRasterExists(self.accumulation) + self.assertRasterExists(self.drainage) + + self.assertRasterMinMax( + self.drainage, + refmin=-8, + refmax=8, + msg="Drainage direction must be between -8 and 8", + ) + + accum_out = "reuse_accum_out" + self.assertModule( + "r.watershed", + elevation=self.elevation, + accumulation_input=self.accumulation, + drainage_input=self.drainage, + accumulation=accum_out, + ) + + self.assertRastersNoDifference( + actual=accum_out, + reference=self.accumulation, + precision=0.01, + msg="Reused accumulation should match original", + ) + + ls_factor = "reuse_ls" + self.assertModule( + "r.watershed", + elevation=self.elevation, + accumulation_input=self.accumulation, + drainage_input=self.drainage, + threshold=1000, + length_slope=ls_factor, + max_slope_length=100, + ) + + self.assertRasterExists(ls_factor) + + +if __name__ == "__main__": + test() From 56b032da0f8ca4e4ea08be4e38403c14e8ae622d Mon Sep 17 00:00:00 2001 From: Sumit Chintanwar Date: Thu, 29 Jan 2026 04:02:40 +0530 Subject: [PATCH 2/5] r.watershed: Add input validation tests for map reuse --- .../testsuite/test_r_watershed_reuse.py | 53 +++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/raster/r.watershed/testsuite/test_r_watershed_reuse.py b/raster/r.watershed/testsuite/test_r_watershed_reuse.py index af0dc5dad8c..68b6f576ef9 100644 --- a/raster/r.watershed/testsuite/test_r_watershed_reuse.py +++ b/raster/r.watershed/testsuite/test_r_watershed_reuse.py @@ -10,6 +10,7 @@ for details. """ +import unittest from grass.gunittest.case import TestCase from grass.gunittest.main import test @@ -24,12 +25,10 @@ class TestWatershedReuse(TestCase): @classmethod def setUpClass(cls): cls.use_temp_region() - cls.runModule( "r.mapcalc", expression=f"{cls.elevation} = row() + col()", ) - cls.runModule("g.region", raster=cls.elevation, res=10) @classmethod @@ -46,8 +45,11 @@ def tearDown(self): exclude=self.elevation, ) + @unittest.expectedFailure def test_reuse_workflow(self): - """Test complete reuse workflow: generate, reuse for accumulation and RUSLE""" + """Test complete reuse workflow: generate, reuse for accumulation and RUSLE. + Expected to fail until accumulation_input and drainage_input parameters are implemented. + """ self.assertModule( "r.watershed", elevation=self.elevation, @@ -95,6 +97,51 @@ def test_reuse_workflow(self): self.assertRasterExists(ls_factor) + @unittest.expectedFailure + def test_input_validation(self): + """Test input validation: both inputs required, depression incompatible. + Expected to fail until accumulation_input and drainage_input parameters are implemented. + """ + self.assertModule( + "r.watershed", + elevation=self.elevation, + accumulation=self.accumulation, + drainage=self.drainage, + ) + + # Missing drainage_input should fail + self.assertModuleFail( + "r.watershed", + elevation=self.elevation, + accumulation_input=self.accumulation, + accumulation="reuse_out", + ) + + # Missing accumulation_input should fail + self.assertModuleFail( + "r.watershed", + elevation=self.elevation, + drainage_input=self.drainage, + accumulation="reuse_out", + ) + + # Depression incompatible with input maps + self.runModule("r.mapcalc", expression="reuse_dep = 1") + self.assertModuleFail( + "r.watershed", + elevation=self.elevation, + accumulation_input=self.accumulation, + drainage_input=self.drainage, + depression="reuse_dep", + accumulation="reuse_out", + ) + + @unittest.skip( + "Basin delineation with input maps not yet supported - TODO: recalculate flow" + ) + def test_basin_limitation(self): + """TODO: Add""" + if __name__ == "__main__": test() From 861d6de05b5d6fb220da4b71a3a43598b5c21dbc Mon Sep 17 00:00:00 2001 From: Sumit Chintanwar Date: Thu, 29 Jan 2026 04:10:35 +0530 Subject: [PATCH 3/5] r.watershed: Add performance tests for map reuse on large regions --- .../testsuite/test_r_watershed_reuse.py | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/raster/r.watershed/testsuite/test_r_watershed_reuse.py b/raster/r.watershed/testsuite/test_r_watershed_reuse.py index 68b6f576ef9..cd62e191ee3 100644 --- a/raster/r.watershed/testsuite/test_r_watershed_reuse.py +++ b/raster/r.watershed/testsuite/test_r_watershed_reuse.py @@ -25,10 +25,12 @@ class TestWatershedReuse(TestCase): @classmethod def setUpClass(cls): cls.use_temp_region() + cls.runModule( "r.mapcalc", expression=f"{cls.elevation} = row() + col()", ) + cls.runModule("g.region", raster=cls.elevation, res=10) @classmethod @@ -50,6 +52,7 @@ def test_reuse_workflow(self): """Test complete reuse workflow: generate, reuse for accumulation and RUSLE. Expected to fail until accumulation_input and drainage_input parameters are implemented. """ + # Generate flow maps self.assertModule( "r.watershed", elevation=self.elevation, @@ -140,7 +143,72 @@ def test_input_validation(self): "Basin delineation with input maps not yet supported - TODO: recalculate flow" ) def test_basin_limitation(self): - """TODO: Add""" + """TODO: Add basin delineation test once supported""" + + +class TestWatershedReuseLarge(TestCase): + """Performance test on full extent - SKIP FOR CI + Remove @unittest.skip to run. + Expected to fail until accumulation_input and drainage_input parameters are implemented. + + """ + + elevation = "reuse_large_elevation" + + @classmethod + def setUpClass(cls): + """Set up larger region for performance testing""" + cls.use_temp_region() + + cls.runModule("g.region", n=5000, s=0, e=5000, w=0, res=10) + cls.runModule( + "r.mapcalc", + expression=f"{cls.elevation} = row() + col() + rand(0, 100)", + ) + cls.runModule("g.region", raster=cls.elevation) + + @classmethod + def tearDownClass(cls): + cls.runModule("g.remove", flags="f", type="raster", name=cls.elevation) + cls.del_temp_region() + + def tearDown(self): + self.runModule( + "g.remove", + flags="f", + type="raster", + pattern="reuse_large_*", + exclude=self.elevation, + ) + + @unittest.skip("DEACTIVATED FOR CI") + def test_large_area_iterations(self): + """Test parameter iterations without recalculating flow + Expected to fail until accumulation_input and drainage_input parameters are implemented. + + """ + accum, drain = "reuse_large_accum", "reuse_large_drain" + + self.assertModule( + "r.watershed", + elevation=self.elevation, + accumulation=accum, + drainage=drain, + ) + + for length in [50, 100, 200, 500, 1000]: + ls_name = f"reuse_large_ls_{length}" + self.assertModule( + "r.watershed", + elevation=self.elevation, + accumulation_input=accum, + drainage_input=drain, + threshold=1000, + length_slope=ls_name, + max_slope_length=length, + overwrite=True, + ) + self.assertRasterExists(ls_name) if __name__ == "__main__": From 61dd2781ebc0c2c8ad85196d8ed76ac1f24bab6a Mon Sep 17 00:00:00 2001 From: Sumit Chintanwar <142656110+sumitchintanwar@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:57:50 +0530 Subject: [PATCH 4/5] Skip TestWatershedReuseLarge for CI Deactivate CI for TestWatershedReuseLarge until parameters --- raster/r.watershed/testsuite/test_r_watershed_reuse.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raster/r.watershed/testsuite/test_r_watershed_reuse.py b/raster/r.watershed/testsuite/test_r_watershed_reuse.py index cd62e191ee3..223b1df31b5 100644 --- a/raster/r.watershed/testsuite/test_r_watershed_reuse.py +++ b/raster/r.watershed/testsuite/test_r_watershed_reuse.py @@ -145,7 +145,7 @@ def test_input_validation(self): def test_basin_limitation(self): """TODO: Add basin delineation test once supported""" - +@unittest.skip("DEACTIVATED FOR CI") class TestWatershedReuseLarge(TestCase): """Performance test on full extent - SKIP FOR CI Remove @unittest.skip to run. From cd18a8de94ec3c821f46654f9f29d64d1734a0ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sat, 14 Mar 2026 18:14:36 -0400 Subject: [PATCH 5/5] Update test_r_watershed_reuse.py Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> --- raster/r.watershed/testsuite/test_r_watershed_reuse.py | 1 + 1 file changed, 1 insertion(+) diff --git a/raster/r.watershed/testsuite/test_r_watershed_reuse.py b/raster/r.watershed/testsuite/test_r_watershed_reuse.py index 223b1df31b5..3dbc4ae02a1 100644 --- a/raster/r.watershed/testsuite/test_r_watershed_reuse.py +++ b/raster/r.watershed/testsuite/test_r_watershed_reuse.py @@ -145,6 +145,7 @@ def test_input_validation(self): def test_basin_limitation(self): """TODO: Add basin delineation test once supported""" + @unittest.skip("DEACTIVATED FOR CI") class TestWatershedReuseLarge(TestCase): """Performance test on full extent - SKIP FOR CI