Parcourir la source

- implemented tests for DataDefinition.validate(data)
- implemented DataDefinition.validate(data)
(has one more feature than the cpp version, you can optionally pass it
a list where it stores errors it finds, for user feedback)


git-svn-id: svn://bind10.isc.org/svn/bind10/branches/parkinglot@720 e5f2f494-b856-4b98-b285-d166d9295462

Jelte Jansen il y a 15 ans
Parent
commit
78a0005a8f

+ 96 - 10
src/lib/config/python/isc/config/datadefinition.py

@@ -19,6 +19,8 @@
 #
 import ast
 
+import isc.cc.data
+
 # file objects are passed around as _io.TextIOWrapper objects
 # import that so we can check those types
 
@@ -36,11 +38,25 @@ class DataDefinition:
         else:
             raise DataDefinitionError("Not a str or file-like object")
 
-    def validate(self, data):
+    def validate(self, data, errors = None):
         """Check whether the given piece of data conforms to this
-           data definition"""
-        # "TODO"
-        return True
+           data definition. If so, it returns True. If not, it will
+           return false. If errors is given, and is an array, a string
+           describing the error will be appended to it. The current
+           version stops as soon as there is one error so this list
+           will not be exhaustive."""
+        data_def = self.get_definition()
+        if 'data_specification' not in data_def:
+            if errors:
+                errors.append("Data definition has no data_specification element")
+            return False
+        data_def = data_def['data_specification']
+        if 'config_data' not in data_def:
+            if errors:
+                errors.append("The is no config_data for this specification")
+            return False
+        errors = []
+        return _validate_spec_list(data_def['config_data'], data, errors)
 
     def __read_data_spec_file(self, file, check = True):
         """Reads the data spec from the given file object.
@@ -102,20 +118,22 @@ def _check_command_spec(commands):
         if type(command) != dict:
             raise DataDefinitionError("command in commands list is not a dict")
         if "command_name" not in command:
-            raise DataDefitionError("no command_name in command item")
+            raise DataDefinitionError("no command_name in command item")
         command_name = command["command_name"]
         if type(command_name) != str:
             raise DataDefinitionError("command_name not a string: " + str(type(command_name)))
         if "command_description" in command:
             if type(command["command_description"]) != str:
-                raise DataDefitionError("command_description not a string in " + command_name)
+                raise DataDefinitionError("command_description not a string in " + command_name)
         if "command_args" in command:
             if type(command["command_args"]) != list:
-                raise DataDefitionError("command_args is not a list in " + command_name)
+                raise DataDefinitionError("command_args is not a list in " + command_name)
             for command_arg in command["command_args"]:
                 if type(command_arg) != dict:
                     raise DataDefinitionError("command argument not a dict in " + command_name)
                 _check_item_spec(command_arg)
+        else:
+            raise DataDefinitionError("command_args missing in " + command_name)
     pass
 
 def _check_item_spec(config_item):
@@ -134,17 +152,19 @@ def _check_item_spec(config_item):
     item_type = config_item["item_type"]
     if type(item_type) != str:
         raise DataDefinitionError("item_type in " + item_name + " is not a string: " + str(type(item_type)))
-    if item_type not in ["integer", "real", "boolean", "string", "list", "map"]:
+    if item_type not in ["integer", "real", "boolean", "string", "list", "map", "any"]:
         raise DataDefinitionError("unknown item_type in " + item_name + ": " + item_type)
     if "item_optional" in config_item:
         if type(config_item["item_optional"]) != bool:
             raise DataDefinitionError("item_default in " + item_name + " is not a boolean")
         if not config_item["item_optional"] and "item_default" not in config_item:
             raise DataDefinitionError("no default value for non-optional item " + item_name)
+    else:
+        raise DataDefinitionError("item_optional not in item " + item_name)
     if "item_default" in config_item:
         item_default = config_item["item_default"]
-        if (item_type == "int" and type(item_default) != int) or \
-           (item_type == "real" and type(item_default) != double) or \
+        if (item_type == "integer" and type(item_default) != int) or \
+           (item_type == "real" and type(item_default) != float) or \
            (item_type == "boolean" and type(item_default) != bool) or \
            (item_type == "string" and type(item_default) != str) or \
            (item_type == "list" and type(item_default) != list) or \
@@ -167,3 +187,69 @@ def _check_item_spec(config_item):
                 raise DataDefinitionError("map_item_spec element is not a dict")
             _check_item_spec(map_item)
 
+
+def _validate_type(spec, value, errors):
+    """Returns true if the value is of the correct type given the
+       specification"""
+    data_type = spec['item_type']
+    if data_type == "integer" and type(value) != int:
+        if errors:
+            errors.append(str(value) + " should be an integer")
+        return False
+    elif data_type == "real" and type(value) != float:
+        if errors:
+            errors.append(str(value) + " should be a real")
+        return False
+    elif data_type == "boolean" and type(value) != bool:
+        if errors:
+            errors.append(str(value) + " should be a boolean")
+        return False
+    elif data_type == "string" and type(value) != str:
+        if errors:
+            errors.append(str(value) + " should be a string")
+        return False
+    elif data_type == "list" and type(value) != list:
+        if errors:
+            errors.append(str(value) + " should be a list, not a " + str(value.__class__.__name__))
+        return False
+    elif data_type == "map" and type(value) != dict:
+        if errors:
+            errors.append(str(value) + " should be a map")
+        return False
+    else:
+        return True
+
+def _validate_item(spec, data, errors):
+    if not _validate_type(spec, data, errors):
+        return False
+    elif type(data) == list:
+        list_spec = spec['list_item_spec']
+        for data_el in data:
+            if not _validate_type(list_spec, data_el, errors):
+                return False
+            if list_spec['item_type'] == "map":
+                if not _validate_item(list_spec, data_el, errors):
+                    return False
+    elif type(data) == dict:
+        if not _validate_spec_list(spec['map_item_spec'], data, errors):
+            return False
+    return True
+
+def _validate_spec(spec, data, errors):
+    item_name = spec['item_name']
+    item_optional = spec['item_optional']
+
+    if item_name in data:
+        return _validate_item(spec, data[item_name], errors)
+    elif not item_optional:
+        if errors:
+            errors.append("non-optional item " + item_name + " missing")
+        return False
+    else:
+        return True
+
+def _validate_spec_list(data_spec, data, errors):
+    for spec_item in data_spec:
+        if not _validate_spec(spec_item, data, errors):
+            return False
+    return True

+ 41 - 3
src/lib/config/python/isc/config/datadefinition_test.py

@@ -19,8 +19,8 @@
 
 import unittest
 import os
-from isc.config import DataDefinition
-
+from isc.config import DataDefinition, DataDefinitionError
+import isc.cc.data
 
 class TestDataDefinition(unittest.TestCase):
 
@@ -31,6 +31,9 @@ class TestDataDefinition(unittest.TestCase):
     def spec_file(self, filename):
         return(self.data_path + os.sep + filename)
 
+    def read_spec_file(self, filename):
+        return DataDefinition(self.spec_file(filename))
+
     def spec1(self, dd):
         data_def = dd.get_definition()
         self.assert_('data_specification' in data_def)
@@ -48,7 +51,42 @@ class TestDataDefinition(unittest.TestCase):
         self.spec1(dd)
 
     def test_bad_specfiles(self):
-        self.assertRaises(DataDefinition(self.spec_file("spec3.spec")))
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec3.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec4.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec5.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec6.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec7.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec8.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec9.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec10.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec11.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec12.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec13.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec14.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec15.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec16.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec17.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec18.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec19.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec20.spec")
+        self.assertRaises(DataDefinitionError, self.read_spec_file, "spec21.spec")
+
+    def validate_data(self, specfile_name, datafile_name):
+        dd = DataDefinition(self.spec_file(specfile_name));
+        data_file = open(self.spec_file(datafile_name))
+        data_str = data_file.read()
+        data = isc.cc.data.parse_value_str(data_str)
+        return dd.validate(data)
+        
+    def test_data_validation(self):
+        self.assertEqual(True, self.validate_data("spec22.spec", "data22_1.data"))
+        self.assertEqual(False, self.validate_data("spec22.spec", "data22_2.data"))
+        self.assertEqual(False, self.validate_data("spec22.spec", "data22_3.data"))
+        self.assertEqual(False, self.validate_data("spec22.spec", "data22_4.data"))
+        self.assertEqual(False, self.validate_data("spec22.spec", "data22_5.data"))
+        self.assertEqual(True, self.validate_data("spec22.spec", "data22_6.data"))
+        self.assertEqual(True, self.validate_data("spec22.spec", "data22_7.data"))
+        self.assertEqual(False, self.validate_data("spec22.spec", "data22_8.data"))
 
 if __name__ == '__main__':
     unittest.main()