Publish Vulpes Preview 15 sources for desktop, Android and Tundra

Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
OverSu 2026-09-30 23:13:03 +02:00
commit b38dea0597
25901 changed files with 9982925 additions and 369 deletions

View file

@ -0,0 +1,18 @@
Metadata-Version: 1.1
Name: gaia-runner-service
Version: 0.3
Summary: A utility for running tests over a socket from JS to python
Home-page: UNKNOWN
Author: Andrew Halberstadt
Author-email: ahalberstadt@mozilla.com
License: MPL 2.0
Description: See https://github.com/mozilla-b2g/gaia/tree/master/tests/python/runner-service
Keywords: mozilla
Platform: UNKNOWN
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)
Classifier: Natural Language :: English
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python
Classifier: Topic :: Software Development :: Libraries :: Python Modules

View file

@ -0,0 +1,13 @@
setup.py
gaia_runner_service.egg-info/PKG-INFO
gaia_runner_service.egg-info/SOURCES.txt
gaia_runner_service.egg-info/dependency_links.txt
gaia_runner_service.egg-info/entry_points.txt
gaia_runner_service.egg-info/not-zip-safe
gaia_runner_service.egg-info/requires.txt
gaia_runner_service.egg-info/top_level.txt
runner_service/__init__.py
runner_service/listener.py
runner_service/runintegration.py
runner_service/handlers/__init__.py
runner_service/handlers/runner.py

View file

@ -0,0 +1,4 @@
[console_scripts]
gaia-integration=runner_service:runintegration.cli

View file

@ -0,0 +1,3 @@
mozrunner >= 6.11
mozprofile >= 0.21
mozcrash >= 0.16

View file

@ -0,0 +1,5 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import runintegration

View file

@ -0,0 +1,11 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from . import runner
runner_handlers = {
'desktop': runner.DesktopHandler,
'device': runner.DeviceHandler,
'emulator': runner.EmulatorHandler,
}

View file

@ -0,0 +1,207 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from abc import ABCMeta, abstractmethod, abstractproperty
import json
import os
import posixpath
import sys
from mozprofile import Profile
from mozrunner import (
B2GDesktopRunner,
B2GDeviceRunner,
B2GEmulatorRunner
)
# We log over a special pipe (fd3) which node knows to hook up.
GECKO_LOG_FD = 3
class DeviceUtils:
def __init__(self, deviceManager):
self.dm = deviceManager
def _purge_data(self):
# Clean up data on device.
print("cleaning up data on device.")
data_paths = ['/cache/*',
'/data/local/debug_info_trigger',
'/data/local/indexedDB',
'/data/local/OfflineCache',
'/data/local/permissions.sqlite',
'/data/local/storage/persistent',
'/data/local/storage/default']
for path in data_paths:
if self.dm.dirExists(path):
self.dm.removeDir(path)
def _purge_wifi_networks(self):
# Remove remembered networks
print("removing remembered wifi networks.")
self.dm.removeDir('/data/misc/wifi/wpa_supplicant.conf')
def _purge_storage_data(self):
# Clean up storage.
print("cleaning up storage on device.")
# TODO: Remove hard-coded paths once bug 1018079 is resolved
storage_paths = ['/mnt/sdcard/',
'/mnt/extsdcard/',
'/storage/sdcard/',
'/storage/sdcard0/',
'/storage/sdcard1/']
for path in storage_paths:
if self.dm.dirExists(path):
for item in self.dm.listFiles(path):
self.dm.removeDir('/'.join([path, item]))
def _purge_installed_apps(self):
# Clean up apps on device.
apps = json.loads(self.dm.pullFile('/data/local/webapps/webapps.json'))
system_install_time = apps['system.gaiamobile.org']['installTime']
for app in apps.values():
if app.get('installTime') > system_install_time:
# removing any webapps installed since build time
path = posixpath.join(app.get('basePath'), app.get('id'))
print('Removing %s' % path)
self.dm.removeDir(path)
def _stop_b2g(self):
print("stopping b2g on device before starting runner.")
self.dm.shellCheckOutput(['stop', 'b2g'])
class MozrunnerHandler(object):
__metaclass__ = ABCMeta
runner = None
def __init__(self, symbols_path=None, *args, **kwargs):
fd = os.fdopen(GECKO_LOG_FD, 'a')
process_args = {
'stream': fd,
'onFinish': self.on_finish
}
dump_path = kwargs.pop('dump_path', os.path.join(os.getcwd(), "minidumps"))
self.common_runner_args = {
'process_args': process_args,
'symbols_path': symbols_path,
'dump_save_path': dump_path,
'env': {
# Required for crash reporting...
'MOZ_CRASHREPORTER': '1',
'MOZ_CRASHREPORTER_NO_REPORT': '1'
}
}
# Ensure we set the DISPLAY if it is set in the current environment so
# things like Xvfb work correctly...
if 'DISPLAY' in os.environ:
self.common_runner_args['env']['DISPLAY'] = os.environ['DISPLAY']
@abstractmethod
def start_runner(self, target, options):
pass
@abstractmethod
def stop_runner(self):
pass
def on_finish(self):
pass
def cleanup(self):
if self.runner:
self.runner.cleanup()
class DesktopHandler(MozrunnerHandler):
def start_runner(self, binary, options):
profile = Profile(profile=options.get('profile'))
cmdargs = options.get('argv', [])
if 'screen' in options and \
'width' in options['screen'] and \
'height' in options['screen']:
screen = '--screen=%sx%s' % (options['screen']['width'], \
options['screen']['height'])
if 'dpi' in options['screen']:
screen = '%s@%s' % (screen, options['screen']['dpi'])
cmdargs.append(screen)
if options.get('noRemote', True):
cmdargs.append('-no-remote')
if options.get('url'):
cmdargs.append(options['url'])
if options.get('chrome'):
cmdargs.extend(['-chrome', options['chrome']])
if options.get('startDebugger'):
cmdargs.extend(['-start-debugger-server', options['startDebugger']])
self.runner = B2GDesktopRunner(binary, cmdargs=cmdargs, profile=profile,
**self.common_runner_args)
self.runner.start()
def stop_runner(self):
self.runner.stop()
class EmulatorHandler(MozrunnerHandler):
def __init__(self, b2g_home, *args, **kwargs):
MozrunnerHandler.__init__(self, *args, **kwargs)
self.runner = B2GEmulatorRunner(b2g_home=b2g_home, **self.common_runner_args)
self.runner.device.start()
def start_runner(self, binary, options):
port = options.get('port')
profile = Profile(profile=options.get('profile'))
self.runner.device.setup_port_forwarding(local_port=port, remote_port=port)
self.runner.profile = profile
self.runner.start()
if not self.runner.device.wait_for_port(port):
raise Exception("Wait for port timed out")
def stop_runner(self):
self.runner.stop()
class DeviceHandler(MozrunnerHandler):
def __init__(self, *args, **kwargs):
serial = kwargs.pop('serial', None)
MozrunnerHandler.__init__(self, *args, **kwargs)
self.runner = B2GDeviceRunner(serial=serial, **self.common_runner_args)
self.runner.device.connect()
def start_runner(self, target, options):
du = DeviceUtils(self.runner.device.dm)
device = self.runner.device
port = options.get('port')
profile = Profile(profile=options.get('profile'))
device.setup_port_forwarding(local_port=port, remote_port=port)
du._stop_b2g()
du._purge_data()
du._purge_wifi_networks()
du._purge_storage_data()
self.runner.profile = profile
self.runner.start()
if not device.wait_for_port(port=port, timeout=600):
raise Exception("Wait for port timed out")
def stop_runner(self):
self.runner.stop()

View file

@ -0,0 +1,157 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import socket
import sys
import os
import json
import uuid
import BaseHTTPServer
import traceback
import mozcrash
from .handlers import runner_handlers
class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
REQUESTS = {}
INACTIVE = []
def log_message(self, format, *args):
'''
Override log message to bypass certain behaviors which assume tcp
sockets
'''
pass
def log_error(self, format, *args):
'''
Override log error to bypass certain behaviors which assume tcp
sockets
'''
print(args)
def send_json(self, status, payload):
'''
Respond to incoming http request...
:param int status:
:param dict payload: payload to send as json to the socket.
'''
payload = json.dumps(payload)
self.send_response(status)
self.send_header('Content-Length', len(payload))
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(payload)
def do_GET(self):
pass
def do_POST(self):
content_length = self.headers.getheader('content-length', 0)
payload = json.loads(self.rfile.read(int(content_length)))
handler = self.path[1:]
method = 'do_' + handler
# No matter what we send application/json
if hasattr(self, method):
try:
func = getattr(self, method)
result = func(payload)
except Exception as e:
self.send_json(500, {
'message': str(e),
'stack': traceback.format_exc()
})
else:
self.send_json(200, result)
else:
self.send_json(404, { message: 'Unknown path :' + self.path })
def do_test_end(self, data):
print 'TEST-END | %s' % data['test']
def do_test_start(self, data):
print 'TEST-START | %s' % data['test']
def do_test_status(self, data):
print 'TEST-STATUS | %s | %s' % (data['subtest'], data['status'])
def do_connect(self, payload):
'''
Simple connection ready hook to signal socket readiness...
'''
return {}
def do_get_crash_info(self, payload):
# Somewhat terrible (maybe) hack to get CrashInfo private interface.
crash_info = mozcrash.mozcrash.CrashInfo(**payload)
# Return first error or nothing...
for info in crash_info:
return {
'signature': info.signature,
'stackwalk_stdout': info.stackwalk_stdout,
'stackwalk_stderr': info.stackwalk_stderr,
'stackwalk_retcode': info.stackwalk_retcode,
'stackwalk_errors': info.stackwalk_errors
}
return {}
def do_start_runner(self, payload):
'''
Begin a runner
:param str|None binary: Target binary (usually b2g-bin) for desktop.
:param dict options: Options specific to the buildapp type.
'''
binary = payload.get('binary')
options = payload.get('options', {})
handler_args = {
'symbols_path': options.get('symbols_path')
}
if 'b2g_home' in options:
handler_args['b2g_home'] = options['b2g_home']
if options['buildapp'] == 'device' and 'serial' in options:
handler_args['serial'] = options['serial']
if 'dump_path' in options:
handler_args['dump_path'] = options['dump_path']
start_id = str(uuid.uuid4())
handler = runner_handlers[options['buildapp']](**handler_args)
handler.start_runner(binary, options)
print "Started runner with id = {0}".format(start_id)
self.REQUESTS[start_id] = handler
return { 'id': start_id }
def do_stop_runner(self, payload):
print "Stopping runner with id = {0}".format(payload['id'])
payload_id = payload['id']
if not payload_id in self.REQUESTS:
if payload_id in self.INACTIVE:
sys.stderr.write(
'Received request to stop ' +
payload_id +
' but already stopped so nothing to do.\n')
return {}
runner_ids = str.join(
', ',
map(lambda x: str(x), self.REQUESTS.keys() + self.INACTIVE))
msg = 'Cannot stop %s - service has %s' % (payload_id, runner_ids)
raise Exception(msg)
self.INACTIVE.append(payload_id)
handler = self.REQUESTS.pop(payload_id)
handler.stop_runner()
handler.cleanup()
return {}

View file

@ -0,0 +1,50 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from argparse import ArgumentParser
import threading
import sys
import socket
import os
import time
import SocketServer
from .listener import RequestHandler
class Server(SocketServer.ThreadingMixIn, SocketServer.UnixStreamServer):
pass
def cli(args=sys.argv[1:]):
'''
Begin the server note that the majority of arguments will be passed via the
cli but over the unix socket instead...
'''
parser = ArgumentParser()
parser.add_argument('--path',
dest='path',
default='/tmp/marionette_socket_host_worker',
help='Path to bind unix socket to'
)
args = parser.parse_args(args)
# Make sure the socket does not already exist.
try:
# XXX: This is potentially a bad thing maybe we should raise or warn
# here...
os.unlink(args.path)
except OSError:
if os.path.exists(args.path):
raise
server = Server(args.path, RequestHandler)
server.timeout = 300
server_thread = threading.Thread(target=server.serve_forever)
server_thread.deamon = True
server.running = True
server_thread.start()
if __name__ == '__main__':
sys.exit(cli())

View file

@ -0,0 +1,40 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
from setuptools import setup, find_packages
# NOTE: import the following due to linux bug http://bugs.python.org/issue15881
try:
import multiprocessing
except ImportError:
pass
PACKAGE_VERSION = '0.4'
deps = ['mozrunner >= 6.11',
'mozprofile >= 0.21']
setup(name='gaia-runner-service',
version=PACKAGE_VERSION,
description='A utility for running tests over a socket from JS to python',
long_description='See https://github.com/mozilla-b2g/gaia/tree/master/tests/python/runner-service',
classifiers=['Environment :: Console',
'Intended Audience :: Developers',
'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)',
'Natural Language :: English',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Topic :: Software Development :: Libraries :: Python Modules',
],
keywords='mozilla',
author='Andrew Halberstadt',
author_email='ahalberstadt@mozilla.com',
license='MPL 2.0',
packages=find_packages(),
include_package_data=True,
zip_safe=False,
install_requires=deps,
entry_points="""
[console_scripts]
gaia-integration=runner_service:runintegration.cli
""")