diff --git a/twisted/internet/gtk3reactor.py b/twisted/internet/gtk3reactor.py index d3a5864f2ad..cbf773ffbd8 100644 --- a/twisted/internet/gtk3reactor.py +++ b/twisted/internet/gtk3reactor.py @@ -19,9 +19,22 @@ Then use twisted.internet APIs as usual. """ +import os + from twisted.internet import gireactor from twisted.python import runtime +# Newer versions of gtk3/pygoject raise a RuntimeError, or just break in a +# confusing manner, if the program is not running under X11. We therefore try +# to fail in a more reasonable manner, and check for $DISPLAY as a reasonable +# approximation of availability of X11. This is somewhat over-aggressive, +# since some older versions of gtk3/pygobject do work with missing $DISPLAY, +# but it's too hard to figure out which, so we always require it. +if (runtime.platform.getType() == 'posix' and + not runtime.platform.isMacOSX() and not os.environ.get("DISPLAY")): + raise ImportError( + "Gtk3 requires X11, and no DISPLAY environment variable is set") + class Gtk3Reactor(gireactor.GIReactor): """ diff --git a/twisted/internet/test/test_gireactor.py b/twisted/internet/test/test_gireactor.py index b78cc0dc616..eff076acdb4 100644 --- a/twisted/internet/test/test_gireactor.py +++ b/twisted/internet/test/test_gireactor.py @@ -7,19 +7,33 @@ import sys, os try: - from twisted.internet import gireactor, gtk3reactor - from gi.repository import Gtk, Gio + from twisted.internet import gireactor + from gi.repository import Gio except ImportError: gireactor = None + gtk3reactor = None +else: + # gtk3reactor may be unavailable even if gireactor is available; in + # particular in pygobject 3.4/gtk 3.6, when no X11 DISPLAY is found. + try: + from twisted.internet import gtk3reactor + except ImportError: + gtk3reactor = None + else: + from gi.repository import Gtk from twisted.python.util import sibpath +from twisted.python.runtime import platform from twisted.internet.defer import Deferred from twisted.internet.error import ReactorAlreadyRunning -from twisted.internet import reactor from twisted.internet.protocol import ProcessProtocol from twisted.trial.unittest import TestCase, SkipTest from twisted.internet.test.reactormixins import ReactorBuilder +from twisted.test.test_twisted import SetAsideModule +# Skip all tests if gi is unavailable: +if gireactor is None: + skip = "gtk3/gi not importable" class GApplicationRegistration(ReactorBuilder, TestCase): @@ -31,10 +45,6 @@ class GApplicationRegistration(ReactorBuilder, TestCase): reactor-running infrastructure, but don't need its test-creation functionality. """ - if gireactor is None: - skip = "gtk3/gi not importable" - - def runReactor(self, app, reactor): """ Register the app, run the reactor, make sure app was activated, and @@ -88,6 +98,10 @@ def test_gtkApplicationActivate(self): self.runReactor(app, reactor) + if gtk3reactor is None: + test_gtkApplicationActivate.skip = ( + "Gtk unavailable (may require running with X11 DISPLAY env set)") + def test_portable(self): """ @@ -164,10 +178,6 @@ class PygtkCompatibilityTests(TestCase): possible. """ - if gireactor is None: - skip = "gtk3/gi not importable" - - def test_noCompatibilityLayer(self): """ If no compatiblity layer is present, imports of gobject and friends @@ -190,6 +200,7 @@ def processExited(self, reason): result.callback(self.data) path = sibpath(__file__, "process_gireactornocompat.py") + from twisted.internet import reactor reactor.spawnProcess(Stdout(), sys.executable, [sys.executable, path], env=os.environ) result.addCallback(self.assertEqual, "success") @@ -205,3 +216,29 @@ def test_compatibilityLayer(self): raise SkipTest("This version of gi doesn't include pygtkcompat.") import gobject self.assertTrue(gobject.__name__.startswith("gi.")) + + + +class Gtk3ReactorTests(TestCase): + """ + Tests for L{gtk3reactor}. + """ + + def test_requiresDISPLAY(self): + """ + On X11, L{gtk3reactor} is unimportable if the C{DISPLAY} environment + variable is not set. + """ + display = os.environ.get("DISPLAY", None) + if display is not None: + self.addCleanup(os.environ.__setitem__, "DISPLAY", display) + del os.environ["DISPLAY"] + with SetAsideModule("twisted.internet.gtk3reactor"): + exc = self.assertRaises(ImportError, + __import__, "twisted.internet.gtk3reactor") + self.assertEqual( + exc.args[0], + "Gtk3 requires X11, and no DISPLAY environment variable is set") + + if platform.getType() != "posix" or platform.isMacOSX(): + test_requiresDISPLAY.skip = "This test is only relevant when using X11" diff --git a/twisted/topfiles/6170.bugfix b/twisted/topfiles/6170.bugfix new file mode 100644 index 00000000000..f24bfb3ca01 --- /dev/null +++ b/twisted/topfiles/6170.bugfix @@ -0,0 +1 @@ +twisted.internet.test.test_gireactor no longer fails when using pygobject 3.4 and gtk 3.6 when X11 is unavailable. \ No newline at end of file