ad982b111a
* remove six and mock usage * temporary turn off rally-docker-check Change-Id: I8582ef236917b3a0239f047ed9763134899d2d56
181 lines
8.4 KiB
Python
181 lines
8.4 KiB
Python
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import copy
|
|
import json
|
|
|
|
from rally.common.plugin import plugin
|
|
from rally import plugins
|
|
|
|
from tests.unit import test
|
|
|
|
|
|
class ConfigSchemasTestCase(test.TestCase):
|
|
|
|
OBJECT_TYPE_KEYS = {"$schema", "type", "description", "required",
|
|
"properties", "patternProperties",
|
|
"minProperties", "maxProperties",
|
|
"additionalProperties", "oneOf", "anyOf"}
|
|
ARRAY_TYPE_KEYS = {"$schema", "type", "description", "items",
|
|
"uniqueItems", "minItems", "maxItems",
|
|
"additionalItems"}
|
|
NUMBER_TYPE_KEYS = {"$schema", "type", "description", "minimum", "maximum",
|
|
"exclusiveMinimum"}
|
|
STRING_TYPE_KEYS = {"$schema", "type", "description", "pattern"}
|
|
|
|
def fail(self, p, schema, msg):
|
|
super(ConfigSchemasTestCase, self).fail(
|
|
"Config schema of plugin '%s' (%s) is invalid. %s "
|
|
"Schema: \n%s" % (p.get_name(),
|
|
"%s.%s" % (p.__module__, p.__name__),
|
|
msg,
|
|
json.dumps(schema, indent=3)))
|
|
|
|
def _check_anyOf_or_oneOf(self, p, schema, definitions):
|
|
if "anyOf" in schema or "oneOf" in schema:
|
|
key = "anyOf" if "anyOf" in schema else "oneOf"
|
|
for case in schema[key]:
|
|
if "description" not in case and "$ref" not in case:
|
|
self.fail(p, schema, "Each case of '%s' should have "
|
|
"description." % key)
|
|
full_schema = copy.deepcopy(schema)
|
|
full_schema.pop(key)
|
|
for k, v in case.items():
|
|
full_schema[k] = v
|
|
self._check_item(p, full_schema, definitions)
|
|
|
|
def _check_object_type(self, p, schema, definitions):
|
|
unexpected_keys = set(schema.keys()) - self.OBJECT_TYPE_KEYS
|
|
if "definitions" in unexpected_keys:
|
|
# TODO(andreykurilin): do not use definitions since it is a hard
|
|
# task to parse and display them
|
|
unexpected_keys -= {"definitions"}
|
|
if unexpected_keys:
|
|
self.fail(p, schema, ("Found unexpected key(s) for object type: "
|
|
"%s." % ", ".join(unexpected_keys)))
|
|
if "additionalProperties" not in schema:
|
|
self.fail(p, schema,
|
|
"'additionalProperties' is required field for objects. "
|
|
"Specify `'additionalProperties': True` explicitly to "
|
|
"accept not validated properties.")
|
|
|
|
if "patternProperties" in schema:
|
|
if "properties" in schema:
|
|
self.fail(p, schema, "Usage both 'patternProperties' and "
|
|
"'properties' in one time is restricted.")
|
|
if not isinstance(schema["patternProperties"], dict):
|
|
self.fail(p, schema, "Field 'patternProperties' should be a "
|
|
"dict.")
|
|
for pattern, description in schema["patternProperties"].items():
|
|
self._check_item(p, description, definitions)
|
|
|
|
if "properties" in schema:
|
|
for property_name, description in schema["properties"].items():
|
|
self._check_item(p, description, definitions)
|
|
|
|
def _check_array_type(self, p, schema, definitions):
|
|
unexpected_keys = set(schema.keys()) - self.ARRAY_TYPE_KEYS
|
|
if "additionalProperties" in unexpected_keys:
|
|
self.fail(p, schema, "Array type doesn't support "
|
|
"'additionalProperties' field.")
|
|
|
|
if unexpected_keys:
|
|
self.fail(p, schema, ("Found unexpected key(s) for array type: "
|
|
"%s." % ", ".join(unexpected_keys)))
|
|
|
|
if "items" not in schema:
|
|
self.fail(p, schema, "Expected items of array type should be "
|
|
"described via 'items' field.")
|
|
|
|
if isinstance(schema["items"], dict):
|
|
self._check_item(p, schema["items"], definitions)
|
|
if "additionalItems" in schema:
|
|
self.fail(p, schema, "When items is a single schema, the "
|
|
"`additionalItems` keyword is "
|
|
"meaningless, and it should not be used.")
|
|
elif isinstance(schema["items"], list):
|
|
for item in schema["items"]:
|
|
self._check_item(p, item, definitions)
|
|
else:
|
|
self.fail(p, schema, ("Field 'items' of array type should be a "
|
|
"list or a dict, but not '%s'" %
|
|
type(schema["items"])))
|
|
|
|
def _check_string_type(self, p, schema):
|
|
unexpected_keys = set(schema.keys()) - self.STRING_TYPE_KEYS
|
|
if unexpected_keys:
|
|
self.fail(p, schema, ("Found unexpected key(s) for string type: "
|
|
"%s." % ", ".join(unexpected_keys)))
|
|
|
|
def _check_number_type(self, p, schema):
|
|
unexpected_keys = set(schema.keys()) - self.NUMBER_TYPE_KEYS
|
|
if unexpected_keys:
|
|
self.fail(p, schema, ("Found unexpected key(s) for integer/number "
|
|
"type: %s." % ", ".join(unexpected_keys)))
|
|
|
|
def _check_simpliest_types(self, p, schema, type_name):
|
|
unexpected_keys = set(schema.keys()) - {"type", "description"}
|
|
if unexpected_keys:
|
|
self.fail(p, schema, ("Found unexpected key(s) for %s type: "
|
|
"%s." % (type_name,
|
|
", ".join(unexpected_keys))))
|
|
|
|
def _check_item(self, p, schema, definitions):
|
|
if "type" in schema or "anyOf" in schema or "oneOf" in schema:
|
|
if "anyOf" in schema or "oneOf" in schema:
|
|
self._check_anyOf_or_oneOf(p, schema, definitions)
|
|
elif "type" in schema:
|
|
if schema["type"] == "object":
|
|
self._check_object_type(p, schema, definitions)
|
|
elif schema["type"] == "array":
|
|
self._check_array_type(p, schema, definitions)
|
|
elif schema["type"] == "string":
|
|
self._check_string_type(p, schema)
|
|
elif schema["type"] in ("number", "integer"):
|
|
self._check_number_type(p, schema)
|
|
elif schema["type"] in ("boolean", "null"):
|
|
self._check_simpliest_types(p, schema, schema["type"])
|
|
elif isinstance(schema["type"], list):
|
|
self._check_simpliest_types(p, schema, "mixed")
|
|
else:
|
|
self.fail(p, schema,
|
|
"Wrong type is used: %s" % schema["type"])
|
|
elif "enum" in schema:
|
|
pass
|
|
elif schema == {}:
|
|
# NOTE(andreykurilin): an empty dict means that the user can
|
|
# transmit whatever he wants in whatever he wants format. It is
|
|
# not the case which we want to support.
|
|
self.fail(p, schema, "Empty schema is not allowed.")
|
|
elif "$ref" in schema:
|
|
definition_name = schema["$ref"].replace("#/definitions/", "")
|
|
if definition_name not in definitions:
|
|
self.fail(p, schema,
|
|
"Definition '%s' is not found." % definition_name)
|
|
else:
|
|
self.fail(p, schema, "Wrong format.")
|
|
|
|
@plugins.ensure_plugins_are_loaded
|
|
def test_schema_is_valid(self):
|
|
for p in plugin.Plugin.get_all():
|
|
if not hasattr(p, "CONFIG_SCHEMA") or "tests.unit" in p.__module__:
|
|
continue
|
|
# allow only top level definitions
|
|
definitions = p.CONFIG_SCHEMA.get("definitions", {})
|
|
for definition in definitions.values():
|
|
self._check_item(p, definition, definitions)
|
|
|
|
# check schema itself
|
|
self._check_item(p, p.CONFIG_SCHEMA, definitions)
|