(core) Make Python tests pass in Python 3.11

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
This commit is contained in:
Alex Hall 2023-07-28 14:25:00 +02:00
parent a77170c4bd
commit d8b2dcbb55
5 changed files with 49 additions and 32 deletions

View File

@ -52,7 +52,10 @@ class AutocompleteContext(object):
} }
for key, value in six.iteritems(self._context): for key, value in six.iteritems(self._context):
if value and callable(value): if value and callable(value):
if six.PY2:
argspec = inspect.formatargspec(*inspect.getargspec(value)) argspec = inspect.formatargspec(*inspect.getargspec(value))
else:
argspec = str(inspect.signature(value)) # pylint: disable=no-member
self._functions[key] = Completion(key, argspec, is_grist_func(value)) self._functions[key] = Completion(key, argspec, is_grist_func(value))
for key, value in self._context.copy().items(): for key, value in self._context.copy().items():

View File

@ -1,4 +1,5 @@
import datetime import datetime
import sys
import test_engine import test_engine
import testsamples import testsamples
@ -69,16 +70,13 @@ class TestCompletion(test_engine.EngineTestCase):
self.assertEqual(self.autocomplete("valu", "Schools", "lastModifier"), self.assertEqual(self.autocomplete("valu", "Schools", "lastModifier"),
["value"]) ["value"])
# Should have same type as column. # Should have same type as column.
self.assertGreaterEqual( self.assert_autocomplete_includes("value.", "Schools", "lastModifier",
set(self.autocomplete("value.", "Schools", "lastModifier")),
{'value.startswith(', 'value.replace(', 'value.title('} {'value.startswith(', 'value.replace(', 'value.title('}
) )
self.assertGreaterEqual( self.assert_autocomplete_includes("value.", "Schools", "lastModified",
set(self.autocomplete("value.", "Schools", "lastModified")),
{'value.month', 'value.strftime(', 'value.replace('} {'value.month', 'value.strftime(', 'value.replace('}
) )
self.assertGreaterEqual( self.assert_autocomplete_includes("value.m", "Schools", "lastModified",
set(self.autocomplete("value.m", "Schools", "lastModified")),
{'value.month', 'value.minute'} {'value.month', 'value.minute'}
) )
@ -159,13 +157,13 @@ class TestCompletion(test_engine.EngineTestCase):
def test_function(self): def test_function(self):
self.assertEqual(self.autocomplete("MEDI", "Address", "city"), self.assertEqual(self.autocomplete("MEDI", "Address", "city"),
[('MEDIAN', '(value, *more_values)', True)]) [('MEDIAN', '(value, *more_values)', True)])
self.assertEqual(self.autocomplete("ma", "Address", "city"), [ self.assert_autocomplete_includes("ma", "Address", "city", {
('MAX', '(value, *more_values)', True), ('MAX', '(value, *more_values)', True),
('MAXA', '(value, *more_values)', True), ('MAXA', '(value, *more_values)', True),
'map(', 'map(',
'math', 'math',
'max(', 'max(',
]) })
def test_member(self): def test_member(self):
self.assertEqual(self.autocomplete("datetime.tz", "Address", "city"), self.assertEqual(self.autocomplete("datetime.tz", "Address", "city"),
@ -294,34 +292,28 @@ class TestCompletion(test_engine.EngineTestCase):
def test_suggest_column_type_methods(self): def test_suggest_column_type_methods(self):
# Should treat columns as correct types. # Should treat columns as correct types.
self.assertGreaterEqual( self.assert_autocomplete_includes("$firstName.", "Students", "firstName",
set(self.autocomplete("$firstName.", "Students", "firstName")),
{'$firstName.startswith(', '$firstName.replace(', '$firstName.title('} {'$firstName.startswith(', '$firstName.replace(', '$firstName.title('}
) )
self.assertGreaterEqual( self.assert_autocomplete_includes("$birthDate.", "Students", "lastName",
set(self.autocomplete("$birthDate.", "Students", "lastName")),
{'$birthDate.month', '$birthDate.strftime(', '$birthDate.replace('} {'$birthDate.month', '$birthDate.strftime(', '$birthDate.replace('}
) )
self.assertGreaterEqual( self.assert_autocomplete_includes("$lastVisit.m", "Students", "firstName",
set(self.autocomplete("$lastVisit.m", "Students", "firstName")),
{'$lastVisit.month', '$lastVisit.minute'} {'$lastVisit.month', '$lastVisit.minute'}
) )
self.assertGreaterEqual( self.assert_autocomplete_includes("$school.", "Students", "firstName",
set(self.autocomplete("$school.", "Students", "firstName")),
{'$school.address', '$school.name', '$school.yearFounded', '$school.budget'} {'$school.address', '$school.name', '$school.yearFounded', '$school.budget'}
) )
self.assertEqual(self.autocomplete("$school.year", "Students", "lastName"), self.assertEqual(self.autocomplete("$school.year", "Students", "lastName"),
['$school.yearFounded']) ['$school.yearFounded'])
self.assertGreaterEqual( self.assert_autocomplete_includes("$yearFounded.", "Schools", "budget",
set(self.autocomplete("$yearFounded.", "Schools", "budget")),
{ {
'$yearFounded.denominator', # Only integers have this '$yearFounded.denominator', # Only integers have this
'$yearFounded.bit_length(', # and this '$yearFounded.bit_length(', # and this
'$yearFounded.real' '$yearFounded.real'
} }
) )
self.assertGreaterEqual( self.assert_autocomplete_includes("$budget.", "Schools", "budget",
set(self.autocomplete("$budget.", "Schools", "budget")),
{'$budget.is_integer(', '$budget.real'} # Only floats have this {'$budget.is_integer(', '$budget.real'} # Only floats have this
) )
@ -331,8 +323,7 @@ class TestCompletion(test_engine.EngineTestCase):
self.autocomplete("$school.name.st", "Students", "firstName"), self.autocomplete("$school.name.st", "Students", "firstName"),
['$school.name.startswith(', '$school.name.strip('] ['$school.name.startswith(', '$school.name.strip(']
) )
self.assertGreaterEqual( self.assert_autocomplete_includes("$school.yearFounded.","Students", "firstName",
set(self.autocomplete("$school.yearFounded.","Students", "firstName")),
{ {
'$school.yearFounded.denominator', '$school.yearFounded.denominator',
'$school.yearFounded.bit_length(', '$school.yearFounded.bit_length(',
@ -498,6 +489,20 @@ class TestCompletion(test_engine.EngineTestCase):
else: else:
return results return results
def assert_autocomplete_includes(self, formula, table, column, expected, user=None, row_id=None):
completions = self.autocomplete(formula, table, column, user=user, row_id=row_id)
def replace_completion(completion):
if isinstance(completion, str) and completion.endswith('()'):
# Python 3.10+ autocompletes the closing paren for methods with no arguments.
# This allows the test to check for `somestring.title(` and work across Python versions.
assert sys.version_info >= (3, 10)
return completion[:-1]
return completion
completions = set(replace_completion(completion) for completion in completions)
self.assertGreaterEqual(completions, expected)
def test_example_values(self): def test_example_values(self):
self.assertEqual( self.assertEqual(
self.autocomplete("$", "Schools", "name", row_id=1), self.autocomplete("$", "Schools", "name", row_id=1),

View File

@ -2,6 +2,7 @@ import difflib
import functools import functools
import json import json
import logging import logging
import sys
import unittest import unittest
from collections import namedtuple from collections import namedtuple
from pprint import pprint from pprint import pprint
@ -273,7 +274,16 @@ class EngineTestCase(unittest.TestCase):
self.assertIsInstance(exc.error, type_) self.assertIsInstance(exc.error, type_)
self.assertEqual(exc._message, message) self.assertEqual(exc._message, message)
if tracebackRegexp: if tracebackRegexp:
self.assertRegex(exc.details, tracebackRegexp) traceback_string = exc.details
if sys.version_info >= (3, 11) and type_ != SyntaxError:
# Python 3.11+ adds lines with only spaces and ^ to indicate the location of the error.
# We remove those lines to make the test work with both old and new versions.
# This doesn't apply to SyntaxError, which has those lines in all versions.
traceback_string = "\n".join(
line for line in traceback_string.splitlines()
if set(line) != {" ", "^"}
)
self.assertRegex(traceback_string.strip(), tracebackRegexp.strip())
def assertViews(self, list_of_views): def assertViews(self, list_of_views):
""" """

View File

@ -37,7 +37,7 @@ schema_data = [
[28, "country", "Text", False, "'US'", "country", ''], [28, "country", "Text", False, "'US'", "country", ''],
[29, "region", "Any", True, [29, "region", "Any", True,
"{'US': 'North America', 'UK': 'Europe'}.get(rec.country, 'N/A')", "region", ''], "{'US': 'North America', 'UK': 'Europe'}.get(rec.country, 'N/A')", "region", ''],
[30, "badSyntax", "Any", True, "for a in b\n10", "", ""], [30, "badSyntax", "Any", True, "for a in\n10", "", ""],
]] ]]
] ]
@ -72,11 +72,10 @@ class TestGenCode(unittest.TestCase):
generated = gcode.get_user_text() generated = gcode.get_user_text()
if six.PY3: if six.PY3:
saved_sample = saved_sample.replace( saved_sample = saved_sample.replace(
"raise SyntaxError('invalid syntax', ('usercode', 1, 11, u'for a in b'))", "raise SyntaxError('invalid syntax', ('usercode', 1, 9, u'for a in'))",
"raise SyntaxError('invalid syntax\\n\\n" "raise SyntaxError('invalid syntax\\n\\n"
"A `SyntaxError` occurs when Python cannot understand your code.\\n\\n" "A `SyntaxError` occurs when Python cannot understand your code.\\n\\n', "
"You wrote a `for` loop but\\nforgot to add a colon `:` at the end\\n\\n', " "('usercode', 1, 9, 'for a in'))"
"('usercode', 1, 11, 'for a in b'))"
) )
self.assertEqual(generated, saved_sample, "Generated code doesn't match sample:\n" + self.assertEqual(generated, saved_sample, "Generated code doesn't match sample:\n" +
"".join(difflib.unified_diff(generated.splitlines(True), "".join(difflib.unified_diff(generated.splitlines(True),

View File

@ -61,8 +61,8 @@ class Address:
return {'US': 'North America', 'UK': 'Europe'}.get(rec.country, 'N/A') return {'US': 'North America', 'UK': 'Europe'}.get(rec.country, 'N/A')
def badSyntax(rec, table): def badSyntax(rec, table):
# for a in b # for a in
# 10 # 10
raise SyntaxError('invalid syntax', ('usercode', 1, 11, u'for a in b')) raise SyntaxError('invalid syntax', ('usercode', 1, 9, u'for a in'))
====================================================================== ======================================================================
""" """