diff --git a/validate/launcher/Makefile.am b/validate/launcher/Makefile.am index 1a4c61d6e9..b4fd617658 100644 --- a/validate/launcher/Makefile.am +++ b/validate/launcher/Makefile.am @@ -12,6 +12,7 @@ launcher_PYTHON = \ httpserver.py \ RangeHTTPServer.py \ utils.py \ + vfb_server.py \ config.py clean-local: diff --git a/validate/launcher/main.py b/validate/launcher/main.py index 61f47df7ac..0ac2f3c3ff 100644 --- a/validate/launcher/main.py +++ b/validate/launcher/main.py @@ -29,6 +29,7 @@ import subprocess from loggable import Loggable from httpserver import HTTPServer +from vfb_server import get_virual_frame_buffer_server from baseclasses import _TestsLauncher, ScenarioManager from utils import printc, path2url, DEFAULT_MAIN_DIR, launch_command, Colors, Protocols, which @@ -189,6 +190,7 @@ class LauncherConfig(Loggable): self.long_limit = utils.LONG_TEST self.config = None self.valgrind = False + self.no_display = False self.xunit_file = None self.main_dir = utils.DEFAULT_MAIN_DIR self.output_dir = None @@ -396,6 +398,13 @@ Note that all testsuite should be inside python modules, so the directory should parser.add_argument("-vg", "--valgrind", dest="valgrind", action="store_true", help="Run the tests inside Valgrind") + parser.add_argument("-nd", "--no-display", dest="no_display", + action="store_true", + help="Run the tests without outputing graphics" + " on any display. It tries to run all graphical operation" + " in a virtual framebuffer." + " Note that it is currently implemented only" + " for the X server thanks to Xvfb (which is requeried in that case)") dir_group = parser.add_argument_group( "Directories and files to be used by the launcher") parser.add_argument('--xunit-file', action='store', @@ -492,6 +501,15 @@ Note that all testsuite should be inside python modules, so the directory should if tests_launcher.needs_http_server() or options.httponly is True: httpsrv.start() + vfb_server = get_virual_frame_buffer_server(options) + if options.no_display: + res = vfb_server.start() + if res[0] is False: + printc("Could not start virtual frame server: %s" % res[1], + Colors.FAIL) + exit(1) + os.environ["DISPLAY"] = vfb_server.display_id + if options.httponly is True: print "Running HTTP server only" return @@ -504,6 +522,7 @@ Note that all testsuite should be inside python modules, so the directory should finally: tests_launcher.final_report() httpsrv.stop() + vfb_server.stop() if e is not None: raise diff --git a/validate/launcher/vfb_server.py b/validate/launcher/vfb_server.py new file mode 100644 index 0000000000..50d364812b --- /dev/null +++ b/validate/launcher/vfb_server.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python2 +# +# Copyright (c) 2015,Thibault Saunier +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this program; if not, write to the +# Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, +# Boston, MA 02110-1301, USA. + +import os +import time +import loggable +import subprocess + + +class VirtualFrameBufferServer(loggable.Loggable): + def __init__(self, options): + loggable.Loggable.__init__(self) + self.options = options + + def start(self): + raise NotImplementedError + + def stop(self): + raise NotImplementedError + + +class Xvfb(VirtualFrameBufferServer): + + """ Class to run xvfb in a process.""" + + def __init__(self, options): + VirtualFrameBufferServer.__init__(self, options) + self.display_id = ":27" + self._process = None + self._logsfile = None + self._command = "Xvfb %s -screen 0 1024x768x24" % self.display_id + + def _check_is_up(self, timeout=60): + """ Check if the xvfb is up, running a simple test based on wget. """ + start = time.time() + while True: + try: + cdisplay = os.environ.get("DISPLAY", None) + os.environ["DISPLAY"] = self.display_id + subprocess.check_output(["xset", "q"], + stderr=self._logsfile) + print("DISPLAY set to %s" % self.display_id) + return True + except subprocess.CalledProcessError: + os.environ["DISPLAY"] = cdisplay + pass + + if time.time() - start > timeout: + return False + + time.sleep(1) + + def start(self): + """ Start xvfb in a subprocess """ + self._logsfile = open(os.path.join(self.options.logsdir, + "xvfb.logs"), 'w+') + if self._check_is_up(timeout=2): + print("xvfb already running") + return (True, None) + + print("Starting xvfb") + try: + self.debug("Launching xvfb: %s (logs in %s)", self._command, self._logsfile) + self._process = subprocess.Popen(self._command.split(" "), + stderr=self._logsfile, + stdout=self._logsfile) + self.debug("Launched xvfb") + + # Dirty way to avoid eating to much CPU... + # good enough for us anyway. + time.sleep(1) + + if self._check_is_up(): + print("Xvfb tarted") + return (True, None) + else: + print("Failed starting xvfb") + self._process.terminate() + self._process = None + except Exception as ex: + return (False, "Could not launch %s %s\n" + "Make sure Xvbf is installed" % (self._command, ex)) + + def stop(self): + """ Stop the xvfb subprocess if running. """ + if self._process: + self._process.terminate() + self._process = None + self.debug("xvfb stoped") + + +def get_virual_frame_buffer_server(options): + """ + Return a VirtualFrameBufferServer + """ + return Xvfb(options)