From 0b0056dca184e89c1cca4cc893d210d6ea8eef51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Fri, 4 Sep 2026 00:11:10 +0000 Subject: [PATCH 1/3] db.univar: Fix crash in in-memory sort fallback on trailing blank line sortfile()'s in-memory fallback (used when the 'sort' command is unavailable, e.g. on Windows) converted every line to float without skipping empty ones, crashing on a trailing blank line with ValueError: could not convert string to float: ''. The main statistics loop already skips empty lines; apply the same handling here. Found and fixed with the help of Claude Code, using a Windows CI test report to diagnose the failure. --- scripts/db.univar/db.univar.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/scripts/db.univar/db.univar.py b/scripts/db.univar/db.univar.py index fc8fb670e93..b0eaf600163 100755 --- a/scripts/db.univar/db.univar.py +++ b/scripts/db.univar/db.univar.py @@ -77,9 +77,7 @@ def sortfile(infile, outfile): else: # FIXME: we need a large-file sorting function gs.warning(_("'sort' not found: sorting in memory")) - lines = inf.readlines() - for i in range(len(lines)): - lines[i] = float(lines[i].rstrip("\r\n")) + lines = [float(line) for line in (line.strip() for line in inf) if line] lines.sort() outf.writelines(str(line) + "\n" for line in lines) From 7255e925ab7670e08b4ab036be287450a60dcd4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Fri, 4 Sep 2026 00:11:10 +0000 Subject: [PATCH 2/3] lib/init: Fix grass.bat not found on Windows in test_grass_tmp_mapset GISBASE itself (where the grass.bat launcher lives) is never added to PATH on Windows, only GISBASE\bin is, so the bare "grass.bat" name failed with FileNotFoundError when this test ran inside an existing GRASS session (as it does under grass.gunittest.main in CI). Resolve the launcher through GISBASE when it is set, keeping the bare name as a fallback otherwise. Root-caused with the help of Claude Code, by tracing the Windows OSGeo4W CI build scripts to see where the launcher batch file ends up and what gets added to PATH, and verified by reproducing and fixing the failure in a scratch Linux build with os.name patched to "nt". --- lib/init/testsuite/test_grass_tmp_mapset.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lib/init/testsuite/test_grass_tmp_mapset.py b/lib/init/testsuite/test_grass_tmp_mapset.py index 92cf3b7e426..4ea5d935033 100644 --- a/lib/init/testsuite/test_grass_tmp_mapset.py +++ b/lib/init/testsuite/test_grass_tmp_mapset.py @@ -26,10 +26,18 @@ class TestTmpMapset(unittest.TestCase): """Tests --tmp-mapset option of grass command""" - # TODO: here we need a name of or path to the main GRASS executable - # TODO: support OSGeo4W executable with: - # executable = "grass" if os.name != "nt" else "grass86.bat" - executable = "grass" if os.name != "nt" else "grass.bat" + # On Windows, GISBASE itself (where the grass.bat launcher lives) is + # never added to PATH, only GISBASE\bin is, so a bare "grass.bat" is + # not found when this test runs inside an existing GRASS session (e.g. + # under grass.gunittest.main). Resolve the full path via GISBASE when + # available and fall back to the bare name otherwise. + _gisbase = os.environ.get("GISBASE") + if os.name != "nt": + executable = "grass" + elif _gisbase: + executable = os.path.join(_gisbase, "grass.bat") + else: + executable = "grass.bat" # an arbitrary, but identifiable and fairly unique name location = "test_tmp_mapset_xy" From 8ff549089c813a374c07672d6552d42e3f5e9f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edouard=20Choini=C3=A8re?= <27212526+echoix@users.noreply.github.com> Date: Sat, 5 Sep 2026 17:39:26 +0000 Subject: [PATCH 3/3] lib/init: Note OSGeo4W's versioned launcher name isn't handled Restores the known-limitation note dropped when the old TODO comments were replaced: the GISBASE-based executable resolution assumes a from-source build's grass.bat, but an OSGeo4W package install names the launcher with a version suffix (e.g. grass86.bat), so it isn't covered here. --- lib/init/testsuite/test_grass_tmp_mapset.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/init/testsuite/test_grass_tmp_mapset.py b/lib/init/testsuite/test_grass_tmp_mapset.py index 4ea5d935033..000f202c73b 100644 --- a/lib/init/testsuite/test_grass_tmp_mapset.py +++ b/lib/init/testsuite/test_grass_tmp_mapset.py @@ -31,6 +31,9 @@ class TestTmpMapset(unittest.TestCase): # not found when this test runs inside an existing GRASS session (e.g. # under grass.gunittest.main). Resolve the full path via GISBASE when # available and fall back to the bare name otherwise. + # Note: on an OSGeo4W package install, the launcher name is versioned + # (e.g. grass86.bat instead of grass.bat), so this resolution does not + # apply there; it only covers a from-source build's layout. _gisbase = os.environ.get("GISBASE") if os.name != "nt": executable = "grass"