Files
codeql/misc/codegen/test/test_schemaloader.py
2025-03-19 15:14:14 +01:00

1029 lines
24 KiB
Python

import sys
import pytest
from misc.codegen.lib.schemadefs import optional
from misc.codegen.test.utils import *
from misc.codegen.lib import schemadefs as defs
from misc.codegen.loaders.schemaloader import load
def test_empty_schema():
@load
class data:
pass
assert data.classes == {}
assert data.includes == set()
assert data.null is None
assert data.null_class is None
def test_one_empty_class():
@load
class data:
class MyClass:
pass
assert data.classes == {
'MyClass': schema.Class('MyClass'),
}
assert data.root_class is data.classes['MyClass']
def test_two_empty_classes():
@load
class data:
class MyClass1:
pass
class MyClass2(MyClass1):
pass
assert data.classes == {
'MyClass1': schema.Class('MyClass1', derived={'MyClass2'}),
'MyClass2': schema.Class('MyClass2', bases=['MyClass1']),
}
assert data.root_class is data.classes['MyClass1']
def test_no_external_bases():
class A:
pass
with pytest.raises(schema.Error):
@load
class data:
class MyClass(A):
pass
def test_no_multiple_roots():
with pytest.raises(schema.Error):
@load
class data:
class MyClass1:
pass
class MyClass2:
pass
def test_empty_classes_diamond():
@load
class data:
class A:
pass
class B(A):
pass
class C(A):
pass
class D(B, C):
pass
assert data.classes == {
'A': schema.Class('A', derived={'B', 'C'}),
'B': schema.Class('B', bases=['A'], derived={'D'}),
'C': schema.Class('C', bases=['A'], derived={'D'}),
'D': schema.Class('D', bases=['B', 'C']),
}
#
def test_group():
@load
class data:
@defs.group("xxx")
class A:
pass
assert data.classes == {
'A': schema.Class('A', pragmas={"group": "xxx"}),
}
def test_group_is_inherited():
@load
class data:
class A:
pass
class B(A):
pass
@defs.group('xxx')
class C(A):
pass
class D(B, C):
pass
assert data.classes == {
'A': schema.Class('A', derived={'B', 'C'}),
'B': schema.Class('B', bases=['A'], derived={'D'}),
'C': schema.Class('C', bases=['A'], derived={'D'}, pragmas={"group": "xxx"}),
'D': schema.Class('D', bases=['B', 'C'], pragmas={"group": "xxx"}),
}
def test_no_mixed_groups_in_bases():
with pytest.raises(schema.Error):
@load
class data:
class A:
pass
@defs.group('x')
class B(A):
pass
@defs.group('y')
class C(A):
pass
class D(B, C):
pass
#
def test_lowercase_rejected():
with pytest.raises(schema.Error):
@load
class data:
class aLowerCase:
pass
def test_properties():
@load
class data:
class A:
one: defs.string
two: defs.optional[defs.int]
three: defs.list[defs.boolean]
four: defs.list[defs.optional[defs.string]]
five: defs.predicate
six: defs.set[defs.string]
assert data.classes == {
'A': schema.Class('A', properties=[
schema.SingleProperty('one', 'string'),
schema.OptionalProperty('two', 'int'),
schema.RepeatedProperty('three', 'boolean'),
schema.RepeatedOptionalProperty('four', 'string'),
schema.PredicateProperty('five'),
schema.RepeatedUnorderedProperty('six', 'string'),
]),
}
def test_class_properties():
class A:
pass
@load
class data:
class A:
pass
class B(A):
one: A
two: defs.optional[A]
three: defs.list[A]
four: defs.list[defs.optional[A]]
five: defs.set[A]
assert data.classes == {
'A': schema.Class('A', derived={'B'}),
'B': schema.Class('B', bases=['A'], properties=[
schema.SingleProperty('one', 'A'),
schema.OptionalProperty('two', 'A'),
schema.RepeatedProperty('three', 'A'),
schema.RepeatedOptionalProperty('four', 'A'),
schema.RepeatedUnorderedProperty('five', 'A'),
]),
}
def test_string_reference_class_properties():
@load
class data:
class A:
one: "A"
two: defs.optional["A"]
three: defs.list["A"]
four: defs.list[defs.optional["A"]]
five: defs.set["A"]
assert data.classes == {
'A': schema.Class('A', properties=[
schema.SingleProperty('one', 'A'),
schema.OptionalProperty('two', 'A'),
schema.RepeatedProperty('three', 'A'),
schema.RepeatedOptionalProperty('four', 'A'),
schema.RepeatedUnorderedProperty('five', 'A'),
]),
}
@pytest.mark.parametrize("spec", [lambda t: t, lambda t: defs.optional[t], lambda t: defs.list[t],
lambda t: defs.list[defs.optional[t]]])
def test_string_reference_dangling(spec):
with pytest.raises(schema.Error):
@load
class data:
class A:
x: spec("B")
def test_children():
@load
class data:
class A:
one: "A" | defs.child
two: defs.optional["A"] | defs.child
three: defs.list["A"] | defs.child
four: defs.list[defs.optional["A"]] | defs.child
assert data.classes == {
'A': schema.Class('A', properties=[
schema.SingleProperty('one', 'A', is_child=True),
schema.OptionalProperty('two', 'A', is_child=True),
schema.RepeatedProperty('three', 'A', is_child=True),
schema.RepeatedOptionalProperty('four', 'A', is_child=True),
]),
}
@pytest.mark.parametrize("spec", [defs.string, defs.int, defs.boolean, defs.predicate, defs.set["A"]])
def test_builtin_predicate_and_set_children_not_allowed(spec):
with pytest.raises(schema.Error):
@load
class data:
class A:
x: spec | defs.child
_class_pragmas = [
(defs.qltest.collapse_hierarchy, "qltest_collapse_hierarchy"),
(defs.qltest.uncollapse_hierarchy, "qltest_uncollapse_hierarchy"),
(defs.qltest.skip, "qltest_skip"),
]
_property_pragmas = [
(defs.cpp.skip, "cpp_skip"),
(defs.ql.internal, "ql_internal"),
]
_pragmas = _class_pragmas + _property_pragmas
@pytest.mark.parametrize("pragma,expected", _property_pragmas)
def test_property_with_pragma(pragma, expected):
@load
class data:
class A:
x: defs.string | pragma
assert data.classes == {
'A': schema.Class('A', properties=[
schema.SingleProperty('x', 'string', pragmas=[expected]),
]),
}
def test_property_with_pragmas():
spec = defs.string
for pragma, _ in _property_pragmas:
spec |= pragma
@load
class data:
class A:
x: spec
assert data.classes == {
'A': schema.Class('A', properties=[
schema.SingleProperty('x', 'string', pragmas=[expected for _, expected in _property_pragmas]),
]),
}
@pytest.mark.parametrize("pragma,expected", _pragmas)
def test_class_with_pragma(pragma, expected):
@load
class data:
@pragma
class A:
pass
assert data.classes == {
'A': schema.Class('A', pragmas=[expected]),
}
def test_class_with_pragmas():
def apply_pragmas(cls):
for p, _ in _pragmas:
p(cls)
@load
class data:
class A:
pass
apply_pragmas(A)
assert data.classes == {
'A': schema.Class('A', pragmas=[e for _, e in _pragmas]),
}
def test_synth_from_class():
@load
class data:
class A:
pass
@defs.synth.from_class(A)
class B(A):
pass
assert data.classes == {
'A': schema.Class('A', derived={'B'}, pragmas={"synth": True}),
'B': schema.Class('B', bases=['A'], pragmas={"synth": schema.SynthInfo(from_class="A")}),
}
def test_synth_from_class_ref():
@load
class data:
@defs.synth.from_class("B")
class A:
pass
class B(A):
pass
assert data.classes == {
'A': schema.Class('A', derived={'B'}, pragmas={"synth": schema.SynthInfo(from_class="B")}),
'B': schema.Class('B', bases=['A']),
}
def test_synth_from_class_dangling():
with pytest.raises(schema.Error):
@load
class data:
@defs.synth.from_class("X")
class A:
pass
def test_synth_class_on():
@load
class data:
class A:
pass
@defs.synth.on_arguments(a=A, i=defs.int)
class B(A):
pass
assert data.classes == {
'A': schema.Class('A', derived={'B'}, pragmas={"synth": True}),
'B': schema.Class('B', bases=['A'], pragmas={"synth": schema.SynthInfo(on_arguments={'a': 'A', 'i': 'int'})}),
}
def test_synth_class_on_ref():
class A:
pass
@load
class data:
@defs.synth.on_arguments(b="B", i=defs.int)
class A:
pass
class B(A):
pass
assert data.classes == {
'A': schema.Class('A', derived={'B'}, pragmas={"synth": schema.SynthInfo(on_arguments={'b': 'B', 'i': 'int'})}),
'B': schema.Class('B', bases=['A']),
}
def test_synth_class_on_dangling():
with pytest.raises(schema.Error):
@load
class data:
@defs.synth.on_arguments(s=defs.string, a="A", i=defs.int)
class B:
pass
def test_synth_class_hierarchy():
@load
class data:
class Root:
pass
class Base(Root):
pass
class Intermediate(Base):
pass
@defs.synth.on_arguments(a=Base, i=defs.int)
class A(Intermediate):
pass
@defs.synth.from_class(Base)
class B(Base):
pass
class C(Root):
pass
assert data.classes == {
'Root': schema.Class('Root', derived={'Base', 'C'}),
'Base': schema.Class('Base', bases=['Root'], derived={'Intermediate', 'B'}, pragmas={"synth": True}),
'Intermediate': schema.Class('Intermediate', bases=['Base'], derived={'A'}, pragmas={"synth": True}),
'A': schema.Class('A', bases=['Intermediate'], pragmas={"synth": schema.SynthInfo(on_arguments={'a': 'Base', 'i': 'int'})}),
'B': schema.Class('B', bases=['Base'], pragmas={"synth": schema.SynthInfo(from_class='Base')}),
'C': schema.Class('C', bases=['Root']),
}
def test_synthesized_property():
@load
class data:
class A:
x: defs.int | defs.synth
assert data.classes["A"].properties == [
schema.SingleProperty("x", "int", synth=True)
]
def test_class_docstring():
@load
class data:
class A:
"""Very important class."""
assert data.classes == {
'A': schema.Class('A', doc=["Very important class."])
}
def test_property_docstring():
@load
class data:
class A:
x: int | defs.desc("very important property.")
assert data.classes == {
'A': schema.Class('A', properties=[schema.SingleProperty('x', 'int', description=["very important property."])])
}
def test_class_docstring_newline():
@load
class data:
class A:
"""Very important
class."""
assert data.classes == {
'A': schema.Class('A', doc=["Very important", "class."])
}
def test_property_docstring_newline():
@load
class data:
class A:
x: int | defs.desc("""very important
property.""")
assert data.classes == {
'A': schema.Class('A',
properties=[schema.SingleProperty('x', 'int', description=["very important", "property."])])
}
def test_class_docstring_stripped():
@load
class data:
class A:
"""
Very important class.
"""
assert data.classes == {
'A': schema.Class('A', doc=["Very important class."])
}
def test_property_docstring_stripped():
@load
class data:
class A:
x: int | defs.desc("""
very important property.
""")
assert data.classes == {
'A': schema.Class('A', properties=[schema.SingleProperty('x', 'int', description=["very important property."])])
}
def test_class_docstring_split():
@load
class data:
class A:
"""Very important class.
As said, very important."""
assert data.classes == {
'A': schema.Class('A', doc=["Very important class.", "", "As said, very important."])
}
def test_property_docstring_split():
@load
class data:
class A:
x: int | defs.desc("""very important property.
Very very important.""")
assert data.classes == {
'A': schema.Class('A', properties=[
schema.SingleProperty('x', 'int', description=["very important property.", "", "Very very important."])])
}
def test_class_docstring_indent():
@load
class data:
class A:
"""
Very important class.
As said, very important.
"""
assert data.classes == {
'A': schema.Class('A', doc=["Very important class.", " As said, very important."])
}
def test_property_docstring_indent():
@load
class data:
class A:
x: int | defs.desc("""
very important property.
Very very important.
""")
assert data.classes == {
'A': schema.Class('A', properties=[
schema.SingleProperty('x', 'int', description=["very important property.", " Very very important."])])
}
def test_property_doc_override():
@load
class data:
class A:
x: int | defs.doc("y")
assert data.classes == {
'A': schema.Class('A', properties=[
schema.SingleProperty('x', 'int', doc="y")]),
}
def test_property_doc_override_no_newlines():
with pytest.raises(schema.Error):
@load
class data:
class A:
x: int | defs.doc("no multiple\nlines")
def test_property_doc_override_no_trailing_dot():
with pytest.raises(schema.Error):
@load
class data:
class A:
x: int | defs.doc("no dots please.")
def test_class_default_doc_name():
@load
class data:
@defs.ql.default_doc_name("b")
class A:
pass
assert data.classes == {
'A': schema.Class('A', pragmas={"ql_default_doc_name": "b"}),
}
def test_db_table_name():
@load
class data:
class A:
x: optional[int] | defs.ql.db_table_name("foo")
assert data.classes == {
'A': schema.Class('A', properties=[schema.OptionalProperty("x", "int", pragmas={"ql_db_table_name": "foo"})]),
}
def test_null_class():
@load
class data:
class Root:
pass
@defs.use_for_null
class Null(Root):
pass
assert data.classes == {
'Root': schema.Class('Root', derived={'Null'}),
'Null': schema.Class('Null', bases=['Root']),
}
assert data.null == 'Null'
assert data.null_class is data.classes[data.null]
def test_null_class_cannot_be_derived():
with pytest.raises(schema.Error):
@load
class data:
class Root:
pass
@defs.use_for_null
class Null(Root):
pass
class Impossible(Null):
pass
def test_null_class_cannot_be_defined_multiple_times():
with pytest.raises(schema.Error):
@load
class data:
class Root:
pass
@defs.use_for_null
class Null1(Root):
pass
@defs.use_for_null
class Null2(Root):
pass
def test_uppercase_acronyms_are_rejected():
with pytest.raises(schema.Error):
@load
class data:
class Root:
pass
class ROTFLNode(Root):
pass
def test_hideable():
@load
class data:
class Root:
pass
@defs.ql.hideable
class A(Root):
pass
class IndirectlyHideable(Root):
pass
class B(A, IndirectlyHideable):
pass
class NonHideable(Root):
pass
assert data.classes == {
"Root": schema.Class("Root", derived={"A", "IndirectlyHideable", "NonHideable"}, pragmas=["ql_hideable"]),
"A": schema.Class("A", bases=["Root"], derived={"B"}, pragmas=["ql_hideable"]),
"IndirectlyHideable": schema.Class("IndirectlyHideable", bases=["Root"], derived={"B"}, pragmas=["ql_hideable"]),
"B": schema.Class("B", bases=["A", "IndirectlyHideable"], pragmas=["ql_hideable"]),
"NonHideable": schema.Class("NonHideable", bases=["Root"]),
}
def test_test_with():
@load
class data:
class Root:
pass
class A(Root):
pass
@defs.qltest.test_with(A)
class B(Root):
pass
@defs.qltest.test_with("D")
class C(Root):
pass
class D(Root):
pass
class E(B):
pass
assert data.classes == {
"Root": schema.Class("Root", derived=set("ABCD")),
"A": schema.Class("A", bases=["Root"]),
"B": schema.Class("B", bases=["Root"], pragmas={"qltest_test_with": "A"}, derived={'E'}),
"C": schema.Class("C", bases=["Root"], pragmas={"qltest_test_with": "D"}),
"D": schema.Class("D", bases=["Root"]),
"E": schema.Class("E", bases=["B"], pragmas={"qltest_test_with": "A"}),
}
def test_annotate_docstring():
@load
class data:
class Root:
""" old docstring """
class A(Root):
""" A docstring """
@defs.annotate(Root)
class _:
"""
new
docstring
"""
@defs.annotate(A)
class _:
pass
assert data.classes == {
"Root": schema.Class("Root", doc=["new", "docstring"], derived={"A"}),
"A": schema.Class("A", bases=["Root"], doc=["A docstring"]),
}
def test_annotate_decorations():
@load
class data:
@defs.qltest.skip
class Root:
pass
@defs.annotate(Root)
@defs.qltest.collapse_hierarchy
@defs.ql.hideable
@defs.cpp.skip
class _:
pass
assert data.classes == {
"Root": schema.Class("Root", pragmas=["qltest_skip", "cpp_skip", "ql_hideable", "qltest_collapse_hierarchy"]),
}
def test_annotate_fields():
@load
class data:
class Root:
x: defs.int
y: defs.optional["Root"] | defs.child
@defs.annotate(Root)
class _:
x: defs._ | defs.doc("foo")
y: defs._ | defs.ql.internal
z: defs.string
assert data.classes == {
"Root": schema.Class("Root", properties=[
schema.SingleProperty("x", "int", doc="foo"),
schema.OptionalProperty("y", "Root", pragmas=["ql_internal"], is_child=True),
schema.SingleProperty("z", "string"),
]),
}
def test_annotate_fields_negations():
@load
class data:
class Root:
x: defs.int | defs.ql.internal
y: defs.optional["Root"] | defs.child | defs.desc("foo\nbar\n")
z: defs.string | defs.synth | defs.doc("foo")
@defs.annotate(Root)
class _:
x: defs._ | ~defs.ql.internal
y: defs._ | ~defs.child | ~defs.ql.internal | ~defs.desc
z: defs._ | ~defs.synth | ~defs.doc
assert data.classes == {
"Root": schema.Class("Root", properties=[
schema.SingleProperty("x", "int"),
schema.OptionalProperty("y", "Root"),
schema.SingleProperty("z", "string"),
]),
}
def test_annotate_non_existing_field():
with pytest.raises(schema.Error):
@load
class data:
class Root:
pass
@defs.annotate(Root)
class _:
x: defs._ | defs.doc("foo")
def test_annotate_not_underscore():
with pytest.raises(schema.Error):
@load
class data:
class Root:
pass
@defs.annotate(Root)
class Something:
"""
new
docstring
"""
def test_annotate_replace_bases():
@load
class data:
class Root:
pass
class A(Root):
pass
class B(Root):
pass
class C(B):
pass
class Derived(A, B):
pass
@defs.annotate(Derived, replace_bases={B: C})
class _:
pass
assert data.classes == {
"Root": schema.Class("Root", derived={"A", "B"}),
"A": schema.Class("A", bases=["Root"], derived={"Derived"}),
"B": schema.Class("B", bases=["Root"], derived={"C"}),
"C": schema.Class("C", bases=["B"], derived={"Derived"}),
"Derived": schema.Class("Derived", bases=["A", "C"]),
}
def test_annotate_add_bases():
@load
class data:
class Root:
pass
class A(Root):
pass
class B(Root):
pass
class C(Root):
pass
class Derived(A):
pass
@defs.annotate(Derived, add_bases=(B, C))
class _:
pass
assert data.classes == {
"Root": schema.Class("Root", derived={"A", "B", "C"}),
"A": schema.Class("A", bases=["Root"], derived={"Derived"}),
"B": schema.Class("B", bases=["Root"], derived={"Derived"}),
"C": schema.Class("C", bases=["Root"], derived={"Derived"}),
"Derived": schema.Class("Derived", bases=["A", "B", "C"]),
}
def test_annotate_drop_field():
@load
class data:
class Root:
x: defs.int
y: defs.string
z: defs.boolean
@defs.annotate(Root)
class _:
y: defs.drop
assert data.classes == {
"Root": schema.Class("Root", properties=[
schema.SingleProperty("x", "int"),
schema.SingleProperty("z", "boolean"),
]),
}
def test_test_with_unknown_string():
with pytest.raises(schema.Error):
@load
class data:
class Root:
pass
@defs.qltest.test_with("B")
class A(Root):
pass
def test_test_with_unknown_class():
with pytest.raises(schema.Error):
class B:
pass
@load
class data:
class Root:
pass
@defs.qltest.test_with(B)
class A(Root):
pass
def test_test_with_double():
with pytest.raises(schema.Error):
class B:
pass
@load
class data:
class Root:
pass
class A(Root):
pass
@defs.qltest.test_with("C")
class B(Root):
pass
@defs.qltest.test_with(A)
class C(Root):
pass
if __name__ == '__main__':
sys.exit(pytest.main([__file__] + sys.argv[1:]))