From 918c02041b8089a967400ba1d083beb77ec3f82c Mon Sep 17 00:00:00 2001 From: RAJVEER42 Date: Tue, 11 Aug 2026 21:56:04 +0530 Subject: [PATCH 1/8] r.support: migrate tests from gunittest to pytest Cover the same four cases as the gunittest testsuite: setting and removing a semantic label at once fails, an over-long label is rejected, r.support assigns the semantic label, and the -b flag clears it. Uses a tmp_path session fixture with a synthetic raster and reads the label back via r.info format=json instead of the ctypes library bindings. --- raster/r.support/tests/r_support_test.py | 56 ++++++++++++++++ raster/r.support/testsuite/test_r_support.py | 69 -------------------- 2 files changed, 56 insertions(+), 69 deletions(-) create mode 100644 raster/r.support/tests/r_support_test.py delete mode 100644 raster/r.support/testsuite/test_r_support.py diff --git a/raster/r.support/tests/r_support_test.py b/raster/r.support/tests/r_support_test.py new file mode 100644 index 00000000000..d583d408151 --- /dev/null +++ b/raster/r.support/tests/r_support_test.py @@ -0,0 +1,56 @@ +import os + +import pytest + +import grass.script as gs +from grass.tools import Tools +from grass.exceptions import CalledModuleError + +SEMANTIC_LABEL = "The_Doors" + + +@pytest.fixture +def session(tmp_path): + """A GRASS session with a single-cell synthetic raster.""" + project = tmp_path / "r_support_project" + gs.create_project(project) + with gs.setup.init(project, env=os.environ.copy()) as session: + tools = Tools(session=session, overwrite=True) + tools.g_region(n=1, s=0, e=1, w=0, res=1) + tools.r_mapcalc(expression="test = 1") + yield session + + +def test_semantic_label_and_removal_are_exclusive(session): + """Setting and removing a semantic label in one call must fail.""" + tools = Tools(session=session) + with pytest.raises(CalledModuleError): + tools.r_support(map="test", semantic_label=SEMANTIC_LABEL, b=True) + + +def test_invalid_semantic_label_is_rejected(session): + """A semantic label longer than the allowed length is rejected.""" + tools = Tools(session=session) + with pytest.raises(CalledModuleError): + tools.r_support(map="test", semantic_label="a" * 256) + + +def test_set_semantic_label(session): + """r.support assigns the semantic label to the map.""" + tools = Tools(session=session) + tools.r_support(map="test", semantic_label=SEMANTIC_LABEL) + + info = tools.r_info(map="test", format="json").json + assert info["semantic_label"] == SEMANTIC_LABEL + + +def test_remove_semantic_label(session): + """The -b flag clears a previously assigned semantic label.""" + tools = Tools(session=session) + tools.r_support(map="test", semantic_label=SEMANTIC_LABEL) + info = tools.r_info(map="test", format="json").json + assert info["semantic_label"] == SEMANTIC_LABEL + + tools.r_support(map="test", flags="b") + info = tools.r_info(map="test", format="json").json + assert not info.get("semantic_label") diff --git a/raster/r.support/testsuite/test_r_support.py b/raster/r.support/testsuite/test_r_support.py deleted file mode 100644 index 9a024c4a723..00000000000 --- a/raster/r.support/testsuite/test_r_support.py +++ /dev/null @@ -1,69 +0,0 @@ -"""Test of r.support basic functionality - -@author Maris Nartiss - -@copyright 2021 by Maris Nartiss and the GRASS Development Team - -@license This program is free software under the GNU General Public License (>=v2). -Read the file COPYING that comes with GRASS -for details -""" - -import random -import string - -from grass.gunittest.case import TestCase -from grass.gunittest.main import test - -from grass.script.core import tempname -from grass.pygrass.gis import Mapset -from grass.pygrass import utils - -from grass.lib.gis import G_remove_misc -from grass.lib.raster import Rast_read_semantic_label - - -class RSupportSemanticLabelHandlingTestCase(TestCase): - @classmethod - def setUpClass(cls): - cls.map = tempname(10) - cls.mapset = Mapset().name - cls.semantic_label = "The_Doors" - cls.use_temp_region() - cls.runModule("g.region", n=1, s=0, e=1, w=0, res=1) - cls.runModule("r.mapcalc", expression="{} = 1".format(cls.map)) - - @classmethod - def tearDownClass(cls): - cls.del_temp_region() - cls.runModule("g.remove", flags="f", type="raster", name=cls.map) - - def test_exclusitivity(self): - self.assertModuleFail( - "r.support", map=self.map, semantic_label=self.semantic_label, b=True - ) - - def test_semantic_label_invalid(self): - self.assertModuleFail( - "r.support", - map=self.map, - semantic_label="".join(random.choices(string.ascii_letters, k=256)), - ) - - def test_set_semantic_label(self): - G_remove_misc("cell_misc", "semantic_label", self.map) - self.assertModule("r.support", map=self.map, semantic_label=self.semantic_label) - ret = utils.decode(Rast_read_semantic_label(self.map, self.mapset)) - self.assertEqual(ret, self.semantic_label) - - def test_remove_semantic_label(self): - self.assertModule("r.support", map=self.map, semantic_label=self.semantic_label) - ret = Rast_read_semantic_label(self.map, self.mapset) - self.assertTrue(bool(ret)) - self.assertModule("r.support", map=self.map, b=True) - ret = Rast_read_semantic_label(self.map, self.mapset) - self.assertFalse(bool(ret)) - - -if __name__ == "__main__": - test() From b51d3bbaf00b2dfeae63dc3c884dec87dcb49477 Mon Sep 17 00:00:00 2001 From: Rajveer* Date: Tue, 11 Aug 2026 22:07:14 +0530 Subject: [PATCH 2/8] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- raster/r.support/tests/r_support_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/raster/r.support/tests/r_support_test.py b/raster/r.support/tests/r_support_test.py index d583d408151..4e85b7d058a 100644 --- a/raster/r.support/tests/r_support_test.py +++ b/raster/r.support/tests/r_support_test.py @@ -23,9 +23,8 @@ def session(tmp_path): def test_semantic_label_and_removal_are_exclusive(session): """Setting and removing a semantic label in one call must fail.""" - tools = Tools(session=session) with pytest.raises(CalledModuleError): - tools.r_support(map="test", semantic_label=SEMANTIC_LABEL, b=True) + tools.r_support(map="test", semantic_label=SEMANTIC_LABEL, flags="b") def test_invalid_semantic_label_is_rejected(session): From 166d92212c2c6575c39e9ed2c6a06a31623dbad6 Mon Sep 17 00:00:00 2001 From: RAJVEER42 Date: Tue, 11 Aug 2026 22:23:10 +0530 Subject: [PATCH 3/8] Create the Tools instance in the exclusive-flags test test_semantic_label_and_removal_are_exclusive used tools without creating it in the test, which flake8 flags as F821 and which raises NameError at run time. --- raster/r.support/tests/r_support_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/raster/r.support/tests/r_support_test.py b/raster/r.support/tests/r_support_test.py index 4e85b7d058a..308d8652696 100644 --- a/raster/r.support/tests/r_support_test.py +++ b/raster/r.support/tests/r_support_test.py @@ -23,6 +23,7 @@ def session(tmp_path): def test_semantic_label_and_removal_are_exclusive(session): """Setting and removing a semantic label in one call must fail.""" + tools = Tools(session=session) with pytest.raises(CalledModuleError): tools.r_support(map="test", semantic_label=SEMANTIC_LABEL, flags="b") From f805989ed5c55a7f6c4472671fea81fcf8257896 Mon Sep 17 00:00:00 2001 From: Valyrian-Code Date: Tue, 11 Aug 2026 22:46:43 +0530 Subject: [PATCH 4/8] r.support: drop unneeded overwrite=True in the test fixture --- raster/r.support/tests/r_support_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raster/r.support/tests/r_support_test.py b/raster/r.support/tests/r_support_test.py index 308d8652696..2f00ef4db03 100644 --- a/raster/r.support/tests/r_support_test.py +++ b/raster/r.support/tests/r_support_test.py @@ -15,7 +15,7 @@ def session(tmp_path): project = tmp_path / "r_support_project" gs.create_project(project) with gs.setup.init(project, env=os.environ.copy()) as session: - tools = Tools(session=session, overwrite=True) + tools = Tools(session=session) tools.g_region(n=1, s=0, e=1, w=0, res=1) tools.r_mapcalc(expression="test = 1") yield session From 449fcbb649437b997b7488ff3f9079da0eb01408 Mon Sep 17 00:00:00 2001 From: Valyrian-Code Date: Thu, 27 Aug 2026 09:36:37 +0530 Subject: [PATCH 5/8] r.support: add SPDX-License-Identifier header --- raster/r.support/tests/r_support_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/raster/r.support/tests/r_support_test.py b/raster/r.support/tests/r_support_test.py index 2f00ef4db03..2ce71ab4749 100644 --- a/raster/r.support/tests/r_support_test.py +++ b/raster/r.support/tests/r_support_test.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: GPL-2.0-or-later import os import pytest From 98bb8a4034ca556fcc2bd25fe6df91d66f94bd27 Mon Sep 17 00:00:00 2001 From: Valyrian-Code Date: Fri, 28 Aug 2026 09:44:33 +0530 Subject: [PATCH 6/8] r.support: add coverage for metadata, history and null-file flags --- raster/r.support/tests/r_support_test.py | 78 ++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/raster/r.support/tests/r_support_test.py b/raster/r.support/tests/r_support_test.py index 2ce71ab4749..29d06470f6f 100644 --- a/raster/r.support/tests/r_support_test.py +++ b/raster/r.support/tests/r_support_test.py @@ -55,3 +55,81 @@ def test_remove_semantic_label(session): tools.r_support(map="test", flags="b") info = tools.r_info(map="test", format="json").json assert not info.get("semantic_label") + + +def test_set_metadata_fields(session): + """title, units, vdatum, source1/source2 and description are all stored.""" + tools = Tools(session=session) + tools.r_support( + map="test", + title="My Title", + units="meters", + vdatum="WGS84", + source1="Source A", + source2="Source B", + description="A description", + ) + + info = tools.r_info(map="test", format="json").json + assert info["title"] == "My Title" + assert info["units"] == "meters" + assert info["vdatum"] == "WGS84" + assert info["source1"] == "Source A" + assert info["source2"] == "Source B" + assert info["description"] == "A description" + + +def test_append_history(session): + """Each history= call appends a line rather than replacing prior ones.""" + tools = Tools(session=session) + tools.r_support(map="test", history="First history line") + tools.r_support(map="test", history="Second history line") + + info = tools.r_info(map="test", format="json").json + assert info["comments"].endswith("First history line\nSecond history line") + + +def test_savehistory_and_loadhistory(session, tmp_path): + """savehistory writes the history to a file that loadhistory can restore + onto another map.""" + tools = Tools(session=session) + tools.r_support(map="test", history="Line one") + tools.r_support(map="test", history="Line two") + expected = tools.r_info(map="test", format="json").json["comments"] + + history_file = tmp_path / "history.txt" + tools.r_support(map="test", savehistory=str(history_file)) + assert history_file.read_text().strip() == expected + + tools.r_mapcalc(expression="test2 = 2") + tools.r_support(map="test2", loadhistory=str(history_file)) + info = tools.r_info(map="test2", format="json").json + assert info["comments"] == expected + + +def test_reset_null_file_clears_null_cells(session): + """The -n flag (re)creates the null file, here clearing the null status + of a cell that matches the CELL type's null sentinel on disk.""" + tools = Tools(session=session) + tools.g_region(n=1, s=0, e=3, w=0, res=1) + tools.r_mapcalc(expression="with_null = if(col() == 2, null(), col())") + before = tools.r_univar(map="with_null", format="json").json + assert before["null_cells"] == 1 + + tools.r_support(map="with_null", flags="n") + after = tools.r_univar(map="with_null", format="json").json + assert after["null_cells"] == 0 + + +def test_delete_null_file_restores_null_cells(session): + """The -d flag removes the null file, falling back to the implicit + null sentinel stored in the raw cell values.""" + tools = Tools(session=session) + tools.g_region(n=1, s=0, e=3, w=0, res=1) + tools.r_mapcalc(expression="with_null = if(col() == 2, null(), col())") + tools.r_support(map="with_null", flags="n") + assert tools.r_univar(map="with_null", format="json").json["null_cells"] == 0 + + tools.r_support(map="with_null", flags="d") + after = tools.r_univar(map="with_null", format="json").json + assert after["null_cells"] == 1 From 94dd7845df7a0edf66b5e7ae233e005e63911a4a Mon Sep 17 00:00:00 2001 From: RAJVEER42 Date: Fri, 11 Sep 2026 04:02:07 +0530 Subject: [PATCH 7/8] r.support: assert exact savehistory file content, not stripped .strip() masked the trailing newline savehistory writes after each history line (main.c fprintf(fp, "%s\n", ...)), so the test would not notice extra blank lines or other whitespace regressions in the output file. Assert the exact expected content instead. --- raster/r.support/tests/r_support_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/raster/r.support/tests/r_support_test.py b/raster/r.support/tests/r_support_test.py index 29d06470f6f..79faa99c90a 100644 --- a/raster/r.support/tests/r_support_test.py +++ b/raster/r.support/tests/r_support_test.py @@ -99,7 +99,7 @@ def test_savehistory_and_loadhistory(session, tmp_path): history_file = tmp_path / "history.txt" tools.r_support(map="test", savehistory=str(history_file)) - assert history_file.read_text().strip() == expected + assert history_file.read_text() == expected + "\n" tools.r_mapcalc(expression="test2 = 2") tools.r_support(map="test2", loadhistory=str(history_file)) From 535d5a2fef11a21a3750256ccea2b61cf34a3150 Mon Sep 17 00:00:00 2001 From: Anna Petrasova Date: Tue, 22 Sep 2026 22:09:41 -0400 Subject: [PATCH 8/8] Apply batched suggestions from code review Co-authored-by: Anna Petrasova --- raster/r.support/tests/r_support_test.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/raster/r.support/tests/r_support_test.py b/raster/r.support/tests/r_support_test.py index 79faa99c90a..23cf1ceee6b 100644 --- a/raster/r.support/tests/r_support_test.py +++ b/raster/r.support/tests/r_support_test.py @@ -70,7 +70,7 @@ def test_set_metadata_fields(session): description="A description", ) - info = tools.r_info(map="test", format="json").json + info = tools.r_info(map="test", format="json") assert info["title"] == "My Title" assert info["units"] == "meters" assert info["vdatum"] == "WGS84" @@ -85,7 +85,7 @@ def test_append_history(session): tools.r_support(map="test", history="First history line") tools.r_support(map="test", history="Second history line") - info = tools.r_info(map="test", format="json").json + info = tools.r_info(map="test", format="json") assert info["comments"].endswith("First history line\nSecond history line") @@ -95,7 +95,7 @@ def test_savehistory_and_loadhistory(session, tmp_path): tools = Tools(session=session) tools.r_support(map="test", history="Line one") tools.r_support(map="test", history="Line two") - expected = tools.r_info(map="test", format="json").json["comments"] + expected = tools.r_info(map="test", format="json")["comments"] history_file = tmp_path / "history.txt" tools.r_support(map="test", savehistory=str(history_file)) @@ -103,7 +103,7 @@ def test_savehistory_and_loadhistory(session, tmp_path): tools.r_mapcalc(expression="test2 = 2") tools.r_support(map="test2", loadhistory=str(history_file)) - info = tools.r_info(map="test2", format="json").json + info = tools.r_info(map="test2", format="json") assert info["comments"] == expected @@ -113,11 +113,11 @@ def test_reset_null_file_clears_null_cells(session): tools = Tools(session=session) tools.g_region(n=1, s=0, e=3, w=0, res=1) tools.r_mapcalc(expression="with_null = if(col() == 2, null(), col())") - before = tools.r_univar(map="with_null", format="json").json + before = tools.r_univar(map="with_null", format="json") assert before["null_cells"] == 1 tools.r_support(map="with_null", flags="n") - after = tools.r_univar(map="with_null", format="json").json + after = tools.r_univar(map="with_null", format="json") assert after["null_cells"] == 0 @@ -128,8 +128,8 @@ def test_delete_null_file_restores_null_cells(session): tools.g_region(n=1, s=0, e=3, w=0, res=1) tools.r_mapcalc(expression="with_null = if(col() == 2, null(), col())") tools.r_support(map="with_null", flags="n") - assert tools.r_univar(map="with_null", format="json").json["null_cells"] == 0 + assert tools.r_univar(map="with_null", format="json")["null_cells"] == 0 tools.r_support(map="with_null", flags="d") - after = tools.r_univar(map="with_null", format="json").json + after = tools.r_univar(map="with_null", format="json") assert after["null_cells"] == 1