mirror of
https://github.com/gristlabs/grist-core.git
synced 2024-10-27 20:44:07 +00:00
d8b2dcbb55
Summary: Mostly just changes to tests to make them more flexible. Test Plan: Python tests pass locally with 3.10 and 3.11. Making tests run in CI on these versions will happen in grist-core. Reviewers: paulfitz Reviewed By: paulfitz Subscribers: paulfitz Differential Revision: https://phab.getgrist.com/D3978
255 lines
11 KiB
Python
255 lines
11 KiB
Python
# coding=utf-8
|
|
|
|
import unittest
|
|
import difflib
|
|
import re
|
|
|
|
import six
|
|
from six.moves import xrange
|
|
|
|
import gencode
|
|
import identifiers
|
|
import schema
|
|
import table
|
|
import testutil
|
|
|
|
schema_data = [
|
|
[1, "Students", [
|
|
[1, "firstName", "Text", False, '', "firstName", ''],
|
|
[2, "lastName", "Text", False, '', "lastName", ''],
|
|
[3, "fullName", "Any", True,
|
|
"rec.firstName + ' ' + rec.lastName", "fullName", ''],
|
|
[4, "fullNameLen", "Any", True, "len(rec.fullName)", "fullNameLen", ''],
|
|
[5, "school", "Ref:Schools", False, '', "school", ''],
|
|
[6, "schoolShort", "Any", True, "rec.school.name.split(' ')[0]", "schoolShort", ''],
|
|
[9, "schoolRegion", "Any", True,
|
|
"addr = $school.address\naddr.state if addr.country == 'US' else addr.region",
|
|
"schoolRegion", ''],
|
|
[8, "school2", "Ref:Schools", True, "Schools.lookupFirst(name=rec.school.name)", "", ""]
|
|
]],
|
|
[2, "Schools", [
|
|
[10, "name", "Text", False, '', "name", ''],
|
|
[12, "address", "Ref:Address",False, '', "address", '']
|
|
]],
|
|
[3, "Address", [
|
|
[21, "city", "Text", False, '', "city", ''],
|
|
[27, "state", "Text", False, '', "state", ''],
|
|
[28, "country", "Text", False, "'US'", "country", ''],
|
|
[29, "region", "Any", True,
|
|
"{'US': 'North America', 'UK': 'Europe'}.get(rec.country, 'N/A')", "region", ''],
|
|
[30, "badSyntax", "Any", True, "for a in\n10", "", ""],
|
|
]]
|
|
]
|
|
|
|
class TestGenCode(unittest.TestCase):
|
|
def setUp(self):
|
|
# Convert the meta tables to appropriate table representations for loading.
|
|
meta_tables = testutil.table_data_from_rows(
|
|
'_grist_Tables',
|
|
("id", "tableId"),
|
|
[(table_row_id, table_id) for (table_row_id, table_id, _) in schema_data])
|
|
|
|
meta_columns = testutil.table_data_from_rows(
|
|
'_grist_Tables_column',
|
|
("parentId", "parentPos", "id", "colId", "type",
|
|
"isFormula", "formula", "label", "widgetOptions"),
|
|
[[table_row_id, i] + e for (table_row_id, _, entries) in schema_data
|
|
for (i, e) in enumerate(entries)])
|
|
|
|
self.schema = schema.build_schema(meta_tables, meta_columns, include_builtin=False)
|
|
|
|
def test_make_module_text(self):
|
|
"""
|
|
Test that make_module_text produces the exact sample output that we have stored
|
|
in the docstring of usercode.py.
|
|
"""
|
|
import usercode
|
|
usercode_sample_re = re.compile(r'^==========*\n', re.M)
|
|
saved_sample = usercode_sample_re.split(usercode.__doc__)[1]
|
|
|
|
gcode = gencode.GenCode()
|
|
gcode.make_module(self.schema)
|
|
generated = gcode.get_user_text()
|
|
if six.PY3:
|
|
saved_sample = saved_sample.replace(
|
|
"raise SyntaxError('invalid syntax', ('usercode', 1, 9, u'for a in'))",
|
|
"raise SyntaxError('invalid syntax\\n\\n"
|
|
"A `SyntaxError` occurs when Python cannot understand your code.\\n\\n', "
|
|
"('usercode', 1, 9, 'for a in'))"
|
|
)
|
|
self.assertEqual(generated, saved_sample, "Generated code doesn't match sample:\n" +
|
|
"".join(difflib.unified_diff(generated.splitlines(True),
|
|
saved_sample.splitlines(True),
|
|
fromfile="generated",
|
|
tofile="usercode.py")))
|
|
|
|
def test_make_module(self):
|
|
"""
|
|
Test that the generated module has the classes and nested classes we expect.
|
|
"""
|
|
gcode = gencode.GenCode()
|
|
gcode.make_module(self.schema)
|
|
module = gcode.usercode
|
|
self.assertTrue(isinstance(module.Students, table.UserTable))
|
|
|
|
def test_ident_combining_chars(self):
|
|
def check(label, ident):
|
|
self.assertEqual(ident, identifiers.pick_table_ident(label))
|
|
self.assertEqual(ident, identifiers.pick_col_ident(label))
|
|
self.assertEqual(ident.lower(), identifiers.pick_col_ident(label.lower()))
|
|
|
|
# Actual example table name from a user
|
|
# unicodedata.normalize can separate accents but doesn't help with Đ
|
|
check(
|
|
u"Bảng_Đặc_Thù",
|
|
u"Bang__ac_Thu",
|
|
)
|
|
|
|
check(
|
|
u"Noëlle",
|
|
u"Noelle",
|
|
)
|
|
check(
|
|
u"Séamus",
|
|
u"Seamus",
|
|
)
|
|
check(
|
|
u"Hélène",
|
|
u"Helene",
|
|
)
|
|
check(
|
|
u"Dilâçar",
|
|
u"Dilacar",
|
|
)
|
|
check(
|
|
u"Erdoğan",
|
|
u"Erdogan",
|
|
)
|
|
check(
|
|
u"Ñwalme",
|
|
u"Nwalme",
|
|
)
|
|
check(
|
|
u"Árvíztűrő tükörfúrógép",
|
|
u"Arvizturo_tukorfurogep",
|
|
)
|
|
|
|
def test_pick_col_ident(self):
|
|
self.assertEqual(identifiers.pick_col_ident("asdf"), "asdf")
|
|
self.assertEqual(identifiers.pick_col_ident(" a s==d!~@#$%^f"), "a_s_d_f")
|
|
self.assertEqual(identifiers.pick_col_ident("123asdf"), "c123asdf")
|
|
self.assertEqual(identifiers.pick_col_ident("!@#"), "A")
|
|
self.assertEqual(identifiers.pick_col_ident("!@#1"), "c1")
|
|
self.assertEqual(identifiers.pick_col_ident("heLLO world"), "heLLO_world")
|
|
self.assertEqual(identifiers.pick_col_ident("!@#", avoid={"A"}), "B")
|
|
|
|
self.assertEqual(identifiers.pick_col_ident("foo", avoid={"bar"}), "foo")
|
|
self.assertEqual(identifiers.pick_col_ident("foo", avoid={"foo"}), "foo2")
|
|
self.assertEqual(identifiers.pick_col_ident("foo", avoid={"foo", "foo2", "foo3"}), "foo4")
|
|
self.assertEqual(identifiers.pick_col_ident("foo1", avoid={"foo1", "foo2", "foo1_2"}), "foo1_3")
|
|
self.assertEqual(identifiers.pick_col_ident(""), "A")
|
|
self.assertEqual(identifiers.pick_table_ident(""), "Table1")
|
|
self.assertEqual(identifiers.pick_col_ident("", avoid={"A"}), "B")
|
|
self.assertEqual(identifiers.pick_col_ident("", avoid={"A","B"}), "C")
|
|
self.assertEqual(identifiers.pick_col_ident(None, avoid={"A","B"}), "C")
|
|
self.assertEqual(identifiers.pick_col_ident("", avoid={'a','b','c','d','E'}), 'F')
|
|
self.assertEqual(identifiers.pick_col_ident(2, avoid={"c2"}), "c2_2")
|
|
|
|
large_set = set()
|
|
for i in xrange(730):
|
|
large_set.add(identifiers._gen_ident(large_set))
|
|
self.assertEqual(identifiers.pick_col_ident("", avoid=large_set), "ABC")
|
|
|
|
def test_pick_table_ident(self):
|
|
self.assertEqual(identifiers.pick_table_ident("123asdf"), "T123asdf")
|
|
self.assertEqual(identifiers.pick_table_ident("!@#"), "Table1")
|
|
self.assertEqual(identifiers.pick_table_ident("!@#1"), "T1")
|
|
|
|
self.assertEqual(identifiers.pick_table_ident("heLLO world"), "HeLLO_world")
|
|
self.assertEqual(identifiers.pick_table_ident("foo", avoid={"Foo"}), "Foo2")
|
|
self.assertEqual(identifiers.pick_table_ident("foo", avoid={"Foo", "Foo2"}), "Foo3")
|
|
self.assertEqual(identifiers.pick_table_ident("FOO", avoid={"foo", "foo2"}), "FOO3")
|
|
|
|
self.assertEqual(identifiers.pick_table_ident(None, avoid={"Table"}), "Table1")
|
|
self.assertEqual(identifiers.pick_table_ident(None, avoid={"Table1"}), "Table2")
|
|
self.assertEqual(identifiers.pick_table_ident("!@#", avoid={"Table1"}), "Table2")
|
|
self.assertEqual(identifiers.pick_table_ident(None, avoid={"Table1", "Table2"}), "Table3")
|
|
|
|
large_set = set()
|
|
for i in xrange(730):
|
|
large_set.add("Table%d" % i)
|
|
self.assertEqual(identifiers.pick_table_ident("", avoid=large_set), "Table730")
|
|
|
|
def test_pick_col_ident_list(self):
|
|
self.assertEqual(identifiers.pick_col_ident_list(["foo", "bar"], avoid={"bar"}),
|
|
["foo", "bar2"])
|
|
self.assertEqual(identifiers.pick_col_ident_list(["bar", "bar"], avoid={"foo"}),
|
|
["bar", "bar2"])
|
|
self.assertEqual(identifiers.pick_col_ident_list(["bar", "bar"], avoid={"bar"}),
|
|
["bar2", "bar3"])
|
|
self.assertEqual(identifiers.pick_col_ident_list(["bAr", "BAR"], avoid={"bar"}),
|
|
["bAr2", "BAR3"])
|
|
|
|
def test_gen_ident(self):
|
|
self.assertEqual(identifiers._gen_ident(set()), 'A')
|
|
self.assertEqual(identifiers._gen_ident({'A'}), 'B')
|
|
self.assertEqual(identifiers._gen_ident({'foo','E','F','H'}), 'A')
|
|
self.assertEqual(identifiers._gen_ident({'a','b','c','d','E'}), 'F')
|
|
|
|
def test_get_grist_type(self):
|
|
self.assertEqual(gencode.get_grist_type("Ref:Foo"), "grist.Reference('Foo')")
|
|
self.assertEqual(gencode.get_grist_type("RefList:Foo"), "grist.ReferenceList('Foo')")
|
|
self.assertEqual(gencode.get_grist_type("Int"), "grist.Int()")
|
|
self.assertEqual(gencode.get_grist_type("DateTime:America/NewYork"),
|
|
"grist.DateTime('America/NewYork')")
|
|
self.assertEqual(gencode.get_grist_type("DateTime:"), "grist.DateTime()")
|
|
self.assertEqual(gencode.get_grist_type("DateTime"), "grist.DateTime()")
|
|
self.assertEqual(gencode.get_grist_type("DateTime: foo bar "), "grist.DateTime('foo bar')")
|
|
self.assertEqual(gencode.get_grist_type("DateTime: "), "grist.DateTime()")
|
|
self.assertEqual(gencode.get_grist_type("RefList:\n ~!@#$%^&*'\":;,\t"),
|
|
"grist.ReferenceList('~!@#$%^&*\\'\":;,')")
|
|
|
|
def test_grist_names(self):
|
|
# Verifies that we can correctly extract the names of Grist objects that occur in formulas.
|
|
# This is used by automatic formula adjustments when columns or tables get renamed.
|
|
gcode = gencode.GenCode()
|
|
gcode.make_module(self.schema)
|
|
# The output of grist_names is described in codebuilder.py, and copied here:
|
|
# col_info: (table_id, col_id) for the formula the name is found in. It is the value passed
|
|
# in by gencode.py to codebuilder.make_formula_body().
|
|
# start_pos: Index of the start character of the name in the text of the formula.
|
|
# table_id: Parsed name when the tuple is for a table name; the name of the column's table
|
|
# when the tuple is for a column name.
|
|
# col_id: None when tuple is for a table name; col_id when the tuple is for a column name.
|
|
expected_names = [
|
|
(('Students', 'fullName'), 4, 'Students', 'firstName'),
|
|
(('Students', 'fullName'), 26, 'Students', 'lastName'),
|
|
(('Students', 'fullNameLen'), 8, 'Students', 'fullName'),
|
|
(('Students', 'schoolShort'), 11, 'Schools', 'name'),
|
|
(('Students', 'schoolShort'), 4, 'Students', 'school'),
|
|
(('Students', 'schoolRegion'), 15, 'Schools', 'address'),
|
|
(('Students', 'schoolRegion'), 8, 'Students', 'school'),
|
|
(('Students', 'schoolRegion'), 42, 'Address', 'country'),
|
|
(('Students', 'schoolRegion'), 28, 'Address', 'state'),
|
|
(('Students', 'schoolRegion'), 68, 'Address', 'region'),
|
|
(('Students', 'school2'), 0, 'Schools', None),
|
|
(('Students', 'school2'), 36, 'Schools', 'name'),
|
|
(('Students', 'school2'), 29, 'Students', 'school'),
|
|
(('Address', 'region'), 48, 'Address', 'country'),
|
|
]
|
|
self.assertEqual(gcode.grist_names(), expected_names)
|
|
|
|
# Test the case of a bare-word function with a keyword argument appearing in a formula. This
|
|
# case had a bug with code parsing.
|
|
self.schema['Address'].columns['testcol'] = schema.SchemaColumn(
|
|
'testcol', 'Any', True, 'foo(bar=$region) or max(Students.all, key=lambda n: -n)')
|
|
gcode.make_module(self.schema)
|
|
self.assertEqual(gcode.grist_names(), expected_names + [
|
|
(('Address', 'testcol'), 9, 'Address', 'region'),
|
|
(('Address', 'testcol'), 24, 'Students', None),
|
|
])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|