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 typing import TYPE_CHECKING
711
12+ import pythoncom
13+ import pywintypes
814import winerror
15+ from pythoncom import _GetGatewayCount , _GetInterfaceCount
16+ from win32com .shell .shell import IsUserAnAdmin
17+
18+ if TYPE_CHECKING :
19+ from _typeshed import OptExcInfo
920
1021##
1122## unittest related stuff
@@ -37,10 +48,7 @@ def countTestCases(self):
3748 return self .num_test_cases
3849
3950 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-
51+ # For the COM suite's sake, always ensure we don't leak gateways/interfaces
4452 gc .collect ()
4553 ni = _GetInterfaceCount ()
4654 ng = _GetGatewayCount ()
@@ -145,20 +153,17 @@ def loadTestsFromName(self, name, module=None):
145153
146154# win32 error codes that probably mean we need to be elevated (ie, if we
147155# aren't elevated, we treat these error codes as 'skipped')
148- non_admin_error_codes = [
156+ non_admin_error_codes = {
149157 winerror .ERROR_ACCESS_DENIED ,
150158 winerror .ERROR_PRIVILEGE_NOT_HELD ,
151- ]
159+ }
152160
153161_is_admin = None
154162
155163
156164def check_is_admin ():
157165 global _is_admin
158166 if _is_admin is None :
159- import pythoncom
160- from win32com .shell .shell import IsUserAnAdmin
161-
162167 try :
163168 _is_admin = IsUserAnAdmin ()
164169 except pythoncom .com_error as exc :
@@ -197,30 +202,19 @@ def find_test_fixture(basename, extra_dir="."):
197202 d = os .path .normcase (d )
198203 if os .path .commonprefix ([this_file , d ]) == d :
199204 # looks like we are in an installed Python, so skip the text.
200- raise TestSkipped (f"Can't find test fixture '{ fname } '" )
205+ raise unittest . SkipTest (f"Can't find test fixture '{ fname } '" )
201206 # Looks like we are running from source, so this is fatal.
202207 raise RuntimeError (f"Can't find test fixture '{ fname } '" )
203208
204209
205- # If this exception is raised by a test, the test is reported as a 'skip'
206- class TestSkipped (Exception ):
207- pass
208-
209-
210210# The 'TestResult' subclass that records the failures and has the special
211- # handling for the TestSkipped exception.
211+ # handling for the unittest.SkipTest exception.
212212class TestResult (unittest .TextTestResult ):
213- def __init__ (self , * args , ** kw ):
214- super ().__init__ (* args , ** kw )
215- self .skips = {} # count of skips for each reason.
213+ def addError (self , test : unittest .TestCase , err : OptExcInfo ) -> None :
214+ """Called when an error has occurred.
216215
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().
216+ Translate a couple of 'well-known' exceptions into 'skipped'
220217 """
221- # translate a couple of 'well-known' exceptions into 'skipped'
222- import pywintypes
223-
224218 exc_val = err [1 ]
225219 # translate ERROR_ACCESS_DENIED for non-admin users to be skipped.
226220 # (access denied errors for an admin user aren't expected.)
@@ -229,42 +223,28 @@ def addError(self, test, err):
229223 and exc_val .winerror in non_admin_error_codes
230224 and not check_is_admin ()
231225 ):
232- exc_val = TestSkipped (exc_val )
226+ return self . addSkip ( test , str (exc_val ) )
233227 # and COM errors due to objects not being registered (the com test
234228 # suite will attempt to catch this and handle it itself if the user
235229 # is admin)
236- elif isinstance (exc_val , pywintypes .com_error ) and exc_val .hresult in [
230+ elif isinstance (exc_val , pywintypes .com_error ) and exc_val .hresult in {
237231 winerror .CO_E_CLASSSTRING ,
238232 winerror .REGDB_E_CLASSNOTREG ,
239233 winerror .TYPE_E_LIBNOTREGISTERED ,
240- ]:
241- exc_val = TestSkipped (exc_val )
242- # NotImplemented generally means the platform doesn't support the
243- # functionality.
234+ }:
235+ return self .addSkip (test , str (exc_val ))
236+ # NotImplemented generally means the platform doesn't support the functionality.
244237 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
238+ return self .addSkip (test , str (exc_val ))
239+
262240 super ().addError (test , err )
263241
264- def printErrors (self ):
242+ def printErrors (self ) -> None :
265243 super ().printErrors ()
266- for reason , num_skipped in self .skips .items ():
267- self .stream .writeln ("SKIPPED: %d tests - %s" % (num_skipped , reason ))
244+ reasons = [reason for (test , reason ) in self .skipped ]
245+ reason_counts = [(reason , reasons .count (reason )) for reason in set (reasons )]
246+ for reason , num_skipped in reason_counts :
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