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:]))