Considerably expand test coverage
This commit is contained in:
parent
0246be6a14
commit
b49582a5dc
2 changed files with 134 additions and 17 deletions
|
@ -8,12 +8,110 @@ import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('schema, obj', [
|
@pytest.mark.parametrize('schema, obj', [
|
||||||
({"type": "number"}, "---\n1.0"),
|
({"type": "number"},
|
||||||
({"type": "integer"}, "---\n3"),
|
"---\n1.0"),
|
||||||
({"type": "string"}, "---\nfoo bar baz"),
|
({"type": "integer"},
|
||||||
({"type": "string", "maxLength": 15}, "---\nfoo bar baz"),
|
"---\n3"),
|
||||||
({"type": "string", "minLength": 10}, "---\nfoo bar baz"),
|
({"type": "string"},
|
||||||
({"type": "string", "pattern": "^foo.*"}, "---\nfoo bar baz"),
|
"---\nfoo bar baz"),
|
||||||
|
({"type": "string",
|
||||||
|
"maxLength": 15},
|
||||||
|
"---\nfoo bar baz"),
|
||||||
|
({"type": "string",
|
||||||
|
"minLength": 10},
|
||||||
|
"---\nfoo bar baz"),
|
||||||
|
({"type": "string",
|
||||||
|
"pattern": "^foo.*"},
|
||||||
|
"---\nfoo bar baz"),
|
||||||
|
({"type": "object",
|
||||||
|
"additionalProperties": True},
|
||||||
|
"---\nfoo: bar\nbaz: qux"),
|
||||||
|
({"type": "object",
|
||||||
|
"properties": {"foo": {"type": "string"}}},
|
||||||
|
"---\nfoo: bar\nbaz: qux"),
|
||||||
|
({"type": "object",
|
||||||
|
"properties": {"foo": {"type": "string"}},
|
||||||
|
"additionalProperties": False},
|
||||||
|
"---\nfoo: bar"),
|
||||||
|
({"type": "object",
|
||||||
|
"properties": {"foo": {"type": "object"}}},
|
||||||
|
"---\nfoo: {}"),
|
||||||
|
({"type": "object",
|
||||||
|
"properties": {"foo": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {"type": "object"}}}},
|
||||||
|
"---\nfoo: [{}, {}, {foo: bar}]"),
|
||||||
])
|
])
|
||||||
def test_lint_document_ok(schema, obj):
|
def test_lint_document_ok(schema, obj):
|
||||||
assert not list(lint_buffer(schema, obj))
|
assert not list(lint_buffer(schema, obj))
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('msg, schema, obj', [
|
||||||
|
# Numerics
|
||||||
|
("Floats are not ints",
|
||||||
|
{"type": "integer"},
|
||||||
|
"---\n1.0"),
|
||||||
|
("Ints are not floats",
|
||||||
|
{"type": "number"},
|
||||||
|
"---\n1"),
|
||||||
|
|
||||||
|
# Numerics - range limits. Integer edition
|
||||||
|
("1 is the limit of the range",
|
||||||
|
{"type": "integer",
|
||||||
|
"exclusiveMaximum": 1},
|
||||||
|
"---\n1"),
|
||||||
|
("1 is the limit of the range",
|
||||||
|
{"type": "integer",
|
||||||
|
"exclusiveMinimum": 1},
|
||||||
|
"---\n1"),
|
||||||
|
("1 is out of the range",
|
||||||
|
{"type": "integer",
|
||||||
|
"minimum": 2},
|
||||||
|
"---\n1"),
|
||||||
|
("1 is out of the range",
|
||||||
|
{"type": "integer",
|
||||||
|
"maximum": 0},
|
||||||
|
"---\n1"),
|
||||||
|
("1 is out of the range",
|
||||||
|
{"type": "integer",
|
||||||
|
"exclusiveMinimum": 1},
|
||||||
|
"---\n1"),
|
||||||
|
|
||||||
|
# Numerics - range limits. Number/Float edition
|
||||||
|
("1 is the limit of the range",
|
||||||
|
{"type": "number",
|
||||||
|
"exclusiveMaximum": 1},
|
||||||
|
"---\n1.0"),
|
||||||
|
("1 is the limit of the range",
|
||||||
|
{"type": "number",
|
||||||
|
"exclusiveMinimum": 1},
|
||||||
|
"---\n1.0"),
|
||||||
|
("1 is out of the range",
|
||||||
|
{"type": "number",
|
||||||
|
"minimum": 2},
|
||||||
|
"---\n1.0"),
|
||||||
|
("1 is out of the range",
|
||||||
|
{"type": "number",
|
||||||
|
"maximum": 0},
|
||||||
|
"---\n1.0"),
|
||||||
|
("1 is out of the range",
|
||||||
|
{"type": "number",
|
||||||
|
"exclusiveMinimum": 1},
|
||||||
|
"---\n1.0"),
|
||||||
|
|
||||||
|
# String shit
|
||||||
|
("String too short",
|
||||||
|
{"type": "string", "minLength": 1},
|
||||||
|
"---\n''"),
|
||||||
|
("String too long",
|
||||||
|
{"type": "string", "maxLength": 1},
|
||||||
|
"---\nfoo"),
|
||||||
|
("String does not match pattern",
|
||||||
|
{"type": "string", "pattern": "bar"},
|
||||||
|
"---\nfoo"),
|
||||||
|
("String does not fully match pattern",
|
||||||
|
{"type": "string", "pattern": "foo"},
|
||||||
|
"---\nfooooooooo"),
|
||||||
|
])
|
||||||
|
def test_lint_document_fails(msg, schema, obj):
|
||||||
|
assert list(lint_buffer(schema, obj)), msg
|
||||||
|
|
|
@ -45,7 +45,10 @@ class YamlLinter(object):
|
||||||
def dereference(self, schema):
|
def dereference(self, schema):
|
||||||
"""Dereference a {"$ref": ""} form."""
|
"""Dereference a {"$ref": ""} form."""
|
||||||
|
|
||||||
if ref := schema.get("$ref"):
|
if schema in [True, False]:
|
||||||
|
return schema
|
||||||
|
|
||||||
|
elif ref := schema.get("$ref"):
|
||||||
assert ref.startswith("#/")
|
assert ref.startswith("#/")
|
||||||
path = ref.lstrip("#/").split("/")
|
path = ref.lstrip("#/").split("/")
|
||||||
schema = self._schema
|
schema = self._schema
|
||||||
|
@ -85,10 +88,10 @@ class YamlLinter(object):
|
||||||
|
|
||||||
for k, v in node.value:
|
for k, v in node.value:
|
||||||
if k.value in properties:
|
if k.value in properties:
|
||||||
yield from self.lint_document(properties.get(k.value), v)
|
yield from self.lint_document(v, properties.get(k.value))
|
||||||
|
|
||||||
elif additional_type:
|
elif additional_type:
|
||||||
yield from self.lint_document(additional_type, v)
|
yield from self.lint_document(v, additional_type)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
yield LintRecord(
|
yield LintRecord(
|
||||||
|
@ -116,7 +119,7 @@ class YamlLinter(object):
|
||||||
subschema = schema.get("items")
|
subschema = schema.get("items")
|
||||||
if subschema:
|
if subschema:
|
||||||
for item in node.value:
|
for item in node.value:
|
||||||
yield from self.lint_document(subschema, item)
|
yield from self.lint_document(item, subschema)
|
||||||
|
|
||||||
def lint_scalar(self, schema, node: Node) -> t.Iterable[str]:
|
def lint_scalar(self, schema, node: Node) -> t.Iterable[str]:
|
||||||
"""FIXME.
|
"""FIXME.
|
||||||
|
@ -204,7 +207,7 @@ class YamlLinter(object):
|
||||||
def _lint_num_range(self, schema, node: Node, value) -> t.Iterable[str]:
|
def _lint_num_range(self, schema, node: Node, value) -> t.Iterable[str]:
|
||||||
""""FIXME."""
|
""""FIXME."""
|
||||||
|
|
||||||
if base := schema.get("multipleOf"):
|
if (base := schema.get("multipleOf")) is not None:
|
||||||
if value % base != 0:
|
if value % base != 0:
|
||||||
yield LintRecord(
|
yield LintRecord(
|
||||||
LintLevel.MISSMATCH,
|
LintLevel.MISSMATCH,
|
||||||
|
@ -213,7 +216,7 @@ class YamlLinter(object):
|
||||||
f"Expected a multiple of {base}, got {value}"
|
f"Expected a multiple of {base}, got {value}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if max := schema.get("exclusiveMaximum"):
|
if (max := schema.get("exclusiveMaximum")) is not None:
|
||||||
if value >= max:
|
if value >= max:
|
||||||
yield LintRecord(
|
yield LintRecord(
|
||||||
LintLevel.MISSMATCH,
|
LintLevel.MISSMATCH,
|
||||||
|
@ -222,7 +225,7 @@ class YamlLinter(object):
|
||||||
f"Expected a value less than {max}, got {value}"
|
f"Expected a value less than {max}, got {value}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if max := schema.get("maximum"):
|
if (max := schema.get("maximum")) is not None:
|
||||||
if value > max:
|
if value > max:
|
||||||
yield LintRecord(
|
yield LintRecord(
|
||||||
LintLevel.MISSMATCH,
|
LintLevel.MISSMATCH,
|
||||||
|
@ -231,7 +234,7 @@ class YamlLinter(object):
|
||||||
f"Expected a value less than or equal to {max}, got {value}"
|
f"Expected a value less than or equal to {max}, got {value}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if min := schema.get("exclusiveMinimum"):
|
if (min := schema.get("exclusiveMinimum")) is not None:
|
||||||
if value <= min:
|
if value <= min:
|
||||||
yield LintRecord(
|
yield LintRecord(
|
||||||
LintLevel.MISSMATCH,
|
LintLevel.MISSMATCH,
|
||||||
|
@ -240,7 +243,7 @@ class YamlLinter(object):
|
||||||
f"Expected a value greater than {min}, got {value}"
|
f"Expected a value greater than {min}, got {value}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if min := schema.get("minimum"):
|
if (min := schema.get("minimum")) is not None:
|
||||||
if value < min:
|
if value < min:
|
||||||
yield LintRecord(
|
yield LintRecord(
|
||||||
LintLevel.MISSMATCH,
|
LintLevel.MISSMATCH,
|
||||||
|
@ -260,20 +263,36 @@ class YamlLinter(object):
|
||||||
schema = schema or self._schema # Fixing up the schema source
|
schema = schema or self._schema # Fixing up the schema source
|
||||||
schema = self.dereference(schema) # And dereference it if needed
|
schema = self.dereference(schema) # And dereference it if needed
|
||||||
|
|
||||||
|
# Special schemas
|
||||||
|
# These are schemas that accept everything.
|
||||||
if schema == True or schema == {}:
|
if schema == True or schema == {}:
|
||||||
yield from []
|
yield from []
|
||||||
|
|
||||||
|
# This is the schema that rejects everything.
|
||||||
|
elif schema == False:
|
||||||
|
yield LintRecord(
|
||||||
|
LintLevel.UNEXPECTED,
|
||||||
|
node,
|
||||||
|
schema,
|
||||||
|
"Received an unexpected value"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Walking the PyYAML node hierarchy
|
||||||
elif isinstance(node, MappingNode):
|
elif isinstance(node, MappingNode):
|
||||||
yield from self.lint_mapping(schema, node)
|
yield from self.lint_mapping(schema, node)
|
||||||
|
|
||||||
elif isinstance(node, SequenceNode):
|
elif isinstance(node, SequenceNode):
|
||||||
yield from self.lint_sequence(schema, node)
|
yield from self.lint_sequence(schema, node)
|
||||||
|
|
||||||
elif isinstance(node, ScalarNode):
|
elif isinstance(node, ScalarNode):
|
||||||
yield from self.lint_scalar(schema, node)
|
yield from self.lint_scalar(schema, node)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
yield from []
|
raise RuntimeError(f"Unsupported PyYAML node {type(node)}")
|
||||||
|
|
||||||
|
|
||||||
def lint_node(schema, node, cls=YamlLinter):
|
def lint_node(schema, node, cls=YamlLinter):
|
||||||
"""Lint a document using a schema and linter."""
|
"""Lint a composed PyYAML AST node using a schema and linter."""
|
||||||
|
|
||||||
print(repr(node))
|
print(repr(node))
|
||||||
linter = cls(schema)
|
linter = cls(schema)
|
||||||
|
|
Loading…
Reference in a new issue