From 1420c5494790b5b1d9e0e74d257e8ca4121f6f29 Mon Sep 17 00:00:00 2001 From: Eric Froemling Date: Sat, 14 Dec 2019 10:52:44 -0800 Subject: [PATCH] added some entity tests --- .idea/dictionaries/ericf.xml | 9 +++ config/config.json | 1 + config/toolconfigsrc/mypy.ini | 3 + tests/test_bafoundation/test_entities.py | 96 ++++++++++++++++++++---- tools/efrotools/statictest.py | 43 +++++++---- 5 files changed, 121 insertions(+), 31 deletions(-) diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index 8546cc4e..91c91dd1 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -187,6 +187,7 @@ bumpmap buttondown buttonwidget + bval byteswap cachable cachebasename @@ -281,6 +282,7 @@ compat compileall compilelocations + compoundlist configerror confighash configkey @@ -470,6 +472,7 @@ entrytype entrytypeselect enumtype + enumval envhash epath epicfail @@ -613,6 +616,7 @@ functxt funtionality futimens + fval gameactivity gamebutton gameclass @@ -794,10 +798,12 @@ iserverget iserverput ispunch + isubval isysroot itms itmsp itunes + ival jackmorgan janktastic janky @@ -1442,6 +1448,7 @@ sitebuiltins skey sline + slval smlh smtpd smtplib @@ -1546,6 +1553,7 @@ svalin sver svne + svvv swip swipsound symlinked @@ -1576,6 +1584,7 @@ tchar tcombine tdelay + tdval teambasesession teamgame teamnamescolors diff --git a/config/config.json b/config/config.json index f53a7cb6..cbe08a29 100644 --- a/config/config.json +++ b/config/config.json @@ -18,6 +18,7 @@ "astroid", "bs_mapdefs_bridgit", "pylint.lint", + "pytest", "bs_mapdefs_zig_zag", "bs_mapdefs_the_pad", "bs_mapdefs_step_right_up", diff --git a/config/toolconfigsrc/mypy.ini b/config/toolconfigsrc/mypy.ini index d8dbd07c..6d34d740 100644 --- a/config/toolconfigsrc/mypy.ini +++ b/config/toolconfigsrc/mypy.ini @@ -18,6 +18,9 @@ ignore_errors = True [mypy-astroid.*] ignore_missing_imports = True +[mypy-pytest.*] +ignore_missing_imports = True + [mypy-efrotools.pylintplugins] disallow_any_unimported = False diff --git a/tests/test_bafoundation/test_entities.py b/tests/test_bafoundation/test_entities.py index 487471a8..43b1ca3e 100644 --- a/tests/test_bafoundation/test_entities.py +++ b/tests/test_bafoundation/test_entities.py @@ -22,30 +22,94 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Sequence +from typing import TYPE_CHECKING +from enum import Enum, unique +import pytest + +from bafoundation import entity from efrotools.statictest import static_type_equals if TYPE_CHECKING: pass -def inc(x: int) -> int: - """Testing inc.""" - return x + 1 +# A smattering of enum value types... +@unique +class EnumTest(Enum): + """Testing...""" + FIRST = 0 + SECOND = 1 -def test_answer() -> None: - """Testing answer.""" - fooval: List[int] = [3, 4] - assert static_type_equals(fooval[0], int) - assert static_type_equals(fooval, List[int]) - somevar: Sequence[int] = [] - assert static_type_equals(somevar, Sequence[int]) - assert isinstance(fooval, list) - assert inc(3) == 4 +class CompoundTest(entity.CompoundValue): + """Testing...""" + isubval = entity.Field('i', entity.IntValue(default=34532)) -def test_answer2() -> None: - """Testing answer.""" - assert inc(3) == 4 +class CompoundTest2(CompoundTest): + """Testing...""" + isubval2 = entity.Field('i2', entity.IntValue(default=3453)) + + +class EntityTest(entity.Entity): + """Testing...""" + ival = entity.Field('i', entity.IntValue(default=345)) + sval = entity.Field('s', entity.StringValue(default='svvv')) + bval = entity.Field('b', entity.BoolValue(default=True)) + fval = entity.Field('f', entity.FloatValue(default=1.0)) + grp = entity.CompoundField('g', CompoundTest2()) + grp2 = entity.CompoundField('g2', CompoundTest()) + enumval = entity.Field('e', entity.EnumValue(EnumTest, default=None)) + enumval2 = entity.Field( + 'e2', entity.OptionalEnumValue(EnumTest, default=EnumTest.SECOND)) + compoundlist = entity.CompoundListField('l', CompoundTest()) + slval = entity.ListField('sl', entity.StringValue()) + tval2 = entity.Field('t2', entity.DateTimeValue()) + str_int_dict = entity.DictField('sd', str, entity.IntValue()) + tdval = entity.CompoundDictField('td', str, CompoundTest()) + fval2 = entity.Field('f2', entity.Float3Value()) + + +class EntityTest2(entity.EntityMixin, CompoundTest2): + """test.""" + + +def test_entity_values() -> None: + """Test various entity assigns for value and type correctness.""" + ent = EntityTest() + + # Simple int field. + with pytest.raises(TypeError): + # noinspection PyTypeHints + ent.ival = 'strval' # type: ignore + assert static_type_equals(ent.ival, int) + assert isinstance(ent.ival, int) + assert ent.ival == 345 + ent.ival = 346 + assert ent.ival == 346 + + # Simple str/int dict field. + assert 'foo' not in ent.str_int_dict + with pytest.raises(TypeError): + # noinspection PyTypeHints + ent.str_int_dict[0] = 123 # type: ignore + with pytest.raises(TypeError): + # noinspection PyTypeHints + ent.str_int_dict['foo'] = 'bar' # type: ignore + ent.str_int_dict['foo'] = 123 + assert static_type_equals(ent.str_int_dict['foo'], int) + assert ent.str_int_dict['foo'] == 123 + + # Compound list field. + with pytest.raises(IndexError): + print(ent.compoundlist[0]) + with pytest.raises(TypeError): + # noinspection PyTypeHints + ent.compoundlist[0] = 123 # type: ignore + assert len(ent.compoundlist) == 0 + assert not ent.compoundlist + ent.compoundlist.append() + assert ent.compoundlist + assert len(ent.compoundlist) == 1 + assert static_type_equals(ent.compoundlist[0], CompoundTest) diff --git a/tools/efrotools/statictest.py b/tools/efrotools/statictest.py index 579de664..1d9afc4b 100644 --- a/tools/efrotools/statictest.py +++ b/tools/efrotools/statictest.py @@ -52,6 +52,8 @@ class StaticTestFile: from efrotools import PYTHON_BIN self._filename = filename + self.modulename = f'temp{_nextfilenum}' + _nextfilenum += 1 # Types we *want* for lines self.linetypes_wanted: Dict[int, str] = {} @@ -71,26 +73,32 @@ class StaticTestFile: # (so that we can recycle our mypy cache). if _tempdir is None: _tempdir = tempfile.TemporaryDirectory() + # print(f"Created temp dir at {_tempdir.name}") # Copy our file into the temp dir with a unique name, find all # instances of static_type_equals(), and run mypy type checks # in those places to get static types. - tempfilepath = os.path.join(_tempdir.name, f'temp{_nextfilenum}.py') - _nextfilenum += 1 + tempfilepath = os.path.join(_tempdir.name, self.modulename + '.py') with open(tempfilepath, 'w') as outfile: outfile.write(self.filter_file_contents(fdata)) - results = subprocess.run([ - PYTHON_BIN, '-m', 'mypy', '--no-error-summary', '--config-file', - '.mypy.ini', '--cache-dir', _tempdir.name, tempfilepath - ], - capture_output=True, - check=False) - # HMM; it seems we get an errored return code due to reveal_type()s. - # So I guess we just have to ignore other errors, which is unforunate. - # (though if the run fails, we'll probably error when attempting to - # look up a revealed type that we don't have anyway) + results = subprocess.run( + [ + PYTHON_BIN, '-m', 'mypy', '--no-error-summary', + '--config-file', '.mypy.ini', '--cache-dir', + os.path.join(_tempdir.name, '.mypy_cache'), tempfilepath + ], + capture_output=True, + check=False, + ) + + # HMM; it seems we always get an errored return code due to + # our use of reveal_type() so we can't look at that. + # However we can look for error notices in the output and fail there. lines = results.stdout.decode().splitlines() for line in lines: + if ': error: ' in line: + print("Full mypy output:\n", results.stdout.decode()) + raise RuntimeError('Errors detected in mypy output.') if 'Revealed type is ' in line: finfo = line.split(' ')[0] fparts = finfo.split(':') @@ -172,17 +180,22 @@ def static_type_equals(value: Any, statictype: Type) -> bool: if filename not in _statictestfiles: _statictestfiles[filename] = StaticTestFile(filename) + testfile = _statictestfiles[filename] - wanttype = _statictestfiles[filename].linetypes_wanted[linenumber] - mypytype = _statictestfiles[filename].linetypes_mypy[linenumber] + wanttype = testfile.linetypes_wanted[linenumber] + mypytype = testfile.linetypes_mypy[linenumber] # Do some filtering of Mypy types to simple python ones. # (ie: 'builtins.list[builtins.int*]' -> int) - mypytype = mypytype.replace('builtins.int*', 'int') + mypytype = mypytype.replace('*', '') + mypytype = mypytype.replace('?', '') mypytype = mypytype.replace('builtins.int', 'int') mypytype = mypytype.replace('builtins.list', 'List') mypytype = mypytype.replace('typing.Sequence', 'Sequence') + # temp3.FooClass -> FooClass + mypytype = mypytype.replace(testfile.modulename + '.', '') + if wanttype != mypytype: print(f'Mypy type "{mypytype}" does not match ' f'the desired type "{wanttype}" on line {linenumber}.')