kopia lustrzana https://github.com/micropython/micropython-lib
unittest: Move back to python-stdlib.
In order to make this more suitable for non-unix ports, the discovery functionality is moved to a separate 'extension' module which can be optionally installed. Signed-off-by: Jim Mussared <jim.mussared@gmail.com>pull/537/head
rodzic
cb88a6a554
commit
796a5986cd
|
@ -0,0 +1,7 @@
|
||||||
|
metadata(version="0.1.0")
|
||||||
|
|
||||||
|
require("argparse")
|
||||||
|
require("fnmatch")
|
||||||
|
require("unittest")
|
||||||
|
|
||||||
|
module("unittest_discover.py")
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Module that is used in both test_isolated_1.py and test_isolated_2.py.
|
||||||
|
# The module should be clean reloaded for each.
|
||||||
|
|
||||||
|
state = None
|
|
@ -0,0 +1,8 @@
|
||||||
|
import unittest
|
||||||
|
import isolated
|
||||||
|
|
||||||
|
|
||||||
|
class TestUnittestIsolated1(unittest.TestCase):
|
||||||
|
def test_NotChangedByOtherTest(self):
|
||||||
|
self.assertIsNone(isolated.state)
|
||||||
|
isolated.state = True
|
|
@ -0,0 +1,8 @@
|
||||||
|
import unittest
|
||||||
|
import isolated
|
||||||
|
|
||||||
|
|
||||||
|
class TestUnittestIsolated2(unittest.TestCase):
|
||||||
|
def test_NotChangedByOtherTest(self):
|
||||||
|
self.assertIsNone(isolated.state)
|
||||||
|
isolated.state = True
|
|
@ -0,0 +1,140 @@
|
||||||
|
# Extension for "unittest" that adds the ability to run via "micropython -m unittest".
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from fnmatch import fnmatch
|
||||||
|
from micropython import const
|
||||||
|
|
||||||
|
from unittest import TestRunner, TestResult, TestSuite
|
||||||
|
|
||||||
|
|
||||||
|
# Run a single test in a clean environment.
|
||||||
|
def _run_test_module(runner: TestRunner, module_name: str, *extra_paths: list[str]):
|
||||||
|
module_snapshot = {k: v for k, v in sys.modules.items()}
|
||||||
|
path_snapshot = sys.path[:]
|
||||||
|
try:
|
||||||
|
for path in reversed(extra_paths):
|
||||||
|
if path:
|
||||||
|
sys.path.insert(0, path)
|
||||||
|
|
||||||
|
module = __import__(module_name)
|
||||||
|
suite = TestSuite(module_name)
|
||||||
|
suite._load_module(module)
|
||||||
|
return runner.run(suite)
|
||||||
|
finally:
|
||||||
|
sys.path[:] = path_snapshot
|
||||||
|
sys.modules.clear()
|
||||||
|
sys.modules.update(module_snapshot)
|
||||||
|
|
||||||
|
|
||||||
|
_DIR_TYPE = const(0x4000)
|
||||||
|
|
||||||
|
|
||||||
|
def _run_all_in_dir(runner: TestRunner, path: str, pattern: str, top: str):
|
||||||
|
result = TestResult()
|
||||||
|
for fname, ftype, *_ in os.ilistdir(path):
|
||||||
|
if fname in ("..", "."):
|
||||||
|
continue
|
||||||
|
if ftype == _DIR_TYPE:
|
||||||
|
result += _run_all_in_dir(
|
||||||
|
runner=runner,
|
||||||
|
path="/".join((path, fname)),
|
||||||
|
pattern=pattern,
|
||||||
|
top=top,
|
||||||
|
)
|
||||||
|
if fnmatch(fname, pattern):
|
||||||
|
module_name = fname.rsplit(".", 1)[0]
|
||||||
|
result += _run_test_module(runner, module_name, path, top)
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
# Implements discovery inspired by https://docs.python.org/3/library/unittest.html#test-discovery
|
||||||
|
def _discover(runner: TestRunner):
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
# parser.add_argument(
|
||||||
|
# "-v",
|
||||||
|
# "--verbose",
|
||||||
|
# action="store_true",
|
||||||
|
# help="Verbose output",
|
||||||
|
# )
|
||||||
|
parser.add_argument(
|
||||||
|
"-s",
|
||||||
|
"--start-directory",
|
||||||
|
dest="start",
|
||||||
|
default=".",
|
||||||
|
help="Directory to start discovery",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-p",
|
||||||
|
"--pattern ",
|
||||||
|
dest="pattern",
|
||||||
|
default="test*.py",
|
||||||
|
help="Pattern to match test files",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"-t",
|
||||||
|
"--top-level-directory",
|
||||||
|
dest="top",
|
||||||
|
help="Top level directory of project (defaults to start directory)",
|
||||||
|
)
|
||||||
|
args = parser.parse_args(args=sys.argv[2:])
|
||||||
|
|
||||||
|
path = args.start
|
||||||
|
top = args.top or path
|
||||||
|
|
||||||
|
return _run_all_in_dir(
|
||||||
|
runner=runner,
|
||||||
|
path=path,
|
||||||
|
pattern=args.pattern,
|
||||||
|
top=top,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Use os.path for path handling.
|
||||||
|
PATH_SEP = getattr(os, "sep", "/")
|
||||||
|
|
||||||
|
|
||||||
|
# foo/bar/x.y.z --> foo/bar, x.y
|
||||||
|
def _dirname_filename_no_ext(path):
|
||||||
|
# Workaround: The Windows port currently reports "/" for os.sep
|
||||||
|
# (and MicroPython doesn't have os.altsep). So for now just
|
||||||
|
# always work with os.sep (i.e. "/").
|
||||||
|
path = path.replace("\\", PATH_SEP)
|
||||||
|
|
||||||
|
split = path.rsplit(PATH_SEP, 1)
|
||||||
|
if len(split) == 1:
|
||||||
|
dirname, filename = "", split[0]
|
||||||
|
else:
|
||||||
|
dirname, filename = split
|
||||||
|
return dirname, filename.rsplit(".", 1)[0]
|
||||||
|
|
||||||
|
|
||||||
|
# This is called from unittest when __name__ == "__main__".
|
||||||
|
def discover_main():
|
||||||
|
failures = 0
|
||||||
|
runner = TestRunner()
|
||||||
|
|
||||||
|
if len(sys.argv) == 1 or (
|
||||||
|
len(sys.argv) >= 2
|
||||||
|
and _dirname_filename_no_ext(sys.argv[0])[1] == "unittest"
|
||||||
|
and sys.argv[1] == "discover"
|
||||||
|
):
|
||||||
|
# No args, or `python -m unittest discover ...`.
|
||||||
|
result = _discover(runner)
|
||||||
|
failures += result.failuresNum or result.errorsNum
|
||||||
|
else:
|
||||||
|
for test_spec in sys.argv[1:]:
|
||||||
|
try:
|
||||||
|
os.stat(test_spec)
|
||||||
|
# File exists, strip extension and import with its parent directory in sys.path.
|
||||||
|
dirname, module_name = _dirname_filename_no_ext(test_spec)
|
||||||
|
result = _run_test_module(runner, module_name, dirname)
|
||||||
|
except OSError:
|
||||||
|
# Not a file, treat as named module to import.
|
||||||
|
result = _run_test_module(runner, test_spec)
|
||||||
|
|
||||||
|
failures += result.failuresNum or result.errorsNum
|
||||||
|
|
||||||
|
# Terminate with non zero return code in case of failures.
|
||||||
|
sys.exit(failures)
|
|
@ -0,0 +1,3 @@
|
||||||
|
metadata(version="0.10.0")
|
||||||
|
|
||||||
|
module("unittest.py")
|
|
@ -1,5 +1,4 @@
|
||||||
import unittest
|
import unittest
|
||||||
from test_unittest_isolated import global_context
|
|
||||||
|
|
||||||
|
|
||||||
class TestUnittestAssertions(unittest.TestCase):
|
class TestUnittestAssertions(unittest.TestCase):
|
||||||
|
@ -143,11 +142,6 @@ class TestUnittestAssertions(unittest.TestCase):
|
||||||
else:
|
else:
|
||||||
self.fail("Unexpected success was not detected")
|
self.fail("Unexpected success was not detected")
|
||||||
|
|
||||||
def test_NotChangedByOtherTest(self):
|
|
||||||
global global_context
|
|
||||||
assert global_context is None
|
|
||||||
global_context = True
|
|
||||||
|
|
||||||
def test_subtest_even(self):
|
def test_subtest_even(self):
|
||||||
"""
|
"""
|
||||||
Test that numbers between 0 and 5 are all even.
|
Test that numbers between 0 and 5 are all even.
|
||||||
|
@ -157,24 +151,5 @@ class TestUnittestAssertions(unittest.TestCase):
|
||||||
self.assertEqual(i % 2, 0)
|
self.assertEqual(i % 2, 0)
|
||||||
|
|
||||||
|
|
||||||
class TestUnittestSetup(unittest.TestCase):
|
|
||||||
class_setup_var = 0
|
|
||||||
|
|
||||||
def setUpClass(self):
|
|
||||||
TestUnittestSetup.class_setup_var += 1
|
|
||||||
|
|
||||||
def tearDownClass(self):
|
|
||||||
# Not sure how to actually test this, but we can check (in the test case below)
|
|
||||||
# that it hasn't been run already at least.
|
|
||||||
TestUnittestSetup.class_setup_var = -1
|
|
||||||
|
|
||||||
def testSetUpTearDownClass_1(self):
|
|
||||||
assert TestUnittestSetup.class_setup_var == 1, TestUnittestSetup.class_setup_var
|
|
||||||
|
|
||||||
def testSetUpTearDownClass_2(self):
|
|
||||||
# Test this twice, as if setUpClass() gets run like setUp() it would be run twice
|
|
||||||
assert TestUnittestSetup.class_setup_var == 1, TestUnittestSetup.class_setup_var
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
|
@ -0,0 +1,29 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestWithRunTest(unittest.TestCase):
|
||||||
|
run = False
|
||||||
|
|
||||||
|
def runTest(self):
|
||||||
|
TestWithRunTest.run = True
|
||||||
|
|
||||||
|
def testRunTest(self):
|
||||||
|
self.fail()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def tearDownClass():
|
||||||
|
if not TestWithRunTest.run:
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
|
||||||
|
def test_func():
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@unittest.expectedFailure
|
||||||
|
def test_foo():
|
||||||
|
raise ValueError()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
|
@ -0,0 +1,28 @@
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestUnittestSetup(unittest.TestCase):
|
||||||
|
class_setup_var = 0
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def setUpClass(cls):
|
||||||
|
assert cls is TestUnittestSetup
|
||||||
|
TestUnittestSetup.class_setup_var += 1
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def tearDownClass(cls):
|
||||||
|
assert cls is TestUnittestSetup
|
||||||
|
# Not sure how to actually test this, but we can check (in the test case below)
|
||||||
|
# that it hasn't been run already at least.
|
||||||
|
TestUnittestSetup.class_setup_var = -1
|
||||||
|
|
||||||
|
def testSetUpTearDownClass_1(self):
|
||||||
|
assert TestUnittestSetup.class_setup_var == 1, TestUnittestSetup.class_setup_var
|
||||||
|
|
||||||
|
def testSetUpTearDownClass_2(self):
|
||||||
|
# Test this twice, as if setUpClass() gets run like setUp() it would be run twice
|
||||||
|
assert TestUnittestSetup.class_setup_var == 1, TestUnittestSetup.class_setup_var
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
|
@ -1,22 +1,13 @@
|
||||||
|
import io
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
import uos
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import io
|
|
||||||
import traceback
|
import traceback
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import uio as io
|
|
||||||
|
|
||||||
traceback = None
|
traceback = None
|
||||||
|
|
||||||
|
|
||||||
def _snapshot_modules():
|
|
||||||
return {k: v for k, v in sys.modules.items()}
|
|
||||||
|
|
||||||
|
|
||||||
__modules__ = _snapshot_modules()
|
|
||||||
|
|
||||||
|
|
||||||
class SkipTest(Exception):
|
class SkipTest(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@ -61,7 +52,7 @@ class SubtestContext:
|
||||||
detail = ", ".join(f"{k}={v}" for k, v in self.params.items())
|
detail = ", ".join(f"{k}={v}" for k, v in self.params.items())
|
||||||
test_details += (f" ({detail})",)
|
test_details += (f" ({detail})",)
|
||||||
|
|
||||||
handle_test_exception(test_details, __test_result__, exc_info, False)
|
_handle_test_exception(test_details, __test_result__, exc_info, False)
|
||||||
# Suppress the exception as we've captured it above
|
# Suppress the exception as we've captured it above
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -258,9 +249,17 @@ class TestSuite:
|
||||||
|
|
||||||
def run(self, result):
|
def run(self, result):
|
||||||
for c in self._tests:
|
for c in self._tests:
|
||||||
run_suite(c, result, self.name)
|
_run_suite(c, result, self.name)
|
||||||
return result
|
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:
|
class TestRunner:
|
||||||
def run(self, suite: TestSuite):
|
def run(self, suite: TestSuite):
|
||||||
|
@ -331,7 +330,7 @@ class TestResult:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
def capture_exc(exc, traceback):
|
def _capture_exc(exc, traceback):
|
||||||
buf = io.StringIO()
|
buf = io.StringIO()
|
||||||
if hasattr(sys, "print_exception"):
|
if hasattr(sys, "print_exception"):
|
||||||
sys.print_exception(exc, buf)
|
sys.print_exception(exc, buf)
|
||||||
|
@ -340,12 +339,12 @@ def capture_exc(exc, traceback):
|
||||||
return buf.getvalue()
|
return buf.getvalue()
|
||||||
|
|
||||||
|
|
||||||
def handle_test_exception(
|
def _handle_test_exception(
|
||||||
current_test: tuple, test_result: TestResult, exc_info: tuple, verbose=True
|
current_test: tuple, test_result: TestResult, exc_info: tuple, verbose=True
|
||||||
):
|
):
|
||||||
exc = exc_info[1]
|
exc = exc_info[1]
|
||||||
traceback = exc_info[2]
|
traceback = exc_info[2]
|
||||||
ex_str = capture_exc(exc, traceback)
|
ex_str = _capture_exc(exc, traceback)
|
||||||
if isinstance(exc, AssertionError):
|
if isinstance(exc, AssertionError):
|
||||||
test_result.failuresNum += 1
|
test_result.failuresNum += 1
|
||||||
test_result.failures.append((current_test, ex_str))
|
test_result.failures.append((current_test, ex_str))
|
||||||
|
@ -359,7 +358,7 @@ def handle_test_exception(
|
||||||
test_result._newFailures += 1
|
test_result._newFailures += 1
|
||||||
|
|
||||||
|
|
||||||
def run_suite(c, test_result: TestResult, suite_name=""):
|
def _run_suite(c, test_result: TestResult, suite_name=""):
|
||||||
if isinstance(c, TestSuite):
|
if isinstance(c, TestSuite):
|
||||||
c.run(test_result)
|
c.run(test_result)
|
||||||
return
|
return
|
||||||
|
@ -388,9 +387,7 @@ def run_suite(c, test_result: TestResult, suite_name=""):
|
||||||
try:
|
try:
|
||||||
test_result._newFailures = 0
|
test_result._newFailures = 0
|
||||||
test_result.testsRun += 1
|
test_result.testsRun += 1
|
||||||
test_globals = dict(**globals())
|
test_function()
|
||||||
test_globals["test_function"] = test_function
|
|
||||||
exec("test_function()", test_globals, test_globals)
|
|
||||||
# No exception occurred, test passed
|
# No exception occurred, test passed
|
||||||
if test_result._newFailures:
|
if test_result._newFailures:
|
||||||
print(" FAIL")
|
print(" FAIL")
|
||||||
|
@ -402,7 +399,7 @@ def run_suite(c, test_result: TestResult, suite_name=""):
|
||||||
test_result.skippedNum += 1
|
test_result.skippedNum += 1
|
||||||
test_result.skipped.append((name, c, reason))
|
test_result.skipped.append((name, c, reason))
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
handle_test_exception(
|
_handle_test_exception(
|
||||||
current_test=(name, c), test_result=test_result, exc_info=sys.exc_info()
|
current_test=(name, c), test_result=test_result, exc_info=sys.exc_info()
|
||||||
)
|
)
|
||||||
# Uncomment to investigate failure in detail
|
# Uncomment to investigate failure in detail
|
||||||
|
@ -417,102 +414,59 @@ def run_suite(c, test_result: TestResult, suite_name=""):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
set_up_class()
|
set_up_class()
|
||||||
|
try:
|
||||||
|
if hasattr(o, "runTest"):
|
||||||
|
name = str(o)
|
||||||
|
run_one(o.runTest)
|
||||||
|
return
|
||||||
|
|
||||||
if hasattr(o, "runTest"):
|
for name in dir(o):
|
||||||
name = str(o)
|
if name.startswith("test"):
|
||||||
run_one(o.runTest)
|
m = getattr(o, name)
|
||||||
return
|
if not callable(m):
|
||||||
|
continue
|
||||||
|
run_one(m)
|
||||||
|
|
||||||
for name in dir(o):
|
if callable(o):
|
||||||
if name.startswith("test"):
|
name = o.__name__
|
||||||
m = getattr(o, name)
|
run_one(o)
|
||||||
if not callable(m):
|
finally:
|
||||||
continue
|
tear_down_class()
|
||||||
run_one(m)
|
|
||||||
|
|
||||||
if callable(o):
|
|
||||||
name = o.__name__
|
|
||||||
run_one(o)
|
|
||||||
|
|
||||||
tear_down_class()
|
|
||||||
|
|
||||||
return exceptions
|
return exceptions
|
||||||
|
|
||||||
|
|
||||||
def _test_cases(mod):
|
# This supports either:
|
||||||
for tn in dir(mod):
|
#
|
||||||
c = getattr(mod, tn)
|
# >>> import mytest
|
||||||
if isinstance(c, object) and isinstance(c, type) and issubclass(c, TestCase):
|
# >>> unitttest.main(mytest)
|
||||||
yield c
|
#
|
||||||
elif tn.startswith("test_") and callable(c):
|
# >>> unittest.main("mytest")
|
||||||
yield c
|
#
|
||||||
|
# Or, a script that ends with:
|
||||||
|
# if __name__ == "__main__":
|
||||||
def run_module(runner, module, path, top):
|
# unittest.main()
|
||||||
if not module:
|
# e.g. run via `mpremote run mytest.py`
|
||||||
raise ValueError("Empty module name")
|
|
||||||
|
|
||||||
# Reset the python environment before running test
|
|
||||||
sys.modules.clear()
|
|
||||||
sys.modules.update(__modules__)
|
|
||||||
|
|
||||||
sys_path_initial = sys.path[:]
|
|
||||||
# Add script dir and top dir to import path
|
|
||||||
sys.path.insert(0, str(path))
|
|
||||||
if top:
|
|
||||||
sys.path.insert(1, top)
|
|
||||||
try:
|
|
||||||
suite = TestSuite(module)
|
|
||||||
m = __import__(module) if isinstance(module, str) else module
|
|
||||||
for c in _test_cases(m):
|
|
||||||
suite.addTest(c)
|
|
||||||
result = runner.run(suite)
|
|
||||||
return result
|
|
||||||
|
|
||||||
finally:
|
|
||||||
sys.path[:] = sys_path_initial
|
|
||||||
|
|
||||||
|
|
||||||
def discover(runner: TestRunner):
|
|
||||||
from unittest_discover import discover
|
|
||||||
|
|
||||||
global __modules__
|
|
||||||
__modules__ = _snapshot_modules()
|
|
||||||
return discover(runner=runner)
|
|
||||||
|
|
||||||
|
|
||||||
def main(module="__main__", testRunner=None):
|
def main(module="__main__", testRunner=None):
|
||||||
if testRunner:
|
if testRunner is None:
|
||||||
if isinstance(testRunner, type):
|
testRunner = TestRunner()
|
||||||
runner = testRunner()
|
elif isinstance(testRunner, type):
|
||||||
else:
|
testRunner = testRunner()
|
||||||
runner = testRunner
|
|
||||||
else:
|
|
||||||
runner = TestRunner()
|
|
||||||
|
|
||||||
if len(sys.argv) <= 1:
|
if isinstance(module, str):
|
||||||
result = discover(runner)
|
module = __import__(module)
|
||||||
elif sys.argv[0].split(".")[0] == "unittest" and sys.argv[1] == "discover":
|
suite = TestSuite(module.__name__)
|
||||||
result = discover(runner)
|
suite._load_module(module)
|
||||||
else:
|
return testRunner.run(suite)
|
||||||
for test_spec in sys.argv[1:]:
|
|
||||||
try:
|
|
||||||
uos.stat(test_spec)
|
|
||||||
# test_spec is a local file, run it directly
|
|
||||||
if "/" in test_spec:
|
|
||||||
path, fname = test_spec.rsplit("/", 1)
|
|
||||||
else:
|
|
||||||
path, fname = ".", test_spec
|
|
||||||
modname = fname.rsplit(".", 1)[0]
|
|
||||||
result = run_module(runner, modname, path, None)
|
|
||||||
|
|
||||||
except OSError:
|
|
||||||
# Not a file, treat as import name
|
|
||||||
result = run_module(runner, test_spec, ".", None)
|
|
||||||
|
|
||||||
# Terminate with non zero return code in case of failures
|
|
||||||
sys.exit(result.failuresNum or result.errorsNum)
|
|
||||||
|
|
||||||
|
|
||||||
|
# Support `micropython -m unittest` (only useful if unitest-discover is
|
||||||
|
# installed).
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
try:
|
||||||
|
# If unitest-discover is installed, use the main() provided there.
|
||||||
|
from unittest_discover import discover_main
|
||||||
|
|
||||||
|
discover_main()
|
||||||
|
except ImportError:
|
||||||
|
pass
|
|
@ -1,6 +0,0 @@
|
||||||
metadata(version="0.9.0")
|
|
||||||
|
|
||||||
require("argparse")
|
|
||||||
require("fnmatch")
|
|
||||||
|
|
||||||
module("unittest.py")
|
|
|
@ -1,15 +0,0 @@
|
||||||
import unittest
|
|
||||||
|
|
||||||
|
|
||||||
global_context = None
|
|
||||||
|
|
||||||
|
|
||||||
class TestUnittestIsolated(unittest.TestCase):
|
|
||||||
def test_NotChangedByOtherTest(self):
|
|
||||||
global global_context
|
|
||||||
assert global_context is None
|
|
||||||
global_context = True
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest.main()
|
|
|
@ -1,70 +0,0 @@
|
||||||
import argparse
|
|
||||||
import sys
|
|
||||||
import uos
|
|
||||||
from fnmatch import fnmatch
|
|
||||||
|
|
||||||
from unittest import TestRunner, TestResult, run_module
|
|
||||||
|
|
||||||
|
|
||||||
def discover(runner: TestRunner):
|
|
||||||
"""
|
|
||||||
Implements discover function inspired by https://docs.python.org/3/library/unittest.html#test-discovery
|
|
||||||
"""
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
# parser.add_argument(
|
|
||||||
# "-v",
|
|
||||||
# "--verbose",
|
|
||||||
# action="store_true",
|
|
||||||
# help="Verbose output",
|
|
||||||
# )
|
|
||||||
parser.add_argument(
|
|
||||||
"-s",
|
|
||||||
"--start-directory",
|
|
||||||
dest="start",
|
|
||||||
default=".",
|
|
||||||
help="Directory to start discovery",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-p",
|
|
||||||
"--pattern ",
|
|
||||||
dest="pattern",
|
|
||||||
default="test*.py",
|
|
||||||
help="Pattern to match test files",
|
|
||||||
)
|
|
||||||
parser.add_argument(
|
|
||||||
"-t",
|
|
||||||
"--top-level-directory",
|
|
||||||
dest="top",
|
|
||||||
help="Top level directory of project (defaults to start directory)",
|
|
||||||
)
|
|
||||||
args = parser.parse_args(args=sys.argv[2:])
|
|
||||||
|
|
||||||
path = args.start
|
|
||||||
top = args.top or path
|
|
||||||
|
|
||||||
return run_all_in_dir(
|
|
||||||
runner=runner,
|
|
||||||
path=path,
|
|
||||||
pattern=args.pattern,
|
|
||||||
top=top,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run_all_in_dir(runner: TestRunner, path: str, pattern: str, top: str):
|
|
||||||
DIR_TYPE = 0x4000
|
|
||||||
|
|
||||||
result = TestResult()
|
|
||||||
for fname, type, *_ in uos.ilistdir(path):
|
|
||||||
if fname in ("..", "."):
|
|
||||||
continue
|
|
||||||
if type == DIR_TYPE:
|
|
||||||
result += run_all_in_dir(
|
|
||||||
runner=runner,
|
|
||||||
path="/".join((path, fname)),
|
|
||||||
pattern=pattern,
|
|
||||||
top=top,
|
|
||||||
)
|
|
||||||
if fnmatch(fname, pattern):
|
|
||||||
modname = fname[: fname.rfind(".")]
|
|
||||||
result += run_module(runner, modname, path, top)
|
|
||||||
return result
|
|
Ładowanie…
Reference in New Issue