You've already forked MicroPythonOS
mirror of
https://github.com/m5stack/MicroPythonOS.git
synced 2026-05-20 11:51:27 -07:00
Add source of unittest package
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
This /lib folder contains:
|
||||
|
||||
- mip.install('github:jonnor/micropython-zipfile')
|
||||
- mip.install("aiohttp") # easy websockets
|
||||
- mip.install("collections") # used by aiohttp
|
||||
- mip.install("unittest")
|
||||
|
||||
- https://github.com/micropython/micropython-lib/blob/master/micropython/aiorepl/aiorepl.py version 0.2.2 # for asyncio REPL, allowing await expressions
|
||||
|
||||
@@ -10,3 +9,4 @@ This /lib folder contains:
|
||||
- https://github.com/micropython/micropython-lib/blob/master/python-stdlib/binascii/binascii.py version 2.4.1 # for base64.py
|
||||
- https://github.com/micropython/micropython-lib/blob/master/python-stdlib/logging/logging.py version 0.6.2 # for About app
|
||||
- https://github.com/micropython/micropython-lib/blob/master/python-stdlib/shutil/shutil.py version 0.0.5 # for rmtree()
|
||||
- https://github.com/micropython/micropython-lib/blob/master/python-stdlib/unittest/unittest/__init__.py version 0.10.4 # for testing (also on-device)
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,464 @@
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
|
||||
try:
|
||||
import traceback
|
||||
except ImportError:
|
||||
traceback = None
|
||||
|
||||
|
||||
class SkipTest(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class AssertRaisesContext:
|
||||
def __init__(self, exc):
|
||||
self.expected = exc
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, tb):
|
||||
self.exception = exc_value
|
||||
if exc_type is None:
|
||||
assert False, "%r not raised" % self.expected
|
||||
if issubclass(exc_type, self.expected):
|
||||
# store exception for later retrieval
|
||||
self.exception = exc_value
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# These are used to provide required context to things like subTest
|
||||
__current_test__ = None
|
||||
__test_result__ = None
|
||||
|
||||
|
||||
class SubtestContext:
|
||||
def __init__(self, msg=None, params=None):
|
||||
self.msg = msg
|
||||
self.params = params
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, *exc_info):
|
||||
if exc_info[0] is not None:
|
||||
# Exception raised
|
||||
global __test_result__, __current_test__
|
||||
test_details = __current_test__
|
||||
if self.msg:
|
||||
test_details += (f" [{self.msg}]",)
|
||||
if self.params:
|
||||
detail = ", ".join(f"{k}={v}" for k, v in self.params.items())
|
||||
test_details += (f" ({detail})",)
|
||||
|
||||
_handle_test_exception(test_details, __test_result__, exc_info, False)
|
||||
# Suppress the exception as we've captured it above
|
||||
return True
|
||||
|
||||
|
||||
class NullContext:
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
pass
|
||||
|
||||
|
||||
class TestCase:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def addCleanup(self, func, *args, **kwargs):
|
||||
if not hasattr(self, "_cleanups"):
|
||||
self._cleanups = []
|
||||
self._cleanups.append((func, args, kwargs))
|
||||
|
||||
def doCleanups(self):
|
||||
if hasattr(self, "_cleanups"):
|
||||
while self._cleanups:
|
||||
func, args, kwargs = self._cleanups.pop()
|
||||
func(*args, **kwargs)
|
||||
|
||||
def subTest(self, msg=None, **params):
|
||||
return SubtestContext(msg=msg, params=params)
|
||||
|
||||
def skipTest(self, reason):
|
||||
raise SkipTest(reason)
|
||||
|
||||
def fail(self, msg=""):
|
||||
assert False, msg
|
||||
|
||||
def assertEqual(self, x, y, msg=""):
|
||||
if not msg:
|
||||
msg = "%r vs (expected) %r" % (x, y)
|
||||
assert x == y, msg
|
||||
|
||||
def assertNotEqual(self, x, y, msg=""):
|
||||
if not msg:
|
||||
msg = "%r not expected to be equal %r" % (x, y)
|
||||
assert x != y, msg
|
||||
|
||||
def assertLessEqual(self, x, y, msg=None):
|
||||
if msg is None:
|
||||
msg = "%r is expected to be <= %r" % (x, y)
|
||||
assert x <= y, msg
|
||||
|
||||
def assertGreaterEqual(self, x, y, msg=None):
|
||||
if msg is None:
|
||||
msg = "%r is expected to be >= %r" % (x, y)
|
||||
assert x >= y, msg
|
||||
|
||||
def assertAlmostEqual(self, x, y, places=None, msg="", delta=None):
|
||||
if x == y:
|
||||
return
|
||||
if delta is not None and places is not None:
|
||||
raise TypeError("specify delta or places not both")
|
||||
|
||||
if delta is not None:
|
||||
if abs(x - y) <= delta:
|
||||
return
|
||||
if not msg:
|
||||
msg = "%r != %r within %r delta" % (x, y, delta)
|
||||
else:
|
||||
if places is None:
|
||||
places = 7
|
||||
if round(abs(y - x), places) == 0:
|
||||
return
|
||||
if not msg:
|
||||
msg = "%r != %r within %r places" % (x, y, places)
|
||||
|
||||
assert False, msg
|
||||
|
||||
def assertNotAlmostEqual(self, x, y, places=None, msg="", delta=None):
|
||||
if delta is not None and places is not None:
|
||||
raise TypeError("specify delta or places not both")
|
||||
|
||||
if delta is not None:
|
||||
if not (x == y) and abs(x - y) > delta:
|
||||
return
|
||||
if not msg:
|
||||
msg = "%r == %r within %r delta" % (x, y, delta)
|
||||
else:
|
||||
if places is None:
|
||||
places = 7
|
||||
if not (x == y) and round(abs(y - x), places) != 0:
|
||||
return
|
||||
if not msg:
|
||||
msg = "%r == %r within %r places" % (x, y, places)
|
||||
|
||||
assert False, msg
|
||||
|
||||
def assertIs(self, x, y, msg=""):
|
||||
if not msg:
|
||||
msg = "%r is not %r" % (x, y)
|
||||
assert x is y, msg
|
||||
|
||||
def assertIsNot(self, x, y, msg=""):
|
||||
if not msg:
|
||||
msg = "%r is %r" % (x, y)
|
||||
assert x is not y, msg
|
||||
|
||||
def assertIsNone(self, x, msg=""):
|
||||
if not msg:
|
||||
msg = "%r is not None" % x
|
||||
assert x is None, msg
|
||||
|
||||
def assertIsNotNone(self, x, msg=""):
|
||||
if not msg:
|
||||
msg = "%r is None" % x
|
||||
assert x is not None, msg
|
||||
|
||||
def assertTrue(self, x, msg=""):
|
||||
if not msg:
|
||||
msg = "Expected %r to be True" % x
|
||||
assert x, msg
|
||||
|
||||
def assertFalse(self, x, msg=""):
|
||||
if not msg:
|
||||
msg = "Expected %r to be False" % x
|
||||
assert not x, msg
|
||||
|
||||
def assertIn(self, x, y, msg=""):
|
||||
if not msg:
|
||||
msg = "Expected %r to be in %r" % (x, y)
|
||||
assert x in y, msg
|
||||
|
||||
def assertIsInstance(self, x, y, msg=""):
|
||||
assert isinstance(x, y), msg
|
||||
|
||||
def assertRaises(self, exc, func=None, *args, **kwargs):
|
||||
if func is None:
|
||||
return AssertRaisesContext(exc)
|
||||
|
||||
try:
|
||||
func(*args, **kwargs)
|
||||
except Exception as e:
|
||||
if isinstance(e, exc):
|
||||
return
|
||||
raise e
|
||||
|
||||
assert False, "%r not raised" % exc
|
||||
|
||||
def assertWarns(self, warn):
|
||||
return NullContext()
|
||||
|
||||
|
||||
def skip(msg):
|
||||
def _decor(fun):
|
||||
# We just replace original fun with _inner
|
||||
def _inner(self):
|
||||
raise SkipTest(msg)
|
||||
|
||||
return _inner
|
||||
|
||||
return _decor
|
||||
|
||||
|
||||
def skipIf(cond, msg):
|
||||
if not cond:
|
||||
return lambda x: x
|
||||
return skip(msg)
|
||||
|
||||
|
||||
def skipUnless(cond, msg):
|
||||
if cond:
|
||||
return lambda x: x
|
||||
return skip(msg)
|
||||
|
||||
|
||||
def expectedFailure(test):
|
||||
def test_exp_fail(*args, **kwargs):
|
||||
try:
|
||||
test(*args, **kwargs)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
assert False, "unexpected success"
|
||||
|
||||
return test_exp_fail
|
||||
|
||||
|
||||
class TestSuite:
|
||||
def __init__(self, name=""):
|
||||
self._tests = []
|
||||
self.name = name
|
||||
|
||||
def addTest(self, cls):
|
||||
self._tests.append(cls)
|
||||
|
||||
def run(self, result):
|
||||
for c in self._tests:
|
||||
_run_suite(c, result, self.name)
|
||||
return result
|
||||
|
||||
def _load_module(self, mod):
|
||||
for tn in dir(mod):
|
||||
c = getattr(mod, tn)
|
||||
if isinstance(c, object) and isinstance(c, type) and issubclass(c, TestCase):
|
||||
self.addTest(c)
|
||||
elif tn.startswith("test") and callable(c):
|
||||
self.addTest(c)
|
||||
|
||||
|
||||
class TestRunner:
|
||||
def run(self, suite: TestSuite):
|
||||
res = TestResult()
|
||||
suite.run(res)
|
||||
|
||||
res.printErrors()
|
||||
print("----------------------------------------------------------------------")
|
||||
print("Ran %d tests\n" % res.testsRun)
|
||||
if res.failuresNum > 0 or res.errorsNum > 0:
|
||||
print("FAILED (failures=%d, errors=%d)" % (res.failuresNum, res.errorsNum))
|
||||
else:
|
||||
msg = "OK"
|
||||
if res.skippedNum > 0:
|
||||
msg += " (skipped=%d)" % res.skippedNum
|
||||
print(msg)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
TextTestRunner = TestRunner
|
||||
|
||||
|
||||
class TestResult:
|
||||
def __init__(self):
|
||||
self.errorsNum = 0
|
||||
self.failuresNum = 0
|
||||
self.skippedNum = 0
|
||||
self.testsRun = 0
|
||||
self.errors = []
|
||||
self.failures = []
|
||||
self.skipped = []
|
||||
self._newFailures = 0
|
||||
|
||||
def wasSuccessful(self):
|
||||
return self.errorsNum == 0 and self.failuresNum == 0
|
||||
|
||||
def printErrors(self):
|
||||
if self.errors or self.failures:
|
||||
print()
|
||||
self.printErrorList(self.errors)
|
||||
self.printErrorList(self.failures)
|
||||
|
||||
def printErrorList(self, lst):
|
||||
sep = "----------------------------------------------------------------------"
|
||||
for c, e in lst:
|
||||
detail = " ".join((str(i) for i in c))
|
||||
print("======================================================================")
|
||||
print(f"FAIL: {detail}")
|
||||
print(sep)
|
||||
print(e)
|
||||
|
||||
def __repr__(self):
|
||||
# Format is compatible with CPython.
|
||||
return "<unittest.result.TestResult run=%d errors=%d failures=%d>" % (
|
||||
self.testsRun,
|
||||
self.errorsNum,
|
||||
self.failuresNum,
|
||||
)
|
||||
|
||||
def __add__(self, other):
|
||||
self.errorsNum += other.errorsNum
|
||||
self.failuresNum += other.failuresNum
|
||||
self.skippedNum += other.skippedNum
|
||||
self.testsRun += other.testsRun
|
||||
self.errors.extend(other.errors)
|
||||
self.failures.extend(other.failures)
|
||||
self.skipped.extend(other.skipped)
|
||||
return self
|
||||
|
||||
|
||||
def _capture_exc(exc, exc_traceback):
|
||||
buf = io.StringIO()
|
||||
if hasattr(sys, "print_exception"):
|
||||
sys.print_exception(exc, buf)
|
||||
elif traceback is not None:
|
||||
traceback.print_exception(None, exc, exc_traceback, file=buf)
|
||||
return buf.getvalue()
|
||||
|
||||
|
||||
def _handle_test_exception(
|
||||
current_test: tuple, test_result: TestResult, exc_info: tuple, verbose=True
|
||||
):
|
||||
exc = exc_info[1]
|
||||
traceback = exc_info[2]
|
||||
ex_str = _capture_exc(exc, traceback)
|
||||
if isinstance(exc, SkipTest):
|
||||
reason = exc.args[0]
|
||||
test_result.skippedNum += 1
|
||||
test_result.skipped.append((current_test, reason))
|
||||
print(" skipped:", reason)
|
||||
return
|
||||
elif isinstance(exc, AssertionError):
|
||||
test_result.failuresNum += 1
|
||||
test_result.failures.append((current_test, ex_str))
|
||||
if verbose:
|
||||
print(" FAIL")
|
||||
else:
|
||||
test_result.errorsNum += 1
|
||||
test_result.errors.append((current_test, ex_str))
|
||||
if verbose:
|
||||
print(" ERROR")
|
||||
test_result._newFailures += 1
|
||||
|
||||
|
||||
def _run_suite(c, test_result: TestResult, suite_name=""):
|
||||
if isinstance(c, TestSuite):
|
||||
c.run(test_result)
|
||||
return
|
||||
|
||||
if isinstance(c, type):
|
||||
o = c()
|
||||
else:
|
||||
o = c
|
||||
set_up_class = getattr(o, "setUpClass", lambda: None)
|
||||
tear_down_class = getattr(o, "tearDownClass", lambda: None)
|
||||
set_up = getattr(o, "setUp", lambda: None)
|
||||
tear_down = getattr(o, "tearDown", lambda: None)
|
||||
exceptions = []
|
||||
try:
|
||||
suite_name += "." + c.__qualname__
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
def run_one(test_function):
|
||||
global __test_result__, __current_test__
|
||||
print("%s (%s) ..." % (name, suite_name), end="")
|
||||
set_up()
|
||||
__test_result__ = test_result
|
||||
test_container = f"({suite_name})"
|
||||
__current_test__ = (name, test_container)
|
||||
try:
|
||||
test_result._newFailures = 0
|
||||
test_result.testsRun += 1
|
||||
test_function()
|
||||
# No exception occurred, test passed
|
||||
if test_result._newFailures:
|
||||
print(" FAIL")
|
||||
else:
|
||||
print(" ok")
|
||||
except Exception as ex:
|
||||
_handle_test_exception(
|
||||
current_test=(name, c), test_result=test_result, exc_info=(type(ex), ex, None)
|
||||
)
|
||||
# Uncomment to investigate failure in detail
|
||||
# raise ex
|
||||
finally:
|
||||
__test_result__ = None
|
||||
__current_test__ = None
|
||||
tear_down()
|
||||
try:
|
||||
o.doCleanups()
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
set_up_class()
|
||||
try:
|
||||
if hasattr(o, "runTest"):
|
||||
name = str(o)
|
||||
run_one(o.runTest)
|
||||
return
|
||||
|
||||
for name in dir(o):
|
||||
if name.startswith("test"):
|
||||
m = getattr(o, name)
|
||||
if not callable(m):
|
||||
continue
|
||||
run_one(m)
|
||||
|
||||
if callable(o):
|
||||
name = o.__name__
|
||||
run_one(o)
|
||||
finally:
|
||||
tear_down_class()
|
||||
|
||||
return exceptions
|
||||
|
||||
|
||||
# This supports either:
|
||||
#
|
||||
# >>> import mytest
|
||||
# >>> unitttest.main(mytest)
|
||||
#
|
||||
# >>> unittest.main("mytest")
|
||||
#
|
||||
# Or, a script that ends with:
|
||||
# if __name__ == "__main__":
|
||||
# unittest.main()
|
||||
# e.g. run via `mpremote run mytest.py`
|
||||
def main(module="__main__", testRunner=None):
|
||||
if testRunner is None:
|
||||
testRunner = TestRunner()
|
||||
elif isinstance(testRunner, type):
|
||||
testRunner = testRunner()
|
||||
|
||||
if isinstance(module, str):
|
||||
module = __import__(module)
|
||||
suite = TestSuite(module.__name__)
|
||||
suite._load_module(module)
|
||||
return testRunner.run(suite)
|
||||
Reference in New Issue
Block a user