Skip to content

Commit 0100c71

Browse files
committed
Swift: testing non-trivial dataclass properties
1 parent 7f04760 commit 0100c71

File tree

10 files changed

+316
-184
lines changed

10 files changed

+316
-184
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@
2020
# python virtual environment folder
2121
.venv/
2222

23+
# binary files created by pytest-cov
24+
.coverage
25+
2326
# It's useful (though not required) to be able to unpack codeql in the ql checkout itself
2427
/codeql/
2528

swift/codegen/.coverage

-52 KB
Binary file not shown.

swift/codegen/dbschemegen.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -19,42 +19,42 @@ def dbtype(typename):
1919
def cls_to_dbscheme(cls: schema.Class):
2020
""" Yield all dbscheme entities needed to model class `cls` """
2121
if cls.derived:
22-
yield DbUnion(dbtype(cls.name), (dbtype(c) for c in cls.derived))
22+
yield Union(dbtype(cls.name), (dbtype(c) for c in cls.derived))
2323
# output a table specific to a class only if it is a leaf class or it has 1-to-1 properties
2424
# Leaf classes need a table to bind the `@` ids
2525
# 1-to-1 properties are added to a class specific table
2626
# in other cases, separate tables are used for the properties, and a class specific table is unneeded
2727
if not cls.derived or any(f.is_single for f in cls.properties):
2828
binding = not cls.derived
29-
keyset = DbKeySet(["id"]) if cls.derived else None
30-
yield DbTable(
29+
keyset = KeySet(["id"]) if cls.derived else None
30+
yield Table(
3131
keyset=keyset,
3232
name=inflection.tableize(cls.name),
3333
columns=[
34-
DbColumn("id", type=dbtype(cls.name), binding=binding),
34+
Column("id", type=dbtype(cls.name), binding=binding),
3535
] + [
36-
DbColumn(f.name, dbtype(f.type)) for f in cls.properties if f.is_single
36+
Column(f.name, dbtype(f.type)) for f in cls.properties if f.is_single
3737
]
3838
)
3939
# use property-specific tables for 1-to-many and 1-to-at-most-1 properties
4040
for f in cls.properties:
4141
if f.is_optional:
42-
yield DbTable(
43-
keyset=DbKeySet(["id"]),
42+
yield Table(
43+
keyset=KeySet(["id"]),
4444
name=inflection.tableize(f"{cls.name}_{f.name}"),
4545
columns=[
46-
DbColumn("id", type=dbtype(cls.name)),
47-
DbColumn(f.name, dbtype(f.type)),
46+
Column("id", type=dbtype(cls.name)),
47+
Column(f.name, dbtype(f.type)),
4848
],
4949
)
5050
elif f.is_repeated:
51-
yield DbTable(
52-
keyset=DbKeySet(["id", "index"]),
51+
yield Table(
52+
keyset=KeySet(["id", "index"]),
5353
name=inflection.tableize(f"{cls.name}_{f.name}"),
5454
columns=[
55-
DbColumn("id", type=dbtype(cls.name)),
56-
DbColumn("index", type="int"),
57-
DbColumn(inflection.singularize(f.name), dbtype(f.type)),
55+
Column("id", type=dbtype(cls.name)),
56+
Column("index", type="int"),
57+
Column(inflection.singularize(f.name), dbtype(f.type)),
5858
]
5959
)
6060

@@ -68,7 +68,7 @@ def get_includes(data: schema.Schema, include_dir: pathlib.Path):
6868
for inc in data.includes:
6969
inc = include_dir / inc
7070
with open(inc) as inclusion:
71-
includes.append(DbSchemeInclude(src=inc.relative_to(paths.swift_dir), data=inclusion.read()))
71+
includes.append(SchemeInclude(src=inc.relative_to(paths.swift_dir), data=inclusion.read()))
7272
return includes
7373

7474

@@ -78,7 +78,7 @@ def generate(opts, renderer):
7878

7979
data = schema.load(input)
8080

81-
dbscheme = DbScheme(src=input.relative_to(paths.swift_dir),
81+
dbscheme = Scheme(src=input.relative_to(paths.swift_dir),
8282
includes=get_includes(data, include_dir=input.parent),
8383
declarations=get_declarations(data))
8484

swift/codegen/lib/dbscheme.py

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111

1212
@dataclass
13-
class DbColumn:
13+
class Column:
1414
schema_name: str
1515
type: str
1616
binding: bool = False
@@ -36,69 +36,69 @@ def rhstype(self):
3636

3737

3838
@dataclass
39-
class DbKeySetId:
39+
class KeySetId:
4040
id: str
4141
first: bool = False
4242

4343

4444
@dataclass
45-
class DbKeySet:
46-
ids: List[DbKeySetId]
45+
class KeySet:
46+
ids: List[KeySetId]
4747

4848
def __post_init__(self):
4949
assert self.ids
50-
self.ids = [DbKeySetId(x) for x in self.ids]
50+
self.ids = [KeySetId(x) for x in self.ids]
5151
self.ids[0].first = True
5252

5353

54-
class DbDecl:
54+
class Decl:
5555
is_table = False
5656
is_union = False
5757

5858

5959
@dataclass
60-
class DbTable(DbDecl):
60+
class Table(Decl):
6161
is_table: ClassVar = True
6262

6363
name: str
64-
columns: List[DbColumn]
65-
keyset: DbKeySet = None
64+
columns: List[Column]
65+
keyset: KeySet = None
6666

6767
def __post_init__(self):
6868
if self.columns:
6969
self.columns[0].first = True
7070

7171

7272
@dataclass
73-
class DbUnionCase:
73+
class UnionCase:
7474
type: str
7575
first: bool = False
7676

7777

7878
@dataclass
79-
class DbUnion(DbDecl):
79+
class Union(Decl):
8080
is_union: ClassVar = True
8181

8282
lhs: str
83-
rhs: List[DbUnionCase]
83+
rhs: List[UnionCase]
8484

8585
def __post_init__(self):
8686
assert self.rhs
87-
self.rhs = [DbUnionCase(x) for x in self.rhs]
87+
self.rhs = [UnionCase(x) for x in self.rhs]
8888
self.rhs.sort(key=lambda c: c.type)
8989
self.rhs[0].first = True
9090

9191

9292
@dataclass
93-
class DbSchemeInclude:
93+
class SchemeInclude:
9494
src: str
9595
data: str
9696

9797

9898
@dataclass
99-
class DbScheme:
99+
class Scheme:
100100
template: ClassVar = 'dbscheme'
101101

102102
src: str
103-
includes: List[DbSchemeInclude]
104-
declarations: List[DbDecl]
103+
includes: List[SchemeInclude]
104+
declarations: List[Decl]

swift/codegen/lib/ql.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,20 @@
66

77

88
@dataclass
9-
class QlParam:
9+
class Param:
1010
param: str
1111
type: str = None
1212
first: bool = False
1313

1414

1515
@dataclass
16-
class QlProperty:
16+
class Property:
1717
singular: str
1818
type: str
1919
tablename: str
20-
tableparams: List[QlParam]
20+
tableparams: List[Param]
2121
plural: str = None
22-
params: List[QlParam] = field(default_factory=list)
22+
params: List[Param] = field(default_factory=list)
2323
first: bool = False
2424
local_var: str = "x"
2525

@@ -31,7 +31,7 @@ def __post_init__(self):
3131
assert self.tableparams
3232
if self.type_is_class:
3333
self.tableparams = [x if x != "result" else self.local_var for x in self.tableparams]
34-
self.tableparams = [QlParam(x) for x in self.tableparams]
34+
self.tableparams = [Param(x) for x in self.tableparams]
3535
self.tableparams[0].first = True
3636

3737
@property
@@ -45,13 +45,13 @@ def type_is_class(self):
4545

4646

4747
@dataclass
48-
class QlClass:
48+
class Class:
4949
template: ClassVar = 'ql_class'
5050

5151
name: str
5252
bases: List[str] = field(default_factory=list)
5353
final: bool = False
54-
properties: List[QlProperty] = field(default_factory=list)
54+
properties: List[Property] = field(default_factory=list)
5555
dir: pathlib.Path = pathlib.Path()
5656
imports: List[str] = field(default_factory=list)
5757

@@ -74,15 +74,15 @@ def path(self):
7474

7575

7676
@dataclass
77-
class QlStub:
77+
class Stub:
7878
template: ClassVar = 'ql_stub'
7979

8080
name: str
8181
base_import: str
8282

8383

8484
@dataclass
85-
class QlImportList:
85+
class ImportList:
8686
template: ClassVar = 'ql_imports'
8787

8888
imports: List[str] = field(default_factory=list)

swift/codegen/qlgen.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,32 @@
1212

1313
def get_ql_property(cls: schema.Class, prop: schema.Property):
1414
if prop.is_single:
15-
return ql.QlProperty(
15+
return ql.Property(
1616
singular=inflection.camelize(prop.name),
1717
type=prop.type,
1818
tablename=inflection.tableize(cls.name),
1919
tableparams=["this"] + ["result" if p is prop else "_" for p in cls.properties if p.is_single],
2020
)
2121
elif prop.is_optional:
22-
return ql.QlProperty(
22+
return ql.Property(
2323
singular=inflection.camelize(prop.name),
2424
type=prop.type,
2525
tablename=inflection.tableize(f"{cls.name}_{prop.name}"),
2626
tableparams=["this", "result"],
2727
)
2828
elif prop.is_repeated:
29-
return ql.QlProperty(
29+
return ql.Property(
3030
singular=inflection.singularize(inflection.camelize(prop.name)),
3131
plural=inflection.pluralize(inflection.camelize(prop.name)),
3232
type=prop.type,
3333
tablename=inflection.tableize(f"{cls.name}_{prop.name}"),
3434
tableparams=["this", "index", "result"],
35-
params=[ql.QlParam("index", type="int")],
35+
params=[ql.Param("index", type="int")],
3636
)
3737

3838

3939
def get_ql_class(cls: schema.Class):
40-
return ql.QlClass(
40+
return ql.Class(
4141
name=cls.name,
4242
bases=cls.bases,
4343
final=not cls.derived,
@@ -51,7 +51,7 @@ def get_import(file):
5151
return str(stem).replace("/", ".")
5252

5353

54-
def get_types_used_by(cls: ql.QlClass):
54+
def get_types_used_by(cls: ql.Class):
5555
for b in cls.bases:
5656
yield b
5757
for p in cls.properties:
@@ -60,7 +60,7 @@ def get_types_used_by(cls: ql.QlClass):
6060
yield param.type
6161

6262

63-
def get_classes_used_by(cls: ql.QlClass):
63+
def get_classes_used_by(cls: ql.Class):
6464
return sorted(set(t for t in get_types_used_by(cls) if t[0].isupper()))
6565

6666

@@ -98,12 +98,12 @@ def generate(opts, renderer):
9898
renderer.render(c, qll)
9999
stub_file = (stub_out / c.path).with_suffix(".qll")
100100
if not stub_file.is_file() or is_generated(stub_file):
101-
stub = ql.QlStub(name=c.name, base_import=get_import(qll))
101+
stub = ql.Stub(name=c.name, base_import=get_import(qll))
102102
renderer.render(stub, stub_file)
103103

104104
# for example path/to/syntax/generated -> path/to/syntax.qll
105105
include_file = stub_out.with_suffix(".qll")
106-
all_imports = ql.QlImportList([v for _, v in sorted(imports.items())])
106+
all_imports = ql.ImportList([v for _, v in sorted(imports.items())])
107107
renderer.render(all_imports, include_file)
108108

109109
renderer.cleanup(existing)

swift/codegen/test/test_dbscheme.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import sys
2+
from copy import deepcopy
3+
4+
from swift.codegen.lib import dbscheme
5+
from swift.codegen.test.utils import *
6+
7+
8+
def test_dbcolumn_name():
9+
assert dbscheme.Column("foo", "some_type").name == "foo"
10+
11+
12+
@pytest.mark.parametrize("keyword", dbscheme.dbscheme_keywords)
13+
def test_dbcolumn_keyword_name(keyword):
14+
assert dbscheme.Column(keyword, "some_type").name == keyword + "_"
15+
16+
17+
@pytest.mark.parametrize("type,binding,lhstype,rhstype", [
18+
("builtin_type", False, "builtin_type", "builtin_type ref"),
19+
("builtin_type", True, "builtin_type", "builtin_type ref"),
20+
("@at_type", False, "int", "@at_type ref"),
21+
("@at_type", True, "unique int", "@at_type"),
22+
])
23+
def test_dbcolumn_types(type, binding, lhstype, rhstype):
24+
col = dbscheme.Column("foo", type, binding)
25+
assert col.lhstype == lhstype
26+
assert col.rhstype == rhstype
27+
28+
29+
def test_keyset_has_first_id_marked():
30+
ids = ["a", "b", "c"]
31+
ks = dbscheme.KeySet(ids)
32+
assert ks.ids[0].first
33+
assert [id.id for id in ks.ids] == ids
34+
35+
36+
def test_table_has_first_column_marked():
37+
columns = [dbscheme.Column("a", "x"), dbscheme.Column("b", "y", binding=True), dbscheme.Column("c", "z")]
38+
expected = deepcopy(columns)
39+
table = dbscheme.Table("foo", columns)
40+
expected[0].first = True
41+
assert table.columns == expected
42+
43+
44+
def test_union_has_first_case_marked():
45+
rhs = ["a", "b", "c"]
46+
u = dbscheme.Union(lhs="x", rhs=rhs)
47+
assert u.rhs[0].first
48+
assert [c.type for c in u.rhs] == rhs
49+
50+
51+
if __name__ == '__main__':
52+
sys.exit(pytest.main())

0 commit comments

Comments
 (0)