diff --git a/com/win32com/test/testall.py b/com/win32com/test/testall.py index 991dbdf6c8..f68b8f787f 100644 --- a/com/win32com/test/testall.py +++ b/com/win32com/test/testall.py @@ -1,28 +1,30 @@ +from __future__ import annotations + import getopt import os import re +import shutil import sys import traceback import unittest - -try: - this_file = __file__ -except NameError: - this_file = sys.argv[0] - -win32com_src_dir = os.path.abspath(os.path.join(this_file, "../..")) +from collections.abc import Callable +from textwrap import dedent +from types import ModuleType +from typing import NoReturn import win32com +this_folder = os.path.dirname(__file__) +win32com_src_dir = os.path.abspath(os.path.join(this_folder, "..")) # We'd prefer the win32com namespace to be the parent of __file__ - ie, our source-tree, # rather than the version installed - otherwise every .py change needs a full install to # test! # We can't patch win32comext as most of them have a .pyd in their root :( -# This clearly ins't ideal or perfect :) +# This clearly isn't ideal or perfect :) win32com.__path__[0] = win32com_src_dir import pythoncom -import win32com.client +import win32com.client.gencache from pywin32_testutil import TestLoader, TestRunner from win32com.test.util import ( CapturingFunctionTestCase, @@ -35,32 +37,17 @@ verbosity = 1 # default unittest verbosity. -def GenerateAndRunOldStyle(): - from . import GenTestScripts - - GenTestScripts.GenerateAll() - try: - pass # - finally: - GenTestScripts.CleanAll() - - -def CleanGenerated(): - import shutil - - import win32com - +def CleanGenerated() -> None: if os.path.isdir(win32com.__gen_path__): if verbosity > 1: - print("Deleting files from %s" % (win32com.__gen_path__)) + print(f"Deleting files from {win32com.__gen_path__}") shutil.rmtree(win32com.__gen_path__) - import win32com.client.gencache win32com.client.gencache.__init__() # Reset -def RemoveRefCountOutput(data): - while 1: +def RemoveRefCountOutput(data: str) -> str: + while True: last_line_pos = data.rfind("\n") if not re.match(r"\[\d+ refs\]", data[last_line_pos + 1 :]): break @@ -72,13 +59,13 @@ def RemoveRefCountOutput(data): return data -def ExecuteSilentlyIfOK(cmd, testcase): +def ExecuteSilentlyIfOK(cmd: str, testcase: TestCase) -> str: f = os.popen(cmd) data = f.read().strip() rc = f.close() if rc: print(data) - testcase.fail("Executing '%s' failed (%d)" % (cmd, rc)) + testcase.fail(f"Executing '{cmd}' failed ({rc})") # for "_d" builds, strip the '[xxx refs]' line return RemoveRefCountOutput(data) @@ -86,33 +73,31 @@ def ExecuteSilentlyIfOK(cmd, testcase): class PyCOMTest(TestCase): no_leak_tests = True # done by the test itself - def testit(self): + def testit(self) -> None: # Check that the item is registered, so we get the correct # 'skipped' behaviour (and recorded as such) rather than either # error or silence due to non-registration. RegisterPythonServer( - os.path.join( - os.path.dirname(__file__), "..", "servers", "test_pycomtest.py" - ), + os.path.join(win32com_src_dir, "servers", "test_pycomtest.py"), "Python.Test.PyCOMTest", ) # Execute testPyComTest in its own process so it can play # with the Python thread state - fname = os.path.join(os.path.dirname(this_file), "testPyComTest.py") + fname = os.path.join(this_folder, "testPyComTest.py") cmd = f'{sys.executable} "{fname}" -q 2>&1' data = ExecuteSilentlyIfOK(cmd, self) class PippoTest(TestCase): - def testit(self): + def testit(self) -> None: # Check we are registered before spawning the process. from win32com.test import pippo_server RegisterPythonServer(pippo_server.__file__, "Python.Test.Pippo") python = sys.executable - fname = os.path.join(os.path.dirname(this_file), "testPippo.py") + fname = os.path.join(this_folder, "testPippo.py") cmd = f'{python} "{fname}" 2>&1' ExecuteSilentlyIfOK(cmd, self) @@ -121,35 +106,44 @@ def testit(self): # function in that module if the module isn't unitest based... unittest_modules = [ # Level 1 tests - fast and few dependencies - good for CI! - """testIterators testvbscript_regexp testStorage - testStreams testWMI policySemantics testShell testROT - testxslt testCollections - errorSemantics.test testArrays - testClipboard - testConversionErrors - """.split(), + [ + "testIterators", + "testvbscript_regexp", + "testStorage", + "testStreams", + "testWMI", + "policySemantics", + "testShell", + "testROT", + "testxslt", + "testCollections", + "errorSemantics.test", + "testArrays", + "testClipboard", + "testConversionErrors", + ], # Level 2 tests - wants our demo COM objects registered. # (these are strange; on github CI they get further than expected when # our objects are not installed, so fail to quietly fail with "can't # register" like they do locally. So really just a nod to CI) - """ - testAXScript testDictionary testServers testvb testMarshal - """.split(), + ["testAXScript", "testDictionary", "testServers", "testvb", "testMarshal"], # Level 3 tests - Requires Office or other non-free stuff. - """testMSOffice.TestAll testMSOfficeEvents.test testAccess.test - testExplorer.TestAll testExchange.test - """.split(), + [ + "testMSOffice.TestAll", + "testMSOfficeEvents.test", + "testAccess.test", + "testExplorer.TestAll", + "testExchange.test", + ], # Level 4 tests - we try and run `makepy` over every typelib installed! - """testmakepy.TestAll - """.split(), + ["testmakepy.TestAll"], ] # A list of other unittest modules we use - these are fully qualified module # names and the module is assumed to be unittest based. unittest_other_modules = [ # Level 1 tests. - """win32com.directsound.test.ds_test - """.split(), + ["win32com.directsound.test.ds_test"], # Level 2 tests. [], # Level 3 tests. @@ -192,7 +186,9 @@ def testit(self): ] -def get_test_mod_and_func(test_name, import_failures): +def get_test_mod_and_func( + test_name: str, import_failures: list[tuple[str, BaseException]] +) -> tuple[None, None] | tuple[ModuleType, Callable[[], object] | None]: if test_name.find(".") > 0: mod_name, func_name = test_name.split(".") else: @@ -202,19 +198,21 @@ def get_test_mod_and_func(test_name, import_failures): try: __import__(fq_mod_name) mod = sys.modules[fq_mod_name] - except: - import_failures.append((mod_name, sys.exc_info()[:2])) + except Exception as error: + import_failures.append((mod_name, error)) return None, None func = None if func_name is None else getattr(mod, func_name) return mod, func # Return a test suite all loaded with the tests we want to run -def make_test_suite(test_level=1): +def make_test_suite( + test_level: int, +) -> tuple[unittest.TestSuite, list[tuple[str, BaseException]]]: suite = unittest.TestSuite() - import_failures = [] + import_failures: list[tuple[str, BaseException]] = [] loader = TestLoader() - for i in range(testLevel): + for i in range(test_level): for mod_name in unittest_modules[i]: mod, func = get_test_mod_and_func(mod_name, import_failures) if mod is None: @@ -234,12 +232,12 @@ def make_test_suite(test_level=1): for test_class in custom_test_cases[i]: suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(test_class)) # other "normal" unittest modules. - for i in range(testLevel): + for i in range(test_level): for mod_name in unittest_other_modules[i]: try: __import__(mod_name) - except: - import_failures.append((mod_name, sys.exc_info()[:2])) + except Exception as error: + import_failures.append((mod_name, error)) continue mod = sys.modules[mod_name] @@ -253,13 +251,15 @@ def make_test_suite(test_level=1): return suite, import_failures -def usage(why): - print(why) - print() - print("win32com test suite") - print("usage: testall [-v] test_level") - print(" where test_level is an integer 1-3. Level 1 tests are quick,") - print(" level 2 tests invoke Word, IE etc, level 3 take ages!") +def usage(why: str) -> NoReturn: + print(f"""\ +{why} + +win32com test suite +usage: testall [-v] test_level + where test_level is an integer 1-4. Level 1 tests are quick, + level 2 tests invoke Word, IE etc, level 3 take ages! + level 4 we try and run `makepy` over every typelib installed""") sys.exit(1) @@ -271,44 +271,47 @@ def usage(why): for opt, val in opts: if opt == "-v": verbosity += 1 - testLevel = 2 # default to quick test with local objects - test_names = [] - for arg in args: - try: - testLevel = int(arg) - if testLevel < 0 or testLevel > 4: - raise ValueError("Only levels 1-4 are supported") - except ValueError: - test_names.append(arg) - if test_names: + + if len(args) > 1: + usage( + "Only a single test level argument is supported. " + + "Test names are not supported yet" + ) + try: + test_level = int(args[0]) + except IndexError: + test_level = 2 # default to quick test with local objects + except ValueError: usage("Test names are not supported yet") + if test_level < 1 or test_level > 4: + usage("Only levels 1-4 are supported") CleanGenerated() - suite, import_failures = make_test_suite(testLevel) + suite, import_failures = make_test_suite(test_level) if verbosity: if hasattr(sys, "gettotalrefcount"): - print("This is a debug build - memory leak tests will also be run.") - print("These tests may take *many* minutes to run - be patient!") - print("(running from python.exe will avoid these leak tests)") + print( + dedent("""\ + This is a debug build - memory leak tests will also be run. + These tests may take *many* minutes to run - be patient! + (running from python.exe will avoid these leak tests""") + ) print( - "Executing level %d tests - %d test cases will be run" - % (testLevel, suite.countTestCases()) + f"Executing level {test_level} tests " + + f"- {suite.countTestCases()} test cases will be run" ) if verbosity == 1 and suite.countTestCases() < 70: # A little row of markers so the dots show how close to finished print("|" * suite.countTestCases()) - testRunner = TestRunner(verbosity=verbosity) - testResult = testRunner.run(suite) + testResult = TestRunner(verbosity=verbosity).run(suite) if import_failures: testResult.stream.writeln( "*** The following test modules could not be imported ***" ) - for mod_name, (exc_type, exc_val) in import_failures: - desc = "\n".join(traceback.format_exception_only(exc_type, exc_val)) + for mod_name, error in import_failures: + desc = "\n".join(traceback.format_exception_only(type(error), error)) testResult.stream.write(f"{mod_name}: {desc}") - testResult.stream.writeln( - "*** %d test(s) could not be run ***" % len(import_failures) - ) + testResult.stream.writeln(f"*** {import_failures} test(s) could not be run ***") # re-print unit-test error here so it is noticed if not testResult.wasSuccessful():