micropython-lib/python-stdlib/unittest-discover/unittest/__main__.py

147 wiersze
4.2 KiB
Python

# 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]
def discover_main():
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)
else:
result = TestResult()
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)
res = _run_test_module(runner, module_name, dirname)
except OSError:
# Not a file, treat as named module to import.
res = _run_test_module(runner, test_spec)
result += res
if not result.testsRun:
# If tests are run their results are already printed.
# Ensure an appropriate output is printed if no tests are found.
runner.run(TestSuite())
# Terminate with non zero return code in case of failures.
sys.exit(result.failuresNum + result.errorsNum)
discover_main()