1010from xrlint .config import Config , ConfigObject
1111from xrlint .constants import CORE_PLUGIN_NAME , DATASET_ROOT_NAME
1212from xrlint .linter import Linter , new_linter
13- from xrlint .node import AttrNode , AttrsNode , DatasetNode , VariableNode
13+ from xrlint .node import AttrNode , AttrsNode , DatasetNode , VariableNode , DataTreeNode
1414from xrlint .plugin import new_plugin
1515from xrlint .processor import ProcessorOp
1616from xrlint .result import Message , Result
@@ -97,6 +97,7 @@ def assert_result_ok(self, result: Result, expected_message: str):
9797
9898
9999class LinterValidateTest (TestCase ):
100+ # noinspection PyUnusedLocal
100101 def setUp (self ):
101102 plugin = new_plugin (name = "test" )
102103
@@ -113,7 +114,7 @@ def validate_attrs(self, ctx: RuleContext, node: AttrsNode):
113114 ctx .report ("Empty attributes" )
114115
115116 @plugin .define_rule ("data-var-dim-must-have-coord" )
116- class DataArrayVer (RuleOp ):
117+ class VariableVer (RuleOp ):
117118 def validate_variable (self , ctx : RuleContext , node : VariableNode ):
118119 if node .in_data_vars ():
119120 for dim_name in node .array .dims :
@@ -131,6 +132,12 @@ def validate_dataset(self, ctx: RuleContext, node: DatasetNode):
131132 ctx .report ("Dataset does not have data variables" )
132133 raise RuleExit # no need to traverse further
133134
135+ @plugin .define_rule ("datatree-without-data-vars" )
136+ class DataTreeVer (RuleOp ):
137+ def validate_datatree (self , ctx : RuleContext , node : DataTreeNode ):
138+ if len (node .datatree .data_vars ) == 0 :
139+ ctx .report ("DataTree does not have data variables" )
140+
134141 @plugin .define_processor ("multi-level-dataset" )
135142 class MultiLevelDataset (ProcessorOp ):
136143 def preprocess (
@@ -159,6 +166,7 @@ def test_rules_are_ok(self):
159166 "no-empty-attrs" ,
160167 "data-var-dim-must-have-coord" ,
161168 "dataset-without-data-vars" ,
169+ "datatree-without-data-vars" ,
162170 ],
163171 list (self .linter .config .objects [0 ].plugins ["test" ].rules .keys ()),
164172 )
@@ -171,11 +179,6 @@ def test_linter_respects_rule_severity_error(self):
171179 Result (
172180 config_object = result .config_object ,
173181 file_path = "<dataset>" ,
174- warning_count = 0 ,
175- error_count = 1 ,
176- fatal_error_count = 0 ,
177- fixable_warning_count = 0 ,
178- fixable_error_count = 0 ,
179182 messages = [
180183 Message (
181184 message = "Dataset does not have data variables" ,
@@ -187,6 +190,9 @@ def test_linter_respects_rule_severity_error(self):
187190 ),
188191 result ,
189192 )
193+ self .assertEqual (0 , result .warning_count )
194+ self .assertEqual (1 , result .error_count )
195+ self .assertEqual (0 , result .fatal_error_count )
190196
191197 def test_linter_respects_rule_severity_warn (self ):
192198 result = self .linter .validate (
@@ -196,11 +202,6 @@ def test_linter_respects_rule_severity_warn(self):
196202 Result (
197203 config_object = result .config_object ,
198204 file_path = "<dataset>" ,
199- warning_count = 1 ,
200- error_count = 0 ,
201- fatal_error_count = 0 ,
202- fixable_warning_count = 0 ,
203- fixable_error_count = 0 ,
204205 messages = [
205206 Message (
206207 message = "Dataset does not have data variables" ,
@@ -212,6 +213,9 @@ def test_linter_respects_rule_severity_warn(self):
212213 ),
213214 result ,
214215 )
216+ self .assertEqual (1 , result .warning_count )
217+ self .assertEqual (0 , result .error_count )
218+ self .assertEqual (0 , result .fatal_error_count )
215219
216220 def test_linter_respects_rule_severity_off (self ):
217221 result = self .linter .validate (
@@ -221,15 +225,13 @@ def test_linter_respects_rule_severity_off(self):
221225 Result (
222226 config_object = result .config_object ,
223227 file_path = "<dataset>" ,
224- warning_count = 0 ,
225- error_count = 0 ,
226- fatal_error_count = 0 ,
227- fixable_warning_count = 0 ,
228- fixable_error_count = 0 ,
229228 messages = [],
230229 ),
231230 result ,
232231 )
232+ self .assertEqual (0 , result .warning_count )
233+ self .assertEqual (0 , result .error_count )
234+ self .assertEqual (0 , result .fatal_error_count )
233235
234236 def test_linter_recognized_unknown_rule (self ):
235237 result = self .linter .validate (xr .Dataset (), rules = {"test/dataset-is-fast" : 2 })
@@ -246,6 +248,66 @@ def test_linter_recognized_unknown_rule(self):
246248 result .messages ,
247249 )
248250
251+ def test_linter_recognized_datatree_rule (self ):
252+ result = self .linter .validate (
253+ xr .DataTree (
254+ children = {
255+ "measurement" : xr .DataTree (
256+ children = {
257+ "r10m" : xr .DataTree (),
258+ "r20m" : xr .DataTree (),
259+ "r60m" : xr .DataTree (),
260+ }
261+ )
262+ }
263+ ),
264+ rules = {"test/datatree-without-data-vars" : 2 },
265+ )
266+ self .assertEqual (
267+ [
268+ Message (
269+ message = "DataTree does not have data variables" ,
270+ node_path = "dt" ,
271+ rule_id = "test/datatree-without-data-vars" ,
272+ severity = 2 ,
273+ fatal = None ,
274+ fix = None ,
275+ suggestions = None ,
276+ ),
277+ Message (
278+ message = "DataTree does not have data variables" ,
279+ node_path = "dt/measurement" ,
280+ rule_id = "test/datatree-without-data-vars" ,
281+ severity = 2 ,
282+ fatal = None ,
283+ fix = None ,
284+ suggestions = None ,
285+ ),
286+ Message (
287+ message = "DataTree does not have data variables" ,
288+ node_path = "dt/measurement/r10m" ,
289+ rule_id = "test/datatree-without-data-vars" ,
290+ severity = 2 ,
291+ ),
292+ Message (
293+ message = "DataTree does not have data variables" ,
294+ node_path = "dt/measurement/r20m" ,
295+ rule_id = "test/datatree-without-data-vars" ,
296+ severity = 2 ,
297+ ),
298+ Message (
299+ message = "DataTree does not have data variables" ,
300+ node_path = "dt/measurement/r60m" ,
301+ rule_id = "test/datatree-without-data-vars" ,
302+ severity = 2 ,
303+ ),
304+ ],
305+ result .messages ,
306+ )
307+ self .assertEqual (0 , result .warning_count )
308+ self .assertEqual (5 , result .error_count )
309+ self .assertEqual (0 , result .fatal_error_count )
310+
249311 def test_linter_real_life_scenario (self ):
250312 dataset = xr .Dataset (
251313 attrs = {
@@ -286,11 +348,6 @@ def test_linter_real_life_scenario(self):
286348 Result (
287349 config_object = result .config_object ,
288350 file_path = "chl-tsm.zarr" ,
289- warning_count = 1 ,
290- error_count = 3 ,
291- fatal_error_count = 0 ,
292- fixable_warning_count = 0 ,
293- fixable_error_count = 0 ,
294351 messages = [
295352 Message (
296353 message = "Attribute name with space: 'created at'" ,
@@ -328,6 +385,9 @@ def test_linter_real_life_scenario(self):
328385 ),
329386 result ,
330387 )
388+ self .assertEqual (1 , result .warning_count )
389+ self .assertEqual (3 , result .error_count )
390+ self .assertEqual (0 , result .fatal_error_count )
331391
332392 def test_processor_ok (self ):
333393 result = self .linter .validate (
0 commit comments