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