1- # Utilities for the pywin32 tests
1+ """Utilities for the pywin32 tests"""
2+
3+ from __future__ import annotations
4+
25import gc
36import os
47import site
58import sys
69import unittest
10+ from collections import Counter
11+ from typing import TYPE_CHECKING
712
13+ import pythoncom
14+ import pywintypes
815import winerror
16+ from pythoncom import _GetGatewayCount , _GetInterfaceCount
17+ from win32com .shell .shell import IsUserAnAdmin
18+
19+ if TYPE_CHECKING :
20+ from _typeshed import OptExcInfo
921
1022##
1123## unittest related stuff
@@ -37,10 +49,7 @@ def countTestCases(self):
3749 return self .num_test_cases
3850
3951 def __call__ (self , result = None ):
40- # For the COM suite's sake, always ensure we don't leak
41- # gateways/interfaces
42- from pythoncom import _GetGatewayCount , _GetInterfaceCount
43-
52+ # For the COM suite's sake, always ensure we don't leak gateways/interfaces
4453 gc .collect ()
4554 ni = _GetInterfaceCount ()
4655 ng = _GetGatewayCount ()
@@ -145,20 +154,17 @@ def loadTestsFromName(self, name, module=None):
145154
146155# win32 error codes that probably mean we need to be elevated (ie, if we
147156# aren't elevated, we treat these error codes as 'skipped')
148- non_admin_error_codes = [
157+ non_admin_error_codes = {
149158 winerror .ERROR_ACCESS_DENIED ,
150159 winerror .ERROR_PRIVILEGE_NOT_HELD ,
151- ]
160+ }
152161
153- _is_admin = None
162+ _is_admin : bool | None = None
154163
155164
156- def check_is_admin ():
165+ def check_is_admin () -> bool :
157166 global _is_admin
158167 if _is_admin is None :
159- import pythoncom
160- from win32com .shell .shell import IsUserAnAdmin
161-
162168 try :
163169 _is_admin = IsUserAnAdmin ()
164170 except pythoncom .com_error as exc :
@@ -197,30 +203,19 @@ def find_test_fixture(basename, extra_dir="."):
197203 d = os .path .normcase (d )
198204 if os .path .commonprefix ([this_file , d ]) == d :
199205 # looks like we are in an installed Python, so skip the text.
200- raise TestSkipped (f"Can't find test fixture '{ fname } '" )
206+ raise unittest . SkipTest (f"Can't find test fixture '{ fname } '" )
201207 # Looks like we are running from source, so this is fatal.
202208 raise RuntimeError (f"Can't find test fixture '{ fname } '" )
203209
204210
205- # If this exception is raised by a test, the test is reported as a 'skip'
206- class TestSkipped (Exception ):
207- pass
208-
209-
210211# The 'TestResult' subclass that records the failures and has the special
211- # handling for the TestSkipped exception.
212+ # handling for the unittest.SkipTest exception.
212213class TestResult (unittest .TextTestResult ):
213- def __init__ (self , * args , ** kw ):
214- super ().__init__ (* args , ** kw )
215- self .skips = {} # count of skips for each reason.
214+ def addError (self , test : unittest .TestCase , err : OptExcInfo ) -> None :
215+ """Called when an error has occurred.
216216
217- def addError (self , test , err ):
218- """Called when an error has occurred. 'err' is a tuple of values as
219- returned by sys.exc_info().
217+ Translate a couple of 'well-known' exceptions into 'skipped'
220218 """
221- # translate a couple of 'well-known' exceptions into 'skipped'
222- import pywintypes
223-
224219 exc_val = err [1 ]
225220 # translate ERROR_ACCESS_DENIED for non-admin users to be skipped.
226221 # (access denied errors for an admin user aren't expected.)
@@ -229,42 +224,27 @@ def addError(self, test, err):
229224 and exc_val .winerror in non_admin_error_codes
230225 and not check_is_admin ()
231226 ):
232- exc_val = TestSkipped (exc_val )
227+ return self . addSkip ( test , str (exc_val ) )
233228 # and COM errors due to objects not being registered (the com test
234229 # suite will attempt to catch this and handle it itself if the user
235230 # is admin)
236- elif isinstance (exc_val , pywintypes .com_error ) and exc_val .hresult in [
231+ elif isinstance (exc_val , pywintypes .com_error ) and exc_val .hresult in {
237232 winerror .CO_E_CLASSSTRING ,
238233 winerror .REGDB_E_CLASSNOTREG ,
239234 winerror .TYPE_E_LIBNOTREGISTERED ,
240- ]:
241- exc_val = TestSkipped (exc_val )
242- # NotImplemented generally means the platform doesn't support the
243- # functionality.
235+ }:
236+ return self .addSkip (test , str (exc_val ))
237+ # NotImplemented generally means the platform doesn't support the functionality.
244238 elif isinstance (exc_val , NotImplementedError ):
245- exc_val = TestSkipped (NotImplementedError )
246-
247- if isinstance (exc_val , TestSkipped ):
248- reason = exc_val .args [0 ]
249- # if the reason itself is another exception, get its args.
250- try :
251- reason = tuple (reason .args )
252- except (AttributeError , TypeError ):
253- pass
254- self .skips .setdefault (reason , 0 )
255- self .skips [reason ] += 1
256- if self .showAll :
257- self .stream .writeln (f"SKIP ({ reason } )" )
258- elif self .dots :
259- self .stream .write ("S" )
260- self .stream .flush ()
261- return
239+ return self .addSkip (test , str (exc_val ))
240+
262241 super ().addError (test , err )
263242
264- def printErrors (self ):
243+ def printErrors (self ) -> None :
265244 super ().printErrors ()
266- for reason , num_skipped in self .skips .items ():
267- self .stream .writeln ("SKIPPED: %d tests - %s" % (num_skipped , reason ))
245+ reasons = [reason for (_ , reason ) in self .skipped ]
246+ for reason , num_skipped in Counter (reasons ).items ():
247+ self .stream .writeln (f"SKIPPED: { num_skipped } tests - { reason } " )
268248
269249
270250# TestRunner subclass necessary just to get our TestResult hooked up.
0 commit comments