зеркало из https://github.com/mozilla/FlightDeck.git
332 строки
11 KiB
Python
332 строки
11 KiB
Python
|
import os
|
||
|
import sys
|
||
|
import time
|
||
|
import tempfile
|
||
|
import atexit
|
||
|
import shutil
|
||
|
|
||
|
import simplejson as json
|
||
|
import mozrunner
|
||
|
from cuddlefish.prefs import DEFAULT_FIREFOX_PREFS
|
||
|
from cuddlefish.prefs import DEFAULT_THUNDERBIRD_PREFS
|
||
|
|
||
|
def install_xpts(harness_root_dir, xpts):
|
||
|
"""
|
||
|
Temporarily 'installs' all given XPCOM typelib files into the
|
||
|
harness components directory.
|
||
|
|
||
|
This is needed because there doesn't seem to be any way to
|
||
|
install typelibs during the runtime of a XULRunner app.
|
||
|
"""
|
||
|
|
||
|
my_components_dir = os.path.join(harness_root_dir, 'components')
|
||
|
installed_xpts = []
|
||
|
for abspath in xpts:
|
||
|
target = os.path.join(my_components_dir,
|
||
|
os.path.basename(abspath))
|
||
|
shutil.copyfile(abspath, target)
|
||
|
installed_xpts.append(target)
|
||
|
|
||
|
@atexit.register
|
||
|
def cleanup_installed_xpts():
|
||
|
for path in installed_xpts:
|
||
|
os.remove(path)
|
||
|
|
||
|
def follow_file(filename):
|
||
|
"""
|
||
|
Generator that yields the latest unread content from the given
|
||
|
file, or None if no new content is available.
|
||
|
|
||
|
For example:
|
||
|
|
||
|
>>> f = open('temp.txt', 'w')
|
||
|
>>> f.write('hello')
|
||
|
>>> f.flush()
|
||
|
>>> tail = follow_file('temp.txt')
|
||
|
>>> tail.next()
|
||
|
'hello'
|
||
|
>>> tail.next() is None
|
||
|
True
|
||
|
>>> f.write('there')
|
||
|
>>> f.flush()
|
||
|
>>> tail.next()
|
||
|
'there'
|
||
|
>>> f.close()
|
||
|
>>> os.remove('temp.txt')
|
||
|
"""
|
||
|
|
||
|
last_pos = 0
|
||
|
last_size = 0
|
||
|
while True:
|
||
|
newstuff = None
|
||
|
if os.path.exists(filename):
|
||
|
size = os.stat(filename).st_size
|
||
|
if size > last_size:
|
||
|
last_size = size
|
||
|
f = open(filename, 'r')
|
||
|
f.seek(last_pos)
|
||
|
newstuff = f.read()
|
||
|
last_pos = f.tell()
|
||
|
f.close()
|
||
|
yield newstuff
|
||
|
|
||
|
class FennecProfile(mozrunner.Profile):
|
||
|
preferences = {}
|
||
|
|
||
|
names = ['fennec']
|
||
|
|
||
|
class FennecRunner(mozrunner.Runner):
|
||
|
profile_class = FennecProfile
|
||
|
|
||
|
names = ['fennec']
|
||
|
|
||
|
__DARWIN_PATH = '/Applications/Fennec.app/Contents/MacOS/fennec'
|
||
|
|
||
|
def __init__(self, binary=None, **kwargs):
|
||
|
if sys.platform == 'darwin' and binary and binary.endswith('.app'):
|
||
|
# Assume it's a Fennec app dir.
|
||
|
binary = os.path.join(binary, 'Contents/MacOS/fennec')
|
||
|
|
||
|
self.__real_binary = binary
|
||
|
|
||
|
mozrunner.Runner.__init__(self, **kwargs)
|
||
|
|
||
|
def find_binary(self):
|
||
|
if not self.__real_binary:
|
||
|
if sys.platform == 'darwin':
|
||
|
if os.path.exists(self.__DARWIN_PATH):
|
||
|
return self.__DARWIN_PATH
|
||
|
self.__real_binary = mozrunner.Runner.find_binary(self)
|
||
|
return self.__real_binary
|
||
|
|
||
|
class XulrunnerAppProfile(mozrunner.Profile):
|
||
|
preferences = {'browser.dom.window.dump.enabled': True,
|
||
|
'javascript.options.strict': True}
|
||
|
|
||
|
names = []
|
||
|
|
||
|
class XulrunnerAppRunner(mozrunner.Runner):
|
||
|
"""
|
||
|
Runner for any XULRunner app. Can use a Firefox binary in XULRunner
|
||
|
mode to execute the app, or can use XULRunner itself. Expects the
|
||
|
app's application.ini to be passed in as one of the items in
|
||
|
'cmdargs' in the constructor.
|
||
|
|
||
|
This class relies a lot on the particulars of mozrunner.Runner's
|
||
|
implementation, and does some unfortunate acrobatics to get around
|
||
|
some of the class' limitations/assumptions.
|
||
|
"""
|
||
|
|
||
|
profile_class = XulrunnerAppProfile
|
||
|
|
||
|
# This is a default, and will be overridden in the instance if
|
||
|
# Firefox is used in XULRunner mode.
|
||
|
names = ['xulrunner']
|
||
|
|
||
|
# Default location of XULRunner on OS X.
|
||
|
__DARWIN_PATH = "/Library/Frameworks/XUL.framework/xulrunner-bin"
|
||
|
__LINUX_PATH = "/usr/bin/xulrunner"
|
||
|
|
||
|
# What our application.ini's path looks like if it's part of
|
||
|
# an "installed" XULRunner app on OS X.
|
||
|
__DARWIN_APP_INI_SUFFIX = '.app/Contents/Resources/application.ini'
|
||
|
|
||
|
def __init__(self, binary=None, **kwargs):
|
||
|
if sys.platform == 'darwin' and binary and binary.endswith('.app'):
|
||
|
# Assume it's a Firefox app dir.
|
||
|
binary = os.path.join(binary, 'Contents/MacOS/firefox-bin')
|
||
|
|
||
|
self.__app_ini = None
|
||
|
self.__real_binary = binary
|
||
|
|
||
|
mozrunner.Runner.__init__(self, **kwargs)
|
||
|
|
||
|
# See if we're using a genuine xulrunner-bin from the XULRunner SDK,
|
||
|
# or if we're being asked to use Firefox in XULRunner mode.
|
||
|
self.__is_xulrunner_sdk = 'xulrunner' in self.binary
|
||
|
|
||
|
if sys.platform == 'linux2' and not self.env.get('LD_LIBRARY_PATH'):
|
||
|
self.env['LD_LIBRARY_PATH'] = os.path.dirname(self.binary)
|
||
|
|
||
|
newargs = []
|
||
|
for item in self.cmdargs:
|
||
|
if 'application.ini' in item:
|
||
|
self.__app_ini = item
|
||
|
else:
|
||
|
newargs.append(item)
|
||
|
self.cmdargs = newargs
|
||
|
|
||
|
if not self.__app_ini:
|
||
|
raise ValueError('application.ini not found in cmdargs')
|
||
|
if not os.path.exists(self.__app_ini):
|
||
|
raise ValueError("file does not exist: '%s'" % self.__app_ini)
|
||
|
|
||
|
if (sys.platform == 'darwin' and
|
||
|
self.binary == self.__DARWIN_PATH and
|
||
|
self.__app_ini.endswith(self.__DARWIN_APP_INI_SUFFIX)):
|
||
|
# If the application.ini is in an app bundle, then
|
||
|
# it could be inside an "installed" XULRunner app.
|
||
|
# If this is the case, use the app's actual
|
||
|
# binary instead of the XUL framework's, so we get
|
||
|
# a proper app icon, etc.
|
||
|
new_binary = '/'.join(self.__app_ini.split('/')[:-2] +
|
||
|
['MacOS', 'xulrunner'])
|
||
|
if os.path.exists(new_binary):
|
||
|
self.binary = new_binary
|
||
|
|
||
|
@property
|
||
|
def command(self):
|
||
|
"""Returns the command list to run."""
|
||
|
|
||
|
if self.__is_xulrunner_sdk:
|
||
|
return [self.binary, self.__app_ini, '-profile',
|
||
|
self.profile.profile]
|
||
|
else:
|
||
|
return [self.binary, '-app', self.__app_ini, '-profile',
|
||
|
self.profile.profile]
|
||
|
|
||
|
def __find_xulrunner_binary(self):
|
||
|
if sys.platform == 'darwin':
|
||
|
if os.path.exists(self.__DARWIN_PATH):
|
||
|
return self.__DARWIN_PATH
|
||
|
if sys.platform == 'linux2':
|
||
|
if os.path.exists(self.__LINUX_PATH):
|
||
|
return self.__LINUX_PATH
|
||
|
return None
|
||
|
|
||
|
def find_binary(self):
|
||
|
# This gets called by the superclass constructor. It will
|
||
|
# always get called, even if a binary was passed into the
|
||
|
# constructor, because we want to have full control over
|
||
|
# what the exact setting of self.binary is.
|
||
|
|
||
|
if not self.__real_binary:
|
||
|
self.__real_binary = self.__find_xulrunner_binary()
|
||
|
if not self.__real_binary:
|
||
|
dummy_profile = {}
|
||
|
runner = mozrunner.FirefoxRunner(profile=dummy_profile)
|
||
|
self.__real_binary = runner.find_binary()
|
||
|
self.names = runner.names
|
||
|
return self.__real_binary
|
||
|
|
||
|
def run_app(harness_root_dir, harness_options, xpts,
|
||
|
app_type, binary=None, profiledir=None, verbose=False,
|
||
|
timeout=None, logfile=None, addons=None):
|
||
|
if binary:
|
||
|
binary = os.path.expanduser(binary)
|
||
|
|
||
|
if addons is None:
|
||
|
addons = []
|
||
|
else:
|
||
|
addons = list(addons)
|
||
|
|
||
|
cmdargs = []
|
||
|
preferences = {}
|
||
|
|
||
|
if app_type == "xulrunner":
|
||
|
profile_class = XulrunnerAppProfile
|
||
|
runner_class = XulrunnerAppRunner
|
||
|
cmdargs.append(os.path.join(harness_root_dir, 'application.ini'))
|
||
|
else:
|
||
|
addons.append(harness_root_dir)
|
||
|
if app_type == "firefox":
|
||
|
profile_class = mozrunner.FirefoxProfile
|
||
|
preferences = DEFAULT_FIREFOX_PREFS
|
||
|
runner_class = mozrunner.FirefoxRunner
|
||
|
elif app_type == "thunderbird":
|
||
|
profile_class = mozrunner.ThunderbirdProfile
|
||
|
preferences = DEFAULT_THUNDERBIRD_PREFS
|
||
|
runner_class = mozrunner.ThunderbirdRunner
|
||
|
elif app_type == "fennec":
|
||
|
profile_class = FennecProfile
|
||
|
runner_class = FennecRunner
|
||
|
else:
|
||
|
raise ValueError("Unknown app: %s" % app_type)
|
||
|
if sys.platform == 'darwin':
|
||
|
cmdargs.append('-foreground')
|
||
|
|
||
|
resultfile = os.path.join(tempfile.gettempdir(), 'harness_result')
|
||
|
if os.path.exists(resultfile):
|
||
|
os.remove(resultfile)
|
||
|
harness_options['resultFile'] = resultfile
|
||
|
|
||
|
def maybe_remove_logfile():
|
||
|
if os.path.exists(logfile):
|
||
|
os.remove(logfile)
|
||
|
|
||
|
logfile_tail = None
|
||
|
|
||
|
if sys.platform in ['win32', 'cygwin']:
|
||
|
if not logfile:
|
||
|
# If we're on Windows, we need to keep a logfile simply
|
||
|
# to print console output to stdout.
|
||
|
logfile = os.path.join(tempfile.gettempdir(), 'harness_log')
|
||
|
logfile_tail = follow_file(logfile)
|
||
|
atexit.register(maybe_remove_logfile)
|
||
|
|
||
|
if logfile:
|
||
|
logfile = os.path.abspath(os.path.expanduser(logfile))
|
||
|
maybe_remove_logfile()
|
||
|
harness_options['logFile'] = logfile
|
||
|
|
||
|
install_xpts(harness_root_dir, xpts)
|
||
|
|
||
|
env = {}
|
||
|
env.update(os.environ)
|
||
|
env['MOZ_NO_REMOTE'] = '1'
|
||
|
env['HARNESS_OPTIONS'] = json.dumps(harness_options)
|
||
|
|
||
|
starttime = time.time()
|
||
|
|
||
|
popen_kwargs = {}
|
||
|
profile = None
|
||
|
|
||
|
profile = profile_class(addons=addons,
|
||
|
profile=profiledir,
|
||
|
preferences=preferences)
|
||
|
runner = runner_class(profile=profile,
|
||
|
binary=binary,
|
||
|
env=env,
|
||
|
cmdargs=cmdargs,
|
||
|
kp_kwargs=popen_kwargs)
|
||
|
|
||
|
print "Using binary at '%s'." % runner.binary
|
||
|
print "Using profile at '%s'." % profile.profile
|
||
|
|
||
|
runner.start()
|
||
|
|
||
|
done = False
|
||
|
output = None
|
||
|
try:
|
||
|
while not done:
|
||
|
time.sleep(0.05)
|
||
|
if logfile_tail:
|
||
|
new_chars = logfile_tail.next()
|
||
|
if new_chars:
|
||
|
sys.stdout.write(new_chars)
|
||
|
sys.stdout.flush()
|
||
|
if os.path.exists(resultfile):
|
||
|
output = open(resultfile).read()
|
||
|
if output in ['OK', 'FAIL']:
|
||
|
done = True
|
||
|
if timeout and (time.time() - starttime > timeout):
|
||
|
raise Exception("Wait timeout exceeded (%ds)" %
|
||
|
timeout)
|
||
|
except:
|
||
|
runner.stop()
|
||
|
raise
|
||
|
else:
|
||
|
runner.wait(10)
|
||
|
finally:
|
||
|
if profile:
|
||
|
profile.cleanup()
|
||
|
|
||
|
print "Total time: %f seconds" % (time.time() - starttime)
|
||
|
|
||
|
if output == 'OK':
|
||
|
print "Program terminated successfully."
|
||
|
return 0
|
||
|
else:
|
||
|
print "Program terminated unsuccessfully."
|
||
|
return -1
|