Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 94 additions & 0 deletions lib/raster3d/tests/lib_raster3d_tilewrite_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"""Test tile index bounds checking in Rast3d_write_tile"""

import os
import subprocess
import sys

import pytest

import grass.script as gs

WRITE_TILE_SCRIPT = """\
import sys
from ctypes import POINTER, byref, c_float, cast

from grass.lib.gis import G_gisinit
from grass.lib.raster import FCELL_TYPE
from grass.lib.raster3d import (
RASTER3D_NO_CACHE,
RASTER3D_Map,
RASTER3D_Region,
Rast3d_adjust_region,
Rast3d_init_defaults,
Rast3d_open_cell_new,
Rast3d_write_tile,
)

index_offset = int(sys.argv[1])

G_gisinit("test")
Rast3d_init_defaults()

region = RASTER3D_Region()
region.north = 100
region.south = 0
region.east = 100
region.west = 0
region.top = 100
region.bottom = 0
region.rows = 10
region.cols = 10
region.depths = 10
Rast3d_adjust_region(byref(region))

handle = Rast3d_open_cell_new(
"bounds_check_3d", FCELL_TYPE, RASTER3D_NO_CACHE, byref(region)
)
if not handle:
sys.exit("Rast3d_open_cell_new failed")
map_ptr = cast(handle, POINTER(RASTER3D_Map))
tile = (c_float * map_ptr.contents.tileSize)()
Rast3d_write_tile(map_ptr, map_ptr.contents.nTiles + index_offset, tile, FCELL_TYPE)
print("tile write accepted")
"""


@pytest.fixture
def session(tmp_path):
"""Active session in an XY project (scope: function)"""
project = tmp_path / "xy_test"
gs.create_project(project)
with gs.setup.init(project, env=os.environ.copy()) as session:
yield session


def write_tile(index_offset, session, tmp_path):
"""Call Rast3d_write_tile with tile index nTiles + index_offset.

Runs in a subprocess because an invalid index triggers a fatal error
which exits the calling process.
"""
script = tmp_path / "write_tile.py"
script.write_text(WRITE_TILE_SCRIPT, encoding="utf-8")
return subprocess.run(
[sys.executable, str(script), str(index_offset)],
env=session.env,
capture_output=True,
text=True,
timeout=60,
check=False,
)


def test_write_tile_accepts_last_index(session, tmp_path):
"""Writing the last valid tile index (nTiles - 1) is accepted"""
result = write_tile(-1, session, tmp_path)
assert result.returncode == 0, result.stderr
assert "tile write accepted" in result.stdout


def test_write_tile_rejects_index_past_end(session, tmp_path):
"""A tile index equal to nTiles is rejected as out of range"""
result = write_tile(0, session, tmp_path)
assert result.returncode != 0, "out-of-range tile index was accepted"
assert "tileIndex out of range" in result.stderr
2 changes: 1 addition & 1 deletion lib/raster3d/tilewrite.c
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ int Rast3d_write_tile(RASTER3D_Map *map, int tileIndex, const void *tile,
int rows, cols, depths, xRedundant, yRedundant, zRedundant, nofNum;

/* valid tileIndex ? */
if ((tileIndex > map->nTiles) || (tileIndex < 0))
if ((tileIndex >= map->nTiles) || (tileIndex < 0))
Rast3d_fatal_error("Rast3d_write_tile: tileIndex out of range");

/* already written ? */
Expand Down
11 changes: 4 additions & 7 deletions raster/r.kappa/testsuite/test_r_kappa.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,13 @@
import os
import json
from pathlib import Path
from tempfile import NamedTemporaryFile

from grass.script import read_command
from grass.script import decode
from grass.script.core import tempname
from grass.script.core import tempfile, tempname
from grass.gunittest.case import TestCase
from grass.gunittest.main import test
from grass.gunittest.checkers import keyvalue_equals, diff_keyvalue
from grass.gunittest.utils import xfail_windows


class MatrixCorrectnessTest(TestCase):
Expand Down Expand Up @@ -488,19 +486,18 @@ def test_stdout(self):
),
)

@xfail_windows
def test_file(self):
for i in range(len(self.references)):
f = NamedTemporaryFile()
outfile = tempfile(create=False)
self.runModule(
"r.kappa",
reference=self.references[i],
classification=self.classifications[i],
output=f.name,
output=outfile,
format="json",
overwrite=True,
)
json_out = json.loads(f.read())
json_out = json.loads(Path(outfile).read_text())
self.assertTrue(
keyvalue_equals(
self.expected_outputs[i], json_out, precision=self.precision
Expand Down
Loading