@@ -378,12 +378,16 @@ def forget(modname):
378378 unlink (importlib .util .cache_from_source (source , debug_override = True ))
379379 unlink (importlib .util .cache_from_source (source , debug_override = False ))
380380
381- # On some platforms, should not run gui test even if it is allowed
382- # in `use_resources'.
383- if sys .platform .startswith ('win' ):
384- import ctypes
385- import ctypes .wintypes
386- def _is_gui_available ():
381+ # Check whether a gui is actually available
382+ def _is_gui_available ():
383+ if hasattr (_is_gui_available , 'result' ):
384+ return _is_gui_available .result
385+ reason = None
386+ if sys .platform .startswith ('win' ):
387+ # if Python is running as a service (such as the buildbot service),
388+ # gui interaction may be disallowed
389+ import ctypes
390+ import ctypes .wintypes
387391 UOI_FLAGS = 1
388392 WSF_VISIBLE = 0x0001
389393 class USEROBJECTFLAGS (ctypes .Structure ):
@@ -403,10 +407,49 @@ class USEROBJECTFLAGS(ctypes.Structure):
403407 ctypes .byref (needed ))
404408 if not res :
405409 raise ctypes .WinError ()
406- return bool (uof .dwFlags & WSF_VISIBLE )
407- else :
408- def _is_gui_available ():
409- return True
410+ if not bool (uof .dwFlags & WSF_VISIBLE ):
411+ reason = "gui not available (WSF_VISIBLE flag not set)"
412+ elif sys .platform == 'darwin' :
413+ # The Aqua Tk implementations on OS X can abort the process if
414+ # being called in an environment where a window server connection
415+ # cannot be made, for instance when invoked by a buildbot or ssh
416+ # process not running under the same user id as the current console
417+ # user. To avoid that, raise an exception if the window manager
418+ # connection is not available.
419+ from ctypes import cdll , c_int , pointer , Structure
420+ from ctypes .util import find_library
421+
422+ app_services = cdll .LoadLibrary (find_library ("ApplicationServices" ))
423+
424+ if app_services .CGMainDisplayID () == 0 :
425+ reason = "gui tests cannot run without OS X window manager"
426+ else :
427+ class ProcessSerialNumber (Structure ):
428+ _fields_ = [("highLongOfPSN" , c_int ),
429+ ("lowLongOfPSN" , c_int )]
430+ psn = ProcessSerialNumber ()
431+ psn_p = pointer (psn )
432+ if ( (app_services .GetCurrentProcess (psn_p ) < 0 ) or
433+ (app_services .SetFrontProcess (psn_p ) < 0 ) ):
434+ reason = "cannot run without OS X gui process"
435+
436+ # check on every platform whether tkinter can actually do anything
437+ if not reason :
438+ try :
439+ from tkinter import Tk
440+ root = Tk ()
441+ root .destroy ()
442+ except Exception as e :
443+ err_string = str (e )
444+ if len (err_string ) > 50 :
445+ err_string = err_string [:50 ] + ' [...]'
446+ reason = 'Tk unavailable due to {}: {}' .format (type (e ).__name__ ,
447+ err_string )
448+
449+ _is_gui_available .reason = reason
450+ _is_gui_available .result = not reason
451+
452+ return _is_gui_available .result
410453
411454def is_resource_enabled (resource ):
412455 """Test whether a resource is enabled. Known resources are set by
@@ -421,7 +464,7 @@ def requires(resource, msg=None):
421464 executing.
422465 """
423466 if resource == 'gui' and not _is_gui_available ():
424- raise unittest . SkipTest ( "Cannot use the 'gui' resource" )
467+ raise ResourceDenied ( _is_gui_available . reason )
425468 # see if the caller's module is __main__ - if so, treat as if
426469 # the resource was set
427470 if sys ._getframe (1 ).f_globals .get ("__name__" ) == "__main__" :
@@ -1589,7 +1632,7 @@ def _id(obj):
15891632
15901633def requires_resource (resource ):
15911634 if resource == 'gui' and not _is_gui_available ():
1592- return unittest .skip ("resource 'gui' is not available" )
1635+ return unittest .skip (_is_gui_available . reason )
15931636 if is_resource_enabled (resource ):
15941637 return _id
15951638 else :
0 commit comments