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}.')