Skip to content

Commit 45bbb53

Browse files
osgeoweblateselma-Bentaiba
authored andcommitted
temporal: add unit tests for AbstractSpaceTimeDataset shift() and snap()
Introduces unittests_temporal_stds_management.py with dedicated coverage for core management methods that had no existing tests. - test_shift_absolute_time: timestamps advance by the given granularity - test_shift_invalid_granularity: regression test for #7228, ensures invalid input returns False rather than a formatting TypeError - test_snap_closes_gaps: end times are snapped to next map's start time test_shift_relative_time is commented out because relative time unit type resolution fails in GRASS 7.8.7 with 'unsupported unit type: None'. Needs verification against GRASS 8.x once #7231 is merged. Related: #7228 (fix string formatting in fatal error messages) #7231 (add explicit return True in shift())
1 parent 65a1515 commit 45bbb53

1 file changed

Lines changed: 230 additions & 0 deletions

File tree

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
"""Unit tests for shift() and snap() methods
2+
of AbstractSpaceTimeDataset
3+
4+
(C) 2026 by the GRASS Development Team
5+
This program is free software under the GNU General Public
6+
License (>=v2). Read the file COPYING that comes with GRASS
7+
for details.
8+
9+
:authors: Selma Bentaiba
10+
"""
11+
12+
import datetime
13+
import os
14+
import uuid
15+
16+
from grass.gunittest.case import TestCase
17+
from grass.gunittest.main import test
18+
19+
import grass.temporal as tgis
20+
21+
22+
class TestShift(TestCase):
23+
@classmethod
24+
def setUpClass(cls) -> None:
25+
os.putenv("GRASS_OVERWRITE", "1")
26+
cls.runModule("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1")
27+
tgis.init()
28+
cls.use_temp_region()
29+
cls.runModule(
30+
"g.region", n=80.0, s=0.0, e=120.0, w=0.0, t=1.0, b=0.0, res=10.0
31+
)
32+
33+
@classmethod
34+
def tearDownClass(cls) -> None:
35+
cls.del_temp_region()
36+
37+
def setUp(self) -> None:
38+
uid = uuid.uuid4().hex[:6]
39+
self.map1 = f"map_1_{uid}"
40+
self.map2 = f"map_2_{uid}"
41+
self.strds_name = f"shift_test_{uid}"
42+
self.runModule(
43+
"r.mapcalc", overwrite=True, quiet=True,
44+
expression=f"{self.map1} = 1"
45+
)
46+
self.runModule(
47+
"r.mapcalc", overwrite=True, quiet=True,
48+
expression=f"{self.map2} = 2"
49+
)
50+
self.strds = tgis.open_new_stds(
51+
name=self.strds_name,
52+
type="strds",
53+
temporaltype="absolute",
54+
title="Test shift",
55+
descr="Test shift",
56+
semantic="field",
57+
overwrite=True,
58+
)
59+
tgis.register_maps_in_space_time_dataset(
60+
type="raster",
61+
name=self.strds.get_name(),
62+
maps=f"{self.map1},{self.map2}",
63+
start="2001-01-01",
64+
increment="1 day",
65+
interval=True,
66+
)
67+
68+
def tearDown(self) -> None:
69+
self.runModule(
70+
"t.unregister",
71+
type="raster",
72+
maps=f"{self.map1},{self.map2}",
73+
quiet=True,
74+
)
75+
self.runModule(
76+
"g.remove", flags="f", type="raster",
77+
name=f"{self.map1},{self.map2}", quiet=True
78+
)
79+
self.strds.delete()
80+
81+
def test_shift_absolute_time(self) -> None:
82+
"""shift() moves all map timestamps forward by the given granularity"""
83+
self.strds.shift(gran="1 days")
84+
85+
maps = self.strds.get_registered_maps_as_objects(
86+
where=None, order="start_time"
87+
)
88+
start, end = maps[0].get_temporal_extent_as_tuple()
89+
self.assertEqual(start, datetime.datetime(2001, 1, 2))
90+
self.assertEqual(end, datetime.datetime(2001, 1, 3))
91+
92+
start, end = maps[1].get_temporal_extent_as_tuple()
93+
self.assertEqual(start, datetime.datetime(2001, 1, 3))
94+
self.assertEqual(end, datetime.datetime(2001, 1, 4))
95+
96+
def test_shift_invalid_granularity(self) -> None:
97+
"""shift() returns False for invalid granularity string"""
98+
result = self.strds.shift(gran="invalid")
99+
self.assertFalse(result)
100+
101+
# TODO: test_shift_relative_time is disabled pending GRASS 8.x testing.
102+
# The registration of relative time maps with unit="seconds" fails in
103+
# GRASS 7.8.7 with "Unsupported relative time unit type: None".
104+
# This should be re-enabled and verified once tested against GRASS 8.x.
105+
# Related to #7231.
106+
#
107+
# def test_shift_relative_time(self) -> None:
108+
# """shift() moves timestamps forward for relative time datasets"""
109+
# uid = uuid.uuid4().hex[:6]
110+
# map_rel_1 = f"map_rel_1_{uid}"
111+
# map_rel_2 = f"map_rel_2_{uid}"
112+
# strds_rel_name = f"shift_test_rel_{uid}"
113+
# self.runModule(
114+
# "r.mapcalc", overwrite=True, quiet=True,
115+
# expression=f"{map_rel_1} = 1"
116+
# )
117+
# self.runModule(
118+
# "r.mapcalc", overwrite=True, quiet=True,
119+
# expression=f"{map_rel_2} = 2"
120+
# )
121+
# strds_rel = tgis.open_new_stds(
122+
# name=strds_rel_name,
123+
# type="strds",
124+
# temporaltype="relative",
125+
# title="Test shift relative",
126+
# descr="Test shift relative",
127+
# semantic="field",
128+
# overwrite=True,
129+
# )
130+
# tgis.register_maps_in_space_time_dataset(
131+
# type="raster",
132+
# name=strds_rel.get_name(),
133+
# maps=f"{map_rel_1},{map_rel_2}",
134+
# start=0,
135+
# increment=1,
136+
# unit="seconds",
137+
# interval=True,
138+
# )
139+
# strds_rel.shift(gran="5")
140+
# maps = strds_rel.get_registered_maps_as_objects(
141+
# where=None, order="start_time"
142+
# )
143+
# start, end = maps[0].get_temporal_extent_as_tuple()
144+
# self.assertEqual(start, 5)
145+
# self.assertEqual(end, 6)
146+
# self.runModule(
147+
# "t.unregister",
148+
# type="raster",
149+
# maps=f"{map_rel_1},{map_rel_2}",
150+
# quiet=True,
151+
# )
152+
# strds_rel.delete()
153+
# self.runModule(
154+
# "g.remove", flags="f", type="raster",
155+
# name=f"{map_rel_1},{map_rel_2}", quiet=True
156+
# )
157+
158+
class TestSnap(TestCase):
159+
@classmethod
160+
def setUpClass(cls) -> None:
161+
os.putenv("GRASS_OVERWRITE", "1")
162+
cls.runModule("g.gisenv", set="TGIS_USE_CURRENT_MAPSET=1")
163+
tgis.init()
164+
cls.use_temp_region()
165+
cls.runModule(
166+
"g.region", n=80.0, s=0.0, e=120.0, w=0.0, t=1.0, b=0.0, res=10.0
167+
)
168+
169+
@classmethod
170+
def tearDownClass(cls) -> None:
171+
cls.del_temp_region()
172+
173+
def setUp(self) -> None:
174+
uid = uuid.uuid4().hex[:6]
175+
self.map1 = f"map_1_{uid}"
176+
self.map2 = f"map_2_{uid}"
177+
self.strds_name = f"snap_test_{uid}"
178+
self.runModule(
179+
"r.mapcalc", overwrite=True, quiet=True,
180+
expression=f"{self.map1} = 1"
181+
)
182+
self.runModule(
183+
"r.mapcalc", overwrite=True, quiet=True,
184+
expression=f"{self.map2} = 2"
185+
)
186+
self.strds = tgis.open_new_stds(
187+
name=self.strds_name,
188+
type="strds",
189+
temporaltype="absolute",
190+
title="Test snap",
191+
descr="Test snap",
192+
semantic="field",
193+
overwrite=True,
194+
)
195+
tgis.register_maps_in_space_time_dataset(
196+
type="raster",
197+
name=self.strds.get_name(),
198+
maps=f"{self.map1},{self.map2}",
199+
start="2001-01-01",
200+
increment="2 days",
201+
interval=False,
202+
)
203+
204+
def tearDown(self) -> None:
205+
self.runModule(
206+
"t.unregister",
207+
type="raster",
208+
maps=f"{self.map1},{self.map2}",
209+
quiet=True,
210+
)
211+
self.runModule(
212+
"g.remove", flags="f", type="raster",
213+
name=f"{self.map1},{self.map2}", quiet=True
214+
)
215+
self.strds.delete()
216+
217+
def test_snap_closes_gaps(self) -> None:
218+
"""snap() sets end time of each map to start time of next neighbor"""
219+
self.strds.snap()
220+
221+
maps = self.strds.get_registered_maps_as_objects(
222+
where=None, order="start_time"
223+
)
224+
start, end = maps[0].get_temporal_extent_as_tuple()
225+
self.assertEqual(start, datetime.datetime(2001, 1, 1))
226+
self.assertEqual(end, datetime.datetime(2001, 1, 3))
227+
228+
229+
if __name__ == "__main__":
230+
test()

0 commit comments

Comments
 (0)