Skip to content

Commit f2b5897

Browse files
committed
Swift: add possibility to collapse class hierarchy in tests
1 parent 780f5ab commit f2b5897

File tree

40 files changed

+267
-60
lines changed

40 files changed

+267
-60
lines changed

swift/codegen/generators/qlgen.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ class ModifiedStubMarkedAsGeneratedError(Error):
3030
def get_ql_property(cls: schema.Class, prop: schema.Property):
3131
common_args = dict(
3232
type=prop.type if not prop.is_predicate else "predicate",
33-
skip_qltest="skip_qltest" in prop.pragmas,
33+
qltest_skip="qltest_skip" in prop.pragmas,
3434
is_child=prop.is_child,
3535
is_optional=prop.is_optional,
3636
is_predicate=prop.is_predicate,
@@ -69,13 +69,14 @@ def get_ql_property(cls: schema.Class, prop: schema.Property):
6969

7070

7171
def get_ql_class(cls: schema.Class):
72+
pragmas = {k: True for k in cls.pragmas if k.startswith("ql")}
7273
return ql.Class(
7374
name=cls.name,
7475
bases=cls.bases,
7576
final=not cls.derived,
7677
properties=[get_ql_property(cls, p) for p in cls.properties],
7778
dir=cls.dir,
78-
skip_qltest="skip_qltest" in cls.pragmas,
79+
**pragmas,
7980
)
8081

8182

@@ -143,7 +144,7 @@ def _get_all_properties_to_be_tested(cls: ql.Class, lookup: typing.Dict[str, ql.
143144
# deduplicate using id
144145
already_seen = set()
145146
for c, p in _get_all_properties(cls, lookup):
146-
if not (c.skip_qltest or p.skip_qltest or id(p) in already_seen):
147+
if not (c.qltest_skip or p.qltest_skip or id(p) in already_seen):
147148
already_seen.add(id(p))
148149
yield ql.PropertyForTest(p.getter, p.type, p.is_single, p.is_predicate, p.is_repeated)
149150

@@ -156,6 +157,20 @@ def _partition(l, pred):
156157
return res
157158

158159

160+
def _is_in_qltest_collapsed_hierachy(cls: ql.Class, lookup: typing.Dict[str, ql.Class]):
161+
return cls.qltest_collapse_hierarchy or _is_under_qltest_collapsed_hierachy(cls, lookup)
162+
163+
164+
def _is_under_qltest_collapsed_hierachy(cls: ql.Class, lookup: typing.Dict[str, ql.Class]):
165+
return not cls.qltest_uncollapse_hierarchy and any(
166+
_is_in_qltest_collapsed_hierachy(lookup[b], lookup) for b in cls.bases)
167+
168+
169+
def _should_skip_qltest(cls: ql.Class, lookup: typing.Dict[str, ql.Class]):
170+
return cls.qltest_skip or not (cls.final or cls.qltest_collapse_hierarchy) or _is_under_qltest_collapsed_hierachy(
171+
cls, lookup)
172+
173+
159174
def generate(opts, renderer):
160175
input = opts.schema
161176
out = opts.ql_output
@@ -196,7 +211,7 @@ def generate(opts, renderer):
196211
classes), out / 'GetImmediateParent.qll')
197212

198213
for c in classes:
199-
if not c.final or c.skip_qltest:
214+
if _should_skip_qltest(c, lookup):
200215
continue
201216
test_dir = test_out / c.path
202217
test_dir.mkdir(parents=True, exist_ok=True)

swift/codegen/lib/ql.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ class Property:
3737
is_optional: bool = False
3838
is_predicate: bool = False
3939
is_child: bool = False
40-
skip_qltest: bool = False
40+
qltest_skip: bool = False
4141

4242
def __post_init__(self):
4343
if self.tableparams:
@@ -79,7 +79,9 @@ class Class:
7979
properties: List[Property] = field(default_factory=list)
8080
dir: pathlib.Path = pathlib.Path()
8181
imports: List[str] = field(default_factory=list)
82-
skip_qltest: bool = False
82+
qltest_skip: bool = False
83+
qltest_collapse_hierarchy: bool = False
84+
qltest_uncollapse_hierarchy: bool = False
8385

8486
def __post_init__(self):
8587
self.bases = sorted(self.bases)

swift/codegen/test/test_qlgen.py

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -480,13 +480,13 @@ def test_test_properties_skipped(opts, generate_tests):
480480
write(opts.ql_test_output / "Derived" / "test.swift")
481481
assert generate_tests([
482482
schema.Class("Base", derived={"Derived"}, properties=[
483-
schema.SingleProperty("x", "string", pragmas=["skip_qltest", "foo"]),
484-
schema.RepeatedProperty("y", "int", pragmas=["bar", "skip_qltest"]),
483+
schema.SingleProperty("x", "string", pragmas=["qltest_skip", "foo"]),
484+
schema.RepeatedProperty("y", "int", pragmas=["bar", "qltest_skip"]),
485485
]),
486486
schema.Class("Derived", bases={"Base"}, properties=[
487-
schema.PredicateProperty("a", pragmas=["skip_qltest"]),
487+
schema.PredicateProperty("a", pragmas=["qltest_skip"]),
488488
schema.OptionalProperty(
489-
"b", "int", pragmas=["bar", "skip_qltest", "baz"]),
489+
"b", "int", pragmas=["bar", "qltest_skip", "baz"]),
490490
]),
491491
]) == {
492492
"Derived/Derived.ql": ql.ClassTester(class_name="Derived"),
@@ -496,7 +496,7 @@ def test_test_properties_skipped(opts, generate_tests):
496496
def test_test_base_class_skipped(opts, generate_tests):
497497
write(opts.ql_test_output / "Derived" / "test.swift")
498498
assert generate_tests([
499-
schema.Class("Base", derived={"Derived"}, pragmas=["skip_qltest", "foo"], properties=[
499+
schema.Class("Base", derived={"Derived"}, pragmas=["qltest_skip", "foo"], properties=[
500500
schema.SingleProperty("x", "string"),
501501
schema.RepeatedProperty("y", "int"),
502502
]),
@@ -510,12 +510,54 @@ def test_test_final_class_skipped(opts, generate_tests):
510510
write(opts.ql_test_output / "Derived" / "test.swift")
511511
assert generate_tests([
512512
schema.Class("Base", derived={"Derived"}),
513-
schema.Class("Derived", bases={"Base"}, pragmas=["skip_qltest", "foo"], properties=[
513+
schema.Class("Derived", bases={"Base"}, pragmas=["qltest_skip", "foo"], properties=[
514514
schema.SingleProperty("x", "string"),
515515
schema.RepeatedProperty("y", "int"),
516516
]),
517517
]) == {}
518518

519519

520+
def test_test_class_hierarchy_collapse(opts, generate_tests):
521+
write(opts.ql_test_output / "Base" / "test.swift")
522+
assert generate_tests([
523+
schema.Class("Base", derived={"D1", "D2"}, pragmas=["foo", "qltest_collapse_hierarchy"]),
524+
schema.Class("D1", bases={"Base"}, properties=[schema.SingleProperty("x", "string")]),
525+
schema.Class("D2", bases={"Base"}, derived={"D3"}, properties=[schema.SingleProperty("y", "string")]),
526+
schema.Class("D3", bases={"D2"}, properties=[schema.SingleProperty("z", "string")]),
527+
]) == {
528+
"Base/Base.ql": ql.ClassTester(class_name="Base"),
529+
}
530+
531+
532+
def test_test_class_hierarchy_uncollapse(opts, generate_tests):
533+
for d in ("Base", "D3", "D4"):
534+
write(opts.ql_test_output / d / "test.swift")
535+
assert generate_tests([
536+
schema.Class("Base", derived={"D1", "D2"}, pragmas=["foo", "qltest_collapse_hierarchy"]),
537+
schema.Class("D1", bases={"Base"}, properties=[schema.SingleProperty("x", "string")]),
538+
schema.Class("D2", bases={"Base"}, derived={"D3", "D4"}, pragmas=["qltest_uncollapse_hierarchy", "bar"]),
539+
schema.Class("D3", bases={"D2"}),
540+
schema.Class("D4", bases={"D2"}),
541+
]) == {
542+
"Base/Base.ql": ql.ClassTester(class_name="Base"),
543+
"D3/D3.ql": ql.ClassTester(class_name="D3"),
544+
"D4/D4.ql": ql.ClassTester(class_name="D4"),
545+
}
546+
547+
548+
def test_test_class_hierarchy_uncollapse_at_final(opts, generate_tests):
549+
for d in ("Base", "D3"):
550+
write(opts.ql_test_output / d / "test.swift")
551+
assert generate_tests([
552+
schema.Class("Base", derived={"D1", "D2"}, pragmas=["foo", "qltest_collapse_hierarchy"]),
553+
schema.Class("D1", bases={"Base"}, properties=[schema.SingleProperty("x", "string")]),
554+
schema.Class("D2", bases={"Base"}, derived={"D3"}),
555+
schema.Class("D3", bases={"D2"}, pragmas=["qltest_uncollapse_hierarchy", "bar"]),
556+
]) == {
557+
"Base/Base.ql": ql.ClassTester(class_name="Base"),
558+
"D3/D3.ql": ql.ClassTester(class_name="D3"),
559+
}
560+
561+
520562
if __name__ == '__main__':
521563
sys.exit(pytest.main([__file__] + sys.argv[1:]))

swift/ql/test/extractor-tests/generated/File/File.ql

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,10 @@
22
import codeql.swift.elements
33
import TestUtils
44

5-
from File x, string getName
5+
from File x, string isUnknown, string getName
66
where
77
toBeTested(x) and
88
not x.isUnknown() and
9+
(if x.isUnknown() then isUnknown = "yes" else isUnknown = "no") and
910
getName = x.getName()
10-
select x, "getName:", getName
11+
select x, "isUnknown:", isUnknown, "getName:", getName
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// generated by codegen/codegen.py
2+
3+
After a swift source file is added in this directory and codegen/codegen.py is run again, test queries
4+
will appear and this file will be deleted

swift/ql/test/extractor-tests/generated/decl/AccessorDecl/AccessorDecl.ql

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,17 @@ import codeql.swift.elements
33
import TestUtils
44

55
from
6-
AccessorDecl x, Type getInterfaceType, string getName, string isGetter, string isSetter,
7-
string isWillSet, string isDidSet
6+
AccessorDecl x, string isUnknown, Type getInterfaceType, string getName, string isGetter,
7+
string isSetter, string isWillSet, string isDidSet
88
where
99
toBeTested(x) and
1010
not x.isUnknown() and
11+
(if x.isUnknown() then isUnknown = "yes" else isUnknown = "no") and
1112
getInterfaceType = x.getInterfaceType() and
1213
getName = x.getName() and
1314
(if x.isGetter() then isGetter = "yes" else isGetter = "no") and
1415
(if x.isSetter() then isSetter = "yes" else isSetter = "no") and
1516
(if x.isWillSet() then isWillSet = "yes" else isWillSet = "no") and
1617
if x.isDidSet() then isDidSet = "yes" else isDidSet = "no"
17-
select x, "getInterfaceType:", getInterfaceType, "getName:", getName, "isGetter:", isGetter,
18-
"isSetter:", isSetter, "isWillSet:", isWillSet, "isDidSet:", isDidSet
18+
select x, "isUnknown:", isUnknown, "getInterfaceType:", getInterfaceType, "getName:", getName,
19+
"isGetter:", isGetter, "isSetter:", isSetter, "isWillSet:", isWillSet, "isDidSet:", isDidSet
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// generated by codegen/codegen.py
2+
import codeql.swift.elements
3+
import TestUtils
4+
5+
from AccessorDecl x
6+
where toBeTested(x) and not x.isUnknown()
7+
select x, x.getLocation()

swift/ql/test/extractor-tests/generated/decl/AssociatedTypeDecl/AssociatedTypeDecl.ql

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import codeql.swift.elements
33
import TestUtils
44

5-
from AssociatedTypeDecl x, Type getInterfaceType, string getName
5+
from AssociatedTypeDecl x, string isUnknown, Type getInterfaceType, string getName
66
where
77
toBeTested(x) and
88
not x.isUnknown() and
9+
(if x.isUnknown() then isUnknown = "yes" else isUnknown = "no") and
910
getInterfaceType = x.getInterfaceType() and
1011
getName = x.getName()
11-
select x, "getInterfaceType:", getInterfaceType, "getName:", getName
12+
select x, "isUnknown:", isUnknown, "getInterfaceType:", getInterfaceType, "getName:", getName
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// generated by codegen/codegen.py
2+
import codeql.swift.elements
3+
import TestUtils
4+
5+
from AssociatedTypeDecl x
6+
where toBeTested(x) and not x.isUnknown()
7+
select x, x.getLocation()

swift/ql/test/extractor-tests/generated/decl/ClassDecl/ClassDecl.ql

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
import codeql.swift.elements
33
import TestUtils
44

5-
from ClassDecl x, Type getInterfaceType, string getName, Type getType
5+
from ClassDecl x, string isUnknown, Type getInterfaceType, string getName, Type getType
66
where
77
toBeTested(x) and
88
not x.isUnknown() and
9+
(if x.isUnknown() then isUnknown = "yes" else isUnknown = "no") and
910
getInterfaceType = x.getInterfaceType() and
1011
getName = x.getName() and
1112
getType = x.getType()
12-
select x, "getInterfaceType:", getInterfaceType, "getName:", getName, "getType:", getType
13+
select x, "isUnknown:", isUnknown, "getInterfaceType:", getInterfaceType, "getName:", getName,
14+
"getType:", getType

0 commit comments

Comments
 (0)