From 021dc0493e9a8b4ca1969830056d4755edddcb7c Mon Sep 17 00:00:00 2001 From: stijn Date: Wed, 7 Sep 2022 12:06:58 +0200 Subject: [PATCH 1/5] unif-ffi/unittest: Reinstate running of module passed to main(). Fix regression introduced by a7b2f631 which broke the typical case of running a test script which has if __name__ == "__main__": unittest.main() --- unix-ffi/unittest/unittest.py | 57 ++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/unix-ffi/unittest/unittest.py b/unix-ffi/unittest/unittest.py index b61686981..743965c1b 100644 --- a/unix-ffi/unittest/unittest.py +++ b/unix-ffi/unittest/unittest.py @@ -448,7 +448,7 @@ def _test_cases(mod): yield c -def run_module(runner, module, path, top): +def run_module(runner, module, *extra_paths): if not module: raise ValueError("Empty module name") @@ -457,13 +457,12 @@ def run_module(runner, module, path, top): 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) + for path in (path for path in reversed(extra_paths) if path): + sys.path.insert(0, str(path)) + try: - suite = TestSuite(module) m = __import__(module) if isinstance(module, str) else module + suite = TestSuite(m.__name__) for c in _test_cases(m): suite.addTest(c) result = runner.run(suite) @@ -490,29 +489,37 @@ def main(module="__main__", testRunner=None): else: runner = TestRunner() - if len(sys.argv) <= 1: - result = discover(runner) - elif sys.argv[0].split(".")[0] == "unittest" and sys.argv[1] == "discover": - result = discover(runner) + if module is not None: + result = run_module(runner, module) else: - 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) + argn = len(sys.argv) + # See https://docs.python.org/3/library/unittest.html#test-discovery: + # python -m unittest is the equivalent of python -m unittest discover. + if not argn or ( + sys.argv[0].split(".")[0] == "unittest" + and (argn == 1 or (argn >= 1 and sys.argv[1] == "discover")) + ): + result = discover(runner) + # Remaining arguments should be test specifiers. + else: + 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) + + except OSError: + # Not a file, treat as import name + result = run_module(runner, test_spec, ".") # Terminate with non zero return code in case of failures sys.exit(result.failuresNum or result.errorsNum) if __name__ == "__main__": - main() + main(None) From 90f140aed446b364f7448e35325b25db44482d30 Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 6 Sep 2022 13:51:02 +0200 Subject: [PATCH 2/5] unif-ffi/unittest: Reinstate correct test function name prefix. Fix regression introduced by 9d9ca3d5. --- unix-ffi/unittest/unittest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/unix-ffi/unittest/unittest.py b/unix-ffi/unittest/unittest.py index 743965c1b..cab1ff292 100644 --- a/unix-ffi/unittest/unittest.py +++ b/unix-ffi/unittest/unittest.py @@ -444,7 +444,7 @@ def _test_cases(mod): c = getattr(mod, tn) if isinstance(c, object) and isinstance(c, type) and issubclass(c, TestCase): yield c - elif tn.startswith("test_") and callable(c): + elif tn.startswith("test") and callable(c): yield c From 5fe2291e4457e3b12691d25cc539027c2470f43e Mon Sep 17 00:00:00 2001 From: stijn Date: Tue, 6 Sep 2022 16:49:08 +0200 Subject: [PATCH 3/5] unif-ffi/unittest: Make path handling compatible with backslashes. --- unix-ffi/unittest/unittest.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/unix-ffi/unittest/unittest.py b/unix-ffi/unittest/unittest.py index cab1ff292..fb2ff2c18 100644 --- a/unix-ffi/unittest/unittest.py +++ b/unix-ffi/unittest/unittest.py @@ -480,6 +480,14 @@ def discover(runner: TestRunner): return discover(runner=runner) +def split_path(path): + path = path.replace("\\", "/") + if "/" in path: + return path.rsplit("/", 1) + else: + return ".", path + + def main(module="__main__", testRunner=None): if testRunner: if isinstance(testRunner, type): @@ -506,10 +514,7 @@ def main(module="__main__", testRunner=None): 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 + path, fname = split_path(test_spec) modname = fname.rsplit(".", 1)[0] result = run_module(runner, modname, path) From 094f3efa28454e5f0837d8c0364a4e3a4373afa1 Mon Sep 17 00:00:00 2001 From: stijn Date: Wed, 7 Sep 2022 12:29:02 +0200 Subject: [PATCH 4/5] unif-ffi/unittest: Force keyword arguments for main(). CPython's unittest.main() function uses a different argument order than what we have now, so to avoid future problems with unclear error messages should we add arguments in the future, force callers to specify exact arguments. --- unix-ffi/unittest/unittest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unix-ffi/unittest/unittest.py b/unix-ffi/unittest/unittest.py index fb2ff2c18..6a0352994 100644 --- a/unix-ffi/unittest/unittest.py +++ b/unix-ffi/unittest/unittest.py @@ -488,7 +488,8 @@ def split_path(path): return ".", path -def main(module="__main__", testRunner=None): +def main(module="__main__", **kwargs): + testRunner = kwargs.get("testRunner", None) if testRunner: if isinstance(testRunner, type): runner = testRunner() From 5d349c44bccfc925b0e1ceffae71cbd3b3de9f40 Mon Sep 17 00:00:00 2001 From: stijn Date: Wed, 7 Sep 2022 15:26:13 +0200 Subject: [PATCH 5/5] unif-ffi/unittest: Make setup/tearDownClass CPython-compatible. These must be class methods decorated, and tearDownClass must be called after runTest() as well. --- unix-ffi/unittest/test_unittest.py | 8 +++++-- unix-ffi/unittest/unittest.py | 34 +++++++++++++++--------------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/unix-ffi/unittest/test_unittest.py b/unix-ffi/unittest/test_unittest.py index 8e108995a..ed5173ac2 100644 --- a/unix-ffi/unittest/test_unittest.py +++ b/unix-ffi/unittest/test_unittest.py @@ -160,10 +160,14 @@ def test_subtest_even(self): class TestUnittestSetup(unittest.TestCase): class_setup_var = 0 - def setUpClass(self): + @classmethod + def setUpClass(cls): + assert cls is TestUnittestSetup TestUnittestSetup.class_setup_var += 1 - def tearDownClass(self): + @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 diff --git a/unix-ffi/unittest/unittest.py b/unix-ffi/unittest/unittest.py index 6a0352994..a19727ecc 100644 --- a/unix-ffi/unittest/unittest.py +++ b/unix-ffi/unittest/unittest.py @@ -418,23 +418,23 @@ def run_one(test_function): set_up_class() - 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) - - tear_down_class() + try: + if hasattr(o, "runTest"): + name = str(o) + run_one(o.runTest) + else: + 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