From 3d8ce1a11a7347cc94a937719c4bc8df46fb8d14 Mon Sep 17 00:00:00 2001 From: Pale Moon Date: Thu, 1 Sep 2016 13:39:08 +0200 Subject: Base import of Tycho code (warning: huge commit) --- testing/tps/INSTALL.sh | 99 ---------------- testing/tps/README | 59 +++++++--- testing/tps/config/README.txt | 7 -- testing/tps/config/config.json.in | 49 ++++---- testing/tps/create_venv.py | 194 +++++++++++++++++++++++++++++++ testing/tps/pages/page1.html | 22 ++-- testing/tps/pages/page2.html | 22 ++-- testing/tps/pages/page3.html | 22 ++-- testing/tps/pages/page4.html | 22 ++-- testing/tps/pages/page5.html | 22 ++-- testing/tps/setup.py | 25 ++-- testing/tps/tps/__init__.py | 14 +-- testing/tps/tps/cli.py | 130 ++++++++++++--------- testing/tps/tps/firefoxrunner.py | 178 ++++++++++++++--------------- testing/tps/tps/mozhttpd.py | 104 ----------------- testing/tps/tps/phase.py | 16 +-- testing/tps/tps/testrunner.py | 233 ++++++++++++++++++++++---------------- testing/tps/tps/thread.py | 64 ----------- 18 files changed, 640 insertions(+), 642 deletions(-) delete mode 100755 testing/tps/INSTALL.sh delete mode 100644 testing/tps/config/README.txt create mode 100644 testing/tps/create_venv.py delete mode 100755 testing/tps/tps/mozhttpd.py delete mode 100644 testing/tps/tps/thread.py (limited to 'testing/tps') diff --git a/testing/tps/INSTALL.sh b/testing/tps/INSTALL.sh deleted file mode 100755 index 8bfcfbd6d..000000000 --- a/testing/tps/INSTALL.sh +++ /dev/null @@ -1,99 +0,0 @@ -#!/bin/bash -# 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/. - - -# This scripts sets up a virutalenv and installs TPS into it. -# It's probably best to specify a path NOT inside the repo, otherwise -# all the virtualenv files will show up in e.g. hg status. - -# get target directory -if [ ! -z "$1" ] -then - TARGET=$1 -else - echo "Usage: INSTALL.sh /path/to/create/virtualenv [/path/to/python2.6]" - exit 1 -fi - -# decide which python to use -if [ ! -z "$2" ] -then - PYTHON=$2 -else - PYTHON=`which python` -fi -if [ -z "${PYTHON}" ] -then - echo "No python found" - exit 1 -fi - -CWD="`pwd`" - -# create the destination directory -mkdir ${TARGET} - -if [ "$?" -gt 0 ] -then - exit 1 -fi - -if [ "${OS}" = "Windows_NT" ] -then - BIN_NAME=Scripts/activate -else - BIN_NAME=bin/activate -fi - -# Create a virtualenv: -curl https://raw.github.com/jonallengriffin/virtualenv/msys/virtualenv.py | ${PYTHON} - ${TARGET} -cd ${TARGET} -. $BIN_NAME -if [ -z "${VIRTUAL_ENV}" ] -then - echo "virtualenv wasn't installed correctly, aborting" - exit 1 -fi - -# install TPS -cd ${CWD} -python setup.py install - -# clean up files created by setup.py -rm -rf build/ -rm -rf dist/ -rm -rf tps.egg-info/ - -if [ "$?" -gt 0 ] -then - exit 1 -fi - -CONFIG="`find ${VIRTUAL_ENV} -name config.json.in`" -NEWCONFIG=${CONFIG:0:${#CONFIG}-3} - -cd "../../services/sync/tests/tps" -TESTDIR="`pwd`" - -cd "../../tps/extensions" -EXTDIR="`pwd`" - -sed 's|__TESTDIR__|'"${TESTDIR}"'|' "${CONFIG}" | sed 's|__EXTENSIONDIR__|'"${EXTDIR}"'|' > "${NEWCONFIG}" -rm ${CONFIG} - -echo -echo "***********************************************************************" -echo -echo "To run TPS, activate the virtualenv using:" -echo " source ${TARGET}/${BIN_NAME}" -echo "then execute tps using:" -echo " runtps --binary=/path/to/firefox" -echo -echo "See runtps --help for all options" -echo -echo "To change your TPS config, please edit the file: " -echo "${NEWCONFIG}" -echo -echo "***********************************************************************" diff --git a/testing/tps/README b/testing/tps/README index fa2ee2ec2..50280682e 100644 --- a/testing/tps/README +++ b/testing/tps/README @@ -1,17 +1,42 @@ -TPS is a test automation framework for Firefox Sync. See -https://developer.mozilla.org/en/TPS for documentation. - -INSTALLATION: - -TPS requires several packages to operate properly. To install TPS and -required packages, use the INSTALL.sh script, provided: - - ./INSTALL.sh /path/to/create/virtualenv - -This script will create a virtalenv and install TPS into it. TPS can then -be run by activating the virtualenv and executing: - - runtps --binary=/path/to/firefox - - - +TPS is a test automation framework for Firefox Sync. See +https://developer.mozilla.org/en/TPS for documentation. + +Installation +============ + +TPS requires several packages to operate properly. To install TPS and +required packages, use the INSTALL.sh script, provided: + + ./INSTALL.sh /path/to/create/virtualenv + +This script will create a virtalenv and install TPS into it. TPS can then +be run by activating the virtualenv and executing: + + runtps --binary=/path/to/firefox + + +Configuration +============= +To edit the TPS configuration, do not edit config/config.json.in in the tree. +Instead, edit config.json inside your virtualenv; it will be located at +something like: + + (linux): /path/to/virtualenv/lib/python2.6/site-packages/tps-0.2.40-py2.6.egg/tps/config.json + (win): /path/to/virtualenv/Lib/site-packages/tps-0.2.40-py2.6.egg/tps/config.json + + +Setting Up Test Accounts +======================== + +Firefox Accounts +---------------- +To create a test account for using the Firefox Account authentication perform the +following steps: + +1. Go to a URL like http://restmail.net/mail/%account_prefix%@restmail.net +2. Go to https://accounts.firefox.com/signup?service=sync&context=fx_desktop_v1 +3. Sign in with the previous chosen email address and a password +4. Go back to the Restmail URL, reload the page +5. Search for the verification link and open that page + +Now you will be able to use your setup Firefox Account for Sync. diff --git a/testing/tps/config/README.txt b/testing/tps/config/README.txt deleted file mode 100644 index 78b8aae41..000000000 --- a/testing/tps/config/README.txt +++ /dev/null @@ -1,7 +0,0 @@ -To edit the TPS configuration, do not edit config.json.in in the tree. -Instead, edit config.json inside your virtualenv; it will be located at -something like: - - (linux): /path/to/virtualenv/lib/python2.6/site-packages/tps-0.2.40-py2.6.egg/tps/config.json - (win): /path/to/virtualenv/Lib/site-packages/tps-0.2.40-py2.6.egg/tps/config.json - diff --git a/testing/tps/config/config.json.in b/testing/tps/config/config.json.in index a2ae428f2..53ccb016c 100644 --- a/testing/tps/config/config.json.in +++ b/testing/tps/config/config.json.in @@ -1,25 +1,24 @@ -{ - "account": { - "serverURL": "", - "admin-secret": "", - "username": "crossweaveservices@mozilla.com", - "password": "crossweaveservicescrossweaveservices", - "passphrase": "r-jwcbc-zgf42-fjn72-p5vpp-iypmi" - }, - "resultstore": { - "host": "brasstacks.mozilla.com", - "path": "/resultserv/post/" - }, - "email": { - "username": "crossweave@mozilla.com", - "password": "", - "passednotificationlist": ["crossweave@mozilla.com"], - "notificationlist": ["crossweave@mozilla.com"] - }, - "platform": "win32", - "os": "win7", - "es": "localhost:9200", - "testdir": "__TESTDIR__", - "extensiondir": "__EXTENSIONDIR__" -} - +{ + "sync_account": { + "username": "__SYNC_ACCOUNT_USERNAME__", + "password": "__SYNC_ACCOUNT_PASSWORD__", + "passphrase": "__SYNC_ACCOUNT_PASSPHRASE__" + }, + "fx_account": { + "username": "__FX_ACCOUNT_USERNAME__", + "password": "__FX_ACCOUNT_PASSWORD__" + }, + "email": { + "username": "crossweave@mozilla.com", + "password": "", + "passednotificationlist": ["crossweave@mozilla.com"], + "notificationlist": ["crossweave@mozilla.com"] + }, + "auth_type": "fx_account", + "es": "localhost:9200", + "os": "Ubuntu", + "platform": "linux64", + "serverURL": null, + "extensiondir": "__EXTENSIONDIR__", + "testdir": "__TESTDIR__" +} diff --git a/testing/tps/create_venv.py b/testing/tps/create_venv.py new file mode 100644 index 000000000..3d5417f8a --- /dev/null +++ b/testing/tps/create_venv.py @@ -0,0 +1,194 @@ +#!/usr/bin/env python +# 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/. + +""" +This scripts sets up a virtualenv and installs TPS into it. +It's probably best to specify a path NOT inside the repo, otherwise +all the virtualenv files will show up in e.g. hg status. +""" + +import optparse +import os +import shutil +import subprocess +import sys +import urllib2 +import zipfile + + +here = os.path.dirname(os.path.abspath(__file__)) +usage_message = """ +*********************************************************************** + +To run TPS, activate the virtualenv using: + source {TARGET}/{BIN_NAME} + +To change your TPS config, please edit the file: + {TARGET}/config.json + +To execute tps use: + runtps --binary=/path/to/firefox + +See runtps --help for all options + +*********************************************************************** +""" + +# Link to the folder, which contains the zip archives of virtualenv +URL_VIRTUALENV = 'https://codeload.github.com/pypa/virtualenv/zip/' +VERSION_VIRTUALENV = '1.11.6' + + +if sys.platform == 'win32': + bin_name = os.path.join('Scripts', 'activate.bat') + activate_env = os.path.join('Scripts', 'activate_this.py') + python_env = os.path.join('Scripts', 'python.exe') +else: + bin_name = os.path.join('bin', 'activate') + activate_env = os.path.join('bin', 'activate_this.py') + python_env = os.path.join('bin', 'python') + + +def download(url, target): + """Downloads the specified url to the given target.""" + response = urllib2.urlopen(url) + with open(target, 'wb') as f: + f.write(response.read()) + + return target + + +def setup_virtualenv(target, python_bin=None): + script_path = os.path.join(here, 'virtualenv-%s' % VERSION_VIRTUALENV, + 'virtualenv.py') + + print 'Downloading virtualenv %s' % VERSION_VIRTUALENV + zip_path = download(URL_VIRTUALENV + VERSION_VIRTUALENV, + os.path.join(here, 'virtualenv.zip')) + + try: + with zipfile.ZipFile(zip_path, 'r') as f: + f.extractall(here) + + print 'Creating new virtual environment' + cmd_args = [sys.executable, script_path, target] + + if python_bin: + cmd_args.extend(['-p', python_bin]) + + subprocess.check_call(cmd_args) + finally: + try: + os.remove(zip_path) + except OSError: + pass + + shutil.rmtree(os.path.dirname(script_path), ignore_errors=True) + + +def update_configfile(source, target, replacements): + lines = [] + + with open(source) as config: + for line in config: + for source_string, target_string in replacements.iteritems(): + if target_string: + line = line.replace(source_string, target_string) + lines.append(line) + + with open(target, 'w') as config: + for line in lines: + config.write(line) + + +def main(): + parser = optparse.OptionParser('Usage: %prog [options] path_to_venv') + parser.add_option('--password', + type='string', + dest='password', + metavar='FX_ACCOUNT_PASSWORD', + default=None, + help='The Firefox Account password.') + parser.add_option('-p', '--python', + type='string', + dest='python', + metavar='PYTHON_BIN', + default=None, + help='The Python interpreter to use.') + parser.add_option('--sync-passphrase', + type='string', + dest='sync_passphrase', + metavar='SYNC_ACCOUNT_PASSPHRASE', + default=None, + help='The old Firefox Sync account passphrase.') + parser.add_option('--sync-password', + type='string', + dest='sync_password', + metavar='SYNC_ACCOUNT_PASSWORD', + default=None, + help='The old Firefox Sync account password.') + parser.add_option('--sync-username', + type='string', + dest='sync_username', + metavar='SYNC_ACCOUNT_USERNAME', + default=None, + help='The old Firefox Sync account username.') + parser.add_option('--username', + type='string', + dest='username', + metavar='FX_ACCOUNT_USERNAME', + default=None, + help='The Firefox Account username.') + + (options, args) = parser.parse_args(args=None, values=None) + + if len(args) != 1: + parser.error('Path to the environment has to be specified') + target = args[0] + assert(target) + + setup_virtualenv(target, python_bin=options.python) + + # Activate tps environment + tps_env = os.path.join(target, activate_env) + execfile(tps_env, dict(__file__=tps_env)) + + # Install TPS in environment + subprocess.check_call([os.path.join(target, python_env), + os.path.join(here, 'setup.py'), 'install']) + + # Get the path to tests and extensions directory by checking check where + # the tests and extensions directories are located + sync_dir = os.path.abspath(os.path.join(here, '..', '..', 'services', + 'sync')) + if os.path.exists(sync_dir): + testdir = os.path.join(sync_dir, 'tests', 'tps') + extdir = os.path.join(sync_dir, 'tps', 'extensions') + else: + testdir = os.path.join(here, 'tests') + extdir = os.path.join(here, 'extensions') + + update_configfile(os.path.join(here, 'config', 'config.json.in'), + os.path.join(target, 'config.json'), + replacements={ + '__TESTDIR__': testdir.replace('\\','/'), + '__EXTENSIONDIR__': extdir.replace('\\','/'), + '__FX_ACCOUNT_USERNAME__': options.username, + '__FX_ACCOUNT_PASSWORD__': options.password, + '__SYNC_ACCOUNT_USERNAME__': options.sync_username, + '__SYNC_ACCOUNT_PASSWORD__': options.sync_password, + '__SYNC_ACCOUNT_PASSPHRASE__': options.sync_passphrase}) + + if not (options.username and options.password): + print '\nFirefox Account credentials not specified.' + if not (options.sync_username and options.sync_password and options.passphrase): + print '\nFirefox Sync account credentials not specified.' + + # Print the user instructions + print usage_message.format(TARGET=target, + BIN_NAME=bin_name) + +if __name__ == "__main__": + main() diff --git a/testing/tps/pages/page1.html b/testing/tps/pages/page1.html index d5f9b1464..256c11c1c 100644 --- a/testing/tps/pages/page1.html +++ b/testing/tps/pages/page1.html @@ -2,14 +2,14 @@ - 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/. --> - - -Crossweave Test Page 1 - - -

-Crossweave Test Page 1 -

- - - + + +Crossweave Test Page 1 + + +

+Crossweave Test Page 1 +

+ + + diff --git a/testing/tps/pages/page2.html b/testing/tps/pages/page2.html index ee13073ce..62b693ed5 100644 --- a/testing/tps/pages/page2.html +++ b/testing/tps/pages/page2.html @@ -2,14 +2,14 @@ - 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/. --> - - -Crossweave Test Page 2 - - -

-Crossweave Test Page 2 -

- - - + + +Crossweave Test Page 2 + + +

+Crossweave Test Page 2 +

+ + + diff --git a/testing/tps/pages/page3.html b/testing/tps/pages/page3.html index fa78abc0f..2d0a981de 100644 --- a/testing/tps/pages/page3.html +++ b/testing/tps/pages/page3.html @@ -2,14 +2,14 @@ - 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/. --> - - -Crossweave Test Page 3 - - -

-Crossweave Test Page 3 -

- - - + + +Crossweave Test Page 3 + + +

+Crossweave Test Page 3 +

+ + + diff --git a/testing/tps/pages/page4.html b/testing/tps/pages/page4.html index 776fbf2af..b28f59d80 100644 --- a/testing/tps/pages/page4.html +++ b/testing/tps/pages/page4.html @@ -2,14 +2,14 @@ - 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/. --> - - -Crossweave Test Page 4 - - -

-Crossweave Test Page 4 -

- - - + + +Crossweave Test Page 4 + + +

+Crossweave Test Page 4 +

+ + + diff --git a/testing/tps/pages/page5.html b/testing/tps/pages/page5.html index 55bb2653e..ffd4ce48b 100644 --- a/testing/tps/pages/page5.html +++ b/testing/tps/pages/page5.html @@ -2,14 +2,14 @@ - 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/. --> - - -Crossweave Test Page 5 - - -

-Crossweave Test Page 5 -

- - - + + +Crossweave Test Page 5 + + +

+Crossweave Test Page 5 +

+ + + diff --git a/testing/tps/setup.py b/testing/tps/setup.py index b62d78696..0b1246437 100644 --- a/testing/tps/setup.py +++ b/testing/tps/setup.py @@ -2,14 +2,21 @@ # 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 sys from setuptools import setup, find_packages +import sys -version = '0.4' +version = '0.5' -deps = ['mozinfo >= 0.3.3', 'mozprofile >= 0.4', - 'mozprocess >= 0.4', 'mozrunner >= 5.8', 'mozinstall >= 1.4', - 'httplib2 >= 0.7.3'] +deps = ['httplib2 == 0.7.3', + 'mozfile == 1.1', + 'mozhttpd == 0.7', + 'mozinfo == 0.7', + 'mozinstall == 1.10', + 'mozprocess == 0.19', + 'mozprofile == 0.21', + 'mozrunner == 6.0', + 'mozversion == 0.6', + ] # we only support python 2.6+ right now assert sys.version_info[0] == 2 @@ -22,10 +29,10 @@ setup(name='tps', """, classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers keywords='', - author='Jonathan Griffin', - author_email='jgriffin@mozilla.com', - url='http://hg.mozilla.org/services/services-central', - license='MPL', + author='Mozilla Automation and Tools team', + author_email='tools@lists.mozilla.org', + url='https://developer.mozilla.org/en-US/docs/TPS', + license='MPL 2.0', packages=find_packages(exclude=['ez_setup', 'examples', 'tests']), include_package_data=True, zip_safe=False, diff --git a/testing/tps/tps/__init__.py b/testing/tps/tps/__init__.py index e1056f219..4433ee9b8 100644 --- a/testing/tps/tps/__init__.py +++ b/testing/tps/tps/__init__.py @@ -1,8 +1,6 @@ -# 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 firefoxrunner import TPSFirefoxRunner -from testrunner import TPSTestRunner -from mozhttpd import MozHttpd - +# 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 . firefoxrunner import TPSFirefoxRunner +from . testrunner import TPSTestRunner diff --git a/testing/tps/tps/cli.py b/testing/tps/tps/cli.py index 70f185101..2610ddc9f 100644 --- a/testing/tps/tps/cli.py +++ b/testing/tps/tps/cli.py @@ -5,95 +5,119 @@ import json import optparse import os +import re import sys - from threading import RLock from tps import TPSTestRunner + def main(): parser = optparse.OptionParser() - parser.add_option("--mobile", - action = "store_true", dest = "mobile", - default = False, - help = "run with mobile settings") - parser.add_option("--testfile", - action = "store", type = "string", dest = "testfile", - default = '../../services/sync/tests/tps/test_sync.js', - help = "path to the test file to run " - "[default: %default]") - parser.add_option("--logfile", - action = "store", type = "string", dest = "logfile", - default = 'tps.log', - help = "path to the log file [default: %default]") - parser.add_option("--resultfile", - action = "store", type = "string", dest = "resultfile", - default = 'tps_result.json', - help = "path to the result file [default: %default]") - parser.add_option("--binary", - action = "store", type = "string", dest = "binary", - default = None, - help = "path to the Firefox binary, specified either as " - "a local file or a url; if omitted, the PATH " - "will be searched;") - parser.add_option("--configfile", - action = "store", type = "string", dest = "configfile", - default = None, - help = "path to the config file to use " - "[default: %default]") - parser.add_option("--pulsefile", - action = "store", type = "string", dest = "pulsefile", - default = None, - help = "path to file containing a pulse message in " - "json format that you want to inject into the monitor") - parser.add_option("--ignore-unused-engines", + parser.add_option('--binary', + action='store', + type='string', + dest='binary', + default=None, + help='path to the Firefox binary, specified either as ' + 'a local file or a url; if omitted, the PATH ' + 'will be searched;') + parser.add_option('--configfile', + action='store', + type='string', + dest='configfile', + default=None, + help='path to the config file to use default: %default]') + parser.add_option('--debug', + action='store_true', + dest='debug', + default=False, + help='run in debug mode') + parser.add_option('--ignore-unused-engines', default=False, - action="store_true", - dest="ignore_unused_engines", - help="If defined, don't load unused engines in individual tests." - " Has no effect for pulse monitor.") + action='store_true', + dest='ignore_unused_engines', + help='If defined, do not load unused engines in individual tests.' + ' Has no effect for pulse monitor.') + parser.add_option('--logfile', + action='store', + type='string', + dest='logfile', + default='tps.log', + help='path to the log file [default: %default]') + parser.add_option('--mobile', + action='store_true', + dest='mobile', + default=False, + help='run with mobile settings') + parser.add_option('--pulsefile', + action='store', + type='string', + dest='pulsefile', + default=None, + help='path to file containing a pulse message in ' + 'json format that you want to inject into the monitor') + parser.add_option('--resultfile', + action='store', + type='string', + dest='resultfile', + default='tps_result.json', + help='path to the result file [default: %default]') + parser.add_option('--testfile', + action='store', + type='string', + dest='testfile', + default='all_tests.json', + help='path to the test file to run [default: %default]') (options, args) = parser.parse_args() configfile = options.configfile if configfile is None: - if os.environ.get('VIRTUAL_ENV'): - configfile = os.path.join(os.path.dirname(__file__), 'config.json') + virtual_env = os.environ.get('VIRTUAL_ENV') + if virtual_env: + configfile = os.path.join(virtual_env, 'config.json') if configfile is None or not os.access(configfile, os.F_OK): - raise Exception("Unable to find config.json in a VIRTUAL_ENV; you must " - "specify a config file using the --configfile option") + raise Exception('Unable to find config.json in a VIRTUAL_ENV; you must ' + 'specify a config file using the --configfile option') # load the config file f = open(configfile, 'r') configcontent = f.read() f.close() config = json.loads(configcontent) + testfile = os.path.join(config.get('testdir', ''), options.testfile) rlock = RLock() print 'using result file', options.resultfile - extensionDir = config.get("extensiondir") + extensionDir = config.get('extensiondir') if not extensionDir or extensionDir == '__EXTENSIONDIR__': - extensionDir = os.path.join(os.getcwd(), "..", "..", "services", "sync", "tps", "extensions") + extensionDir = os.path.join(os.getcwd(), '..', '..', + 'services', 'sync', 'tps', 'extensions') else: if sys.platform == 'win32': # replace msys-style paths with proper Windows paths - import re m = re.match('^\/\w\/', extensionDir) if m: - extensionDir = "%s:/%s" % (m.group(0)[1:2], extensionDir[3:]) - extensionDir = extensionDir.replace("/", "\\") + extensionDir = '%s:/%s' % (m.group(0)[1:2], extensionDir[3:]) + extensionDir = extensionDir.replace('/', '\\') TPS = TPSTestRunner(extensionDir, - testfile=options.testfile, - logfile=options.logfile, binary=options.binary, config=config, - rlock=rlock, + debug=options.debug, + ignore_unused_engines=options.ignore_unused_engines, + logfile=options.logfile, mobile=options.mobile, resultfile=options.resultfile, - ignore_unused_engines=options.ignore_unused_engines) + rlock=rlock, + testfile=testfile, + ) TPS.run_tests() -if __name__ == "__main__": + if TPS.numfailed > 0 or TPS.numpassed == 0: + sys.exit(1) + +if __name__ == '__main__': main() diff --git a/testing/tps/tps/firefoxrunner.py b/testing/tps/tps/firefoxrunner.py index c307aa7a0..3b7c143d8 100644 --- a/testing/tps/tps/firefoxrunner.py +++ b/testing/tps/tps/firefoxrunner.py @@ -1,94 +1,84 @@ -# 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 copy -import httplib2 -import os -import shutil - -import mozinstall - -from mozprofile import Profile -from mozrunner import FirefoxRunner - -class TPSFirefoxRunner(object): - - PROCESS_TIMEOUT = 240 - - def __init__(self, binary): - if binary is not None and ('http://' in binary or 'ftp://' in binary): - self.url = binary - self.binary = None - else: - self.url = None - self.binary = binary - self.runner = None - self.installdir = None - - def __del__(self): - if self.installdir: - shutil.rmtree(self.installdir, True) - - def download_url(self, url, dest=None): - h = httplib2.Http() - resp, content = h.request(url, "GET") - if dest == None: - dest = os.path.basename(url) - - local = open(dest, 'wb') - local.write(content) - local.close() - return dest - - def download_build(self, installdir='downloadedbuild', appname='firefox'): - self.installdir = os.path.abspath(installdir) - buildName = os.path.basename(self.url) - pathToBuild = os.path.join(os.path.dirname(os.path.abspath(__file__)), - buildName) - - # delete the build if it already exists - if os.access(pathToBuild, os.F_OK): - os.remove(pathToBuild) - - # download the build - print "downloading build" - self.download_url(self.url, pathToBuild) - - # install the build - print "installing %s" % pathToBuild - shutil.rmtree(self.installdir, True) - binary = mozinstall.install(src=pathToBuild, dest=self.installdir) - - # remove the downloaded archive - os.remove(pathToBuild) - - return binary - - def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None): - """Runs the given FirefoxRunner with the given Profile, waits - for completion, then returns the process exit code - """ - if profile is None: - profile = Profile() - self.profile = profile - - if self.binary is None and self.url: - self.binary = self.download_build() - - if self.runner is None: - self.runner = FirefoxRunner(self.profile, binary=self.binary) - - self.runner.profile = self.profile - - if env is not None: - self.runner.env.update(env) - - if args is not None: - self.runner.cmdargs = copy.copy(args) - - self.runner.start() - - status = self.runner.process_handler.waitForFinish(timeout=timeout) - - return status +# 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 copy +import httplib2 +import os + +import mozfile +import mozinstall +from mozprofile import Profile +from mozrunner import FirefoxRunner + + +class TPSFirefoxRunner(object): + + PROCESS_TIMEOUT = 240 + + def __init__(self, binary): + if binary is not None and ('http://' in binary or 'ftp://' in binary): + self.url = binary + self.binary = None + else: + self.url = None + self.binary = binary + + self.installdir = None + + def __del__(self): + if self.installdir: + mozfile.remove(self.installdir, True) + + def download_url(self, url, dest=None): + h = httplib2.Http() + resp, content = h.request(url, 'GET') + if dest == None: + dest = os.path.basename(url) + + local = open(dest, 'wb') + local.write(content) + local.close() + return dest + + def download_build(self, installdir='downloadedbuild', appname='firefox'): + self.installdir = os.path.abspath(installdir) + buildName = os.path.basename(self.url) + pathToBuild = os.path.join(os.path.dirname(os.path.abspath(__file__)), + buildName) + + # delete the build if it already exists + if os.access(pathToBuild, os.F_OK): + os.remove(pathToBuild) + + # download the build + print 'downloading build' + self.download_url(self.url, pathToBuild) + + # install the build + print 'installing %s' % pathToBuild + mozfile.remove(self.installdir, True) + binary = mozinstall.install(src=pathToBuild, dest=self.installdir) + + # remove the downloaded archive + os.remove(pathToBuild) + + return binary + + def run(self, profile=None, timeout=PROCESS_TIMEOUT, env=None, args=None): + """Runs the given FirefoxRunner with the given Profile, waits + for completion, then returns the process exit code + """ + if profile is None: + profile = Profile() + self.profile = profile + + if self.binary is None and self.url: + self.binary = self.download_build() + + runner = FirefoxRunner(profile=self.profile, binary=self.binary, + env=env, cmdargs=args) + + runner.start(timeout=timeout) + return runner.wait() diff --git a/testing/tps/tps/mozhttpd.py b/testing/tps/tps/mozhttpd.py deleted file mode 100755 index 6ec64a3a7..000000000 --- a/testing/tps/tps/mozhttpd.py +++ /dev/null @@ -1,104 +0,0 @@ -#!/usr/bin/python -# -# 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 BaseHTTPServer -import SimpleHTTPServer -import threading -import sys -import os -import urllib -import re -from urlparse import urlparse -from SocketServer import ThreadingMixIn - -DOCROOT = '.' - -class EasyServer(ThreadingMixIn, BaseHTTPServer.HTTPServer): - allow_reuse_address = True - -class MozRequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler): - def translate_path(self, path): - # It appears that the default path is '/' and os.path.join makes the '/' - o = urlparse(path) - - sep = '/' - if sys.platform == 'win32': - sep = '' - - ret = '%s%s' % ( sep, DOCROOT.strip('/') ) - - # Stub out addons.mozilla.org search API, which is used when installing - # add-ons. The version is hard-coded because we want tests to fail when - # the API updates so we can update our stubbed files with the changes. - if o.path.find('/en-US/firefox/api/1.5/search/guid:') == 0: - ids = urllib.unquote(o.path[len('/en-US/firefox/api/1.5/search/guid:'):]) - - if ids.count(',') > 0: - raise Exception('Searching for multiple IDs is not supported.') - - base = ids - at_loc = ids.find('@') - if at_loc > 0: - base = ids[0:at_loc] - - ret += '/%s.xml' % base - - else: - ret += '/%s' % o.path.strip('/') - - return ret - - # I found on my local network that calls to this were timing out - # I believe all of these calls are from log_message - def address_string(self): - return "a.b.c.d" - - # This produces a LOT of noise - def log_message(self, format, *args): - pass - -class MozHttpd(object): - def __init__(self, host="127.0.0.1", port=8888, docroot='.'): - global DOCROOT - self.host = host - self.port = int(port) - DOCROOT = docroot - - def start(self): - self.httpd = EasyServer((self.host, self.port), MozRequestHandler) - self.server = threading.Thread(target=self.httpd.serve_forever) - self.server.setDaemon(True) # don't hang on exit - self.server.start() - #self.testServer() - - #TODO: figure this out - def testServer(self): - fileList = os.listdir(DOCROOT) - filehandle = urllib.urlopen('http://%s:%s' % (self.host, self.port)) - data = filehandle.readlines(); - filehandle.close() - - for line in data: - found = False - # '@' denotes a symlink and we need to ignore it. - webline = re.sub('\<[a-zA-Z0-9\-\_\.\=\"\'\/\\\%\!\@\#\$\^\&\*\(\) ]*\>', '', line.strip('\n')).strip('/').strip().strip('@') - if webline != "": - if webline == "Directory listing for": - found = True - else: - for fileName in fileList: - if fileName == webline: - found = True - - if (found == False): - print "NOT FOUND: " + webline.strip() - - def stop(self): - if self.httpd: - self.httpd.shutdown() - self.httpd.server_close() - - __del__ = stop diff --git a/testing/tps/tps/phase.py b/testing/tps/tps/phase.py index 39e49ebd1..8996020d5 100644 --- a/testing/tps/tps/phase.py +++ b/testing/tps/tps/phase.py @@ -7,7 +7,7 @@ import re class TPSTestPhase(object): lineRe = re.compile( - r"^(.*?)test phase (?P\d+): (?P.*)$") + r'^(.*?)test phase (?P\d+): (?P.*)$') def __init__(self, phase, profile, testname, testpath, logfile, env, firefoxRunner, logfn, ignore_unused_engines=False): @@ -42,7 +42,7 @@ class TPSTestPhase(object): if self.ignore_unused_engines: args.append('--ignore-unused-engines') - self.log("\nlaunching Firefox for phase %s with args %s\n" % + self.log('\nLaunching Firefox for phase %s with args %s\n' % (self.phase, str(args))) self.firefoxRunner.run(env=self.env, args=args, @@ -55,7 +55,7 @@ class TPSTestPhase(object): # skip to the part of the log file that deals with the test we're running if not found_test: - if line.find("Running test %s" % self.testname) > -1: + if line.find('Running test %s' % self.testname) > -1: found_test = True else: continue @@ -63,13 +63,13 @@ class TPSTestPhase(object): # look for the status of the current phase match = self.lineRe.match(line) if match: - if match.group("matchphase") == self.phasenum: - self._status = match.group("matchstatus") + if match.group('matchphase') == self.phasenum: + self._status = match.group('matchstatus') break # set the status to FAIL if there is TPS error - if line.find("CROSSWEAVE ERROR: ") > -1 and not self._status: - self._status = "FAIL" - self.errline = line[line.find("CROSSWEAVE ERROR: ") + len("CROSSWEAVE ERROR: "):] + if line.find('CROSSWEAVE ERROR: ') > -1 and not self._status: + self._status = 'FAIL' + self.errline = line[line.find('CROSSWEAVE ERROR: ') + len('CROSSWEAVE ERROR: '):] f.close() diff --git a/testing/tps/tps/testrunner.py b/testing/tps/tps/testrunner.py index f4efe4930..c41c73e43 100644 --- a/testing/tps/tps/testrunner.py +++ b/testing/tps/tps/testrunner.py @@ -11,11 +11,14 @@ import tempfile import time import traceback +from mozhttpd import MozHttpd +import mozinfo from mozprofile import Profile +import mozversion + +from .firefoxrunner import TPSFirefoxRunner +from .phase import TPSTestPhase -from tps.firefoxrunner import TPSFirefoxRunner -from tps.phase import TPSTestPhase -from tps.mozhttpd import MozHttpd class TempFile(object): """Class for temporary files that delete themselves when garbage-collected. @@ -41,71 +44,103 @@ class TempFile(object): __del__ = cleanup + class TPSTestRunner(object): - default_env = { 'GNOME_DISABLE_CRASH_DIALOG': '1', - 'XRE_NO_WINDOWS_CRASH_DIALOG': '1', - 'MOZ_NO_REMOTE': '1', - 'XPCOM_DEBUG_BREAK': 'warn', - } - default_preferences = { 'app.update.enabled' : False, - 'extensions.getAddons.get.url': 'http://127.0.0.1:4567/en-US/firefox/api/%API_VERSION%/search/guid:%IDS%', - 'extensions.update.enabled' : False, - 'extensions.update.notifyUser' : False, - 'browser.shell.checkDefaultBrowser' : False, - 'browser.tabs.warnOnClose' : False, - 'browser.warnOnQuit': False, - 'browser.sessionstore.resume_from_crash': False, - 'services.sync.addons.ignoreRepositoryChecking': True, - 'services.sync.firstSync': 'notReady', - 'services.sync.lastversion': '1.0', - 'services.sync.log.rootLogger': 'Trace', - 'services.sync.log.logger.engine.addons': 'Trace', - 'services.sync.log.logger.service.main': 'Trace', - 'services.sync.log.logger.engine.bookmarks': 'Trace', - 'services.sync.log.appender.console': 'Trace', - 'services.sync.log.appender.debugLog.enabled': True, - 'toolkit.startup.max_resumed_crashes': -1, - 'browser.dom.window.dump.enabled': True, - # Allow installing extensions dropped into the profile folder - 'extensions.autoDisableScopes': 10, - # Don't open a dialog to show available add-on updates - 'extensions.update.notifyUser' : False, - } + extra_env = { + 'MOZ_CRASHREPORTER_DISABLE': '1', + 'GNOME_DISABLE_CRASH_DIALOG': '1', + 'XRE_NO_WINDOWS_CRASH_DIALOG': '1', + 'MOZ_NO_REMOTE': '1', + 'XPCOM_DEBUG_BREAK': 'warn', + } + + default_preferences = { + 'app.update.enabled': False, + 'browser.dom.window.dump.enabled': True, + 'browser.sessionstore.resume_from_crash': False, + 'browser.shell.checkDefaultBrowser': False, + 'browser.tabs.warnOnClose': False, + 'browser.warnOnQuit': False, + # Allow installing extensions dropped into the profile folder + 'extensions.autoDisableScopes': 10, + 'extensions.getAddons.get.url': 'http://127.0.0.1:4567/addons/api/%IDS%.xml', + 'extensions.update.enabled': False, + # Don't open a dialog to show available add-on updates + 'extensions.update.notifyUser': False, + 'services.sync.addons.ignoreRepositoryChecking': True, + 'services.sync.firstSync': 'notReady', + 'services.sync.lastversion': '1.0', + 'toolkit.startup.max_resumed_crashes': -1, + } + + debug_preferences = { + 'services.sync.log.appender.console': 'Trace', + 'services.sync.log.appender.dump': 'Trace', + 'services.sync.log.appender.file.level': 'Trace', + 'services.sync.log.appender.file.logOnSuccess': True, + 'services.sync.log.rootLogger': 'Trace', + 'services.sync.log.logger.addonutils': 'Trace', + 'services.sync.log.logger.declined': 'Trace', + 'services.sync.log.logger.service.main': 'Trace', + 'services.sync.log.logger.status': 'Trace', + 'services.sync.log.logger.authenticator': 'Trace', + 'services.sync.log.logger.network.resources': 'Trace', + 'services.sync.log.logger.service.jpakeclient': 'Trace', + 'services.sync.log.logger.engine.bookmarks': 'Trace', + 'services.sync.log.logger.engine.clients': 'Trace', + 'services.sync.log.logger.engine.forms': 'Trace', + 'services.sync.log.logger.engine.history': 'Trace', + 'services.sync.log.logger.engine.passwords': 'Trace', + 'services.sync.log.logger.engine.prefs': 'Trace', + 'services.sync.log.logger.engine.tabs': 'Trace', + 'services.sync.log.logger.engine.addons': 'Trace', + 'services.sync.log.logger.engine.apps': 'Trace', + 'services.sync.log.logger.identity': 'Trace', + 'services.sync.log.logger.userapi': 'Trace', + } + syncVerRe = re.compile( - r"Sync version: (?P.*)\n") + r'Sync version: (?P.*)\n') ffVerRe = re.compile( - r"Firefox version: (?P.*)\n") - ffDateRe = re.compile( - r"Firefox builddate: (?P.*)\n") + r'Firefox version: (?P.*)\n') + ffBuildIDRe = re.compile( + r'Firefox buildid: (?P.*)\n') def __init__(self, extensionDir, - testfile="sync.test", - binary=None, config=None, rlock=None, mobile=False, - logfile="tps.log", resultfile="tps_result.json", - ignore_unused_engines=False): + binary=None, + config=None, + debug=False, + ignore_unused_engines=False, + logfile='tps.log', + mobile=False, + rlock=None, + resultfile='tps_result.json', + testfile=None): + self.binary = binary + self.config = config if config else {} + self.debug = debug self.extensions = [] - self.testfile = testfile + self.ignore_unused_engines = ignore_unused_engines self.logfile = os.path.abspath(logfile) + self.mobile = mobile + self.rlock = rlock self.resultfile = resultfile - self.binary = binary - self.ignore_unused_engines = ignore_unused_engines - self.config = config if config else {} - self.repo = None - self.changeset = None + self.testfile = testfile + + self.addonversion = None self.branch = None + self.changeset = None + self.errorlogs = {} + self.extensionDir = extensionDir + self.firefoxRunner = None + self.nightly = False self.numfailed = 0 self.numpassed = 0 - self.nightly = False - self.rlock = rlock - self.mobile = mobile - self.tpsxpi = None - self.firefoxRunner = None - self.extensionDir = extensionDir - self.productversion = None - self.addonversion = None self.postdata = {} - self.errorlogs = {} + self.productversion = None + self.repo = None + self.tpsxpi = None @property def mobile(self): @@ -166,13 +201,7 @@ class TPSTestRunner(object): def run_single_test(self, testdir, testname): testpath = os.path.join(testdir, testname) - self.log("Running test %s\n" % testname) - - # Create a random account suffix that is used when creating test - # accounts on a staging server. - account_suffix = {"account-suffix": ''.join([str(random.randint(0,9)) - for i in range(1,6)])} - self.config['account'].update(account_suffix) + self.log("Running test %s\n" % testname, True) # Read and parse the test file, merge it with the contents of the config # file, and write the combined output to a temporary file. @@ -182,7 +211,7 @@ class TPSTestRunner(object): try: test = json.loads(testcontent) except: - test = json.loads(testcontent[testcontent.find("{"):testcontent.find("}") + 1]) + test = json.loads(testcontent[testcontent.find('{'):testcontent.find('}') + 1]) testcontent += 'var config = %s;\n' % json.dumps(self.config, indent=2) testcontent += 'var seconds_since_epoch = %d;\n' % int(time.time()) @@ -222,9 +251,9 @@ class TPSTestRunner(object): phase.run() # if a failure occurred, dump the entire sync log into the test log - if phase.status != "PASS": + if phase.status != 'PASS': for profile in profiles: - self.log("\nDumping sync log for profile %s\n" % profiles[profile].profile) + self.log('\nDumping sync log for profile %s\n' % profiles[profile].profile) for root, dirs, files in os.walk(os.path.join(profiles[profile].profile, 'weave', 'logs')): for f in files: weavelog = os.path.join(profiles[profile].profile, 'weave', 'logs', f) @@ -246,11 +275,11 @@ class TPSTestRunner(object): f = open(self.logfile) logdata = f.read() match = self.syncVerRe.search(logdata) - sync_version = match.group("syncversion") if match else 'unknown' + sync_version = match.group('syncversion') if match else 'unknown' match = self.ffVerRe.search(logdata) - firefox_version = match.group("ffver") if match else 'unknown' - match = self.ffDateRe.search(logdata) - firefox_builddate = match.group("ffdate") if match else 'unknown' + firefox_version = match.group('ffver') if match else 'unknown' + match = self.ffBuildIDRe.search(logdata) + firefox_buildid = match.group('ffbuildid') if match else 'unknown' f.close() if phase.status == 'PASS': logdata = '' @@ -266,7 +295,7 @@ class TPSTestRunner(object): logstr = "\n%s | %s%s\n" % (result[0], testname, (' | %s' % result[1] if result[1] else '')) try: - repoinfo = self.firefoxRunner.runner.get_repositoryInfo() + repoinfo = mozversion.get_version(self.binary) except: repoinfo = {} apprepo = repoinfo.get('application_repository', '') @@ -280,19 +309,19 @@ class TPSTestRunner(object): tmplogfile.close() self.errorlogs[testname] = tmplogfile - resultdata = ({ "productversion": { "version": firefox_version, - "buildid": firefox_builddate, - "builddate": firefox_builddate[0:8], - "product": "Firefox", - "repository": apprepo, - "changeset": appchangeset, + resultdata = ({ 'productversion': { 'version': firefox_version, + 'buildid': firefox_buildid, + 'builddate': firefox_buildid[0:8], + 'product': 'Firefox', + 'repository': apprepo, + 'changeset': appchangeset, }, - "addonversion": { "version": sync_version, - "product": "Firefox Sync" }, - "name": testname, - "message": result[1], - "state": result[0], - "logdata": logdata + 'addonversion': { 'version': sync_version, + 'product': 'Firefox Sync' }, + 'name': testname, + 'message': result[1], + 'state': result[0], + 'logdata': logdata }) self.log(logstr, True) @@ -303,17 +332,32 @@ class TPSTestRunner(object): return resultdata + def update_preferences(self): + self.preferences = self.default_preferences.copy() + + if self.mobile: + self.preferences.update({'services.sync.client.type' : 'mobile'}) + + # Set a dummy username to force the correct authentication type. For the + # old sync, the username is not allowed to contain a '@'. + dummy = {'fx_account': 'dummy@somewhere', 'sync_account': 'dummy'} + auth_type = self.config.get('auth_type', 'fx_account') + self.preferences.update({'services.sync.username': dummy[auth_type]}) + + if self.debug: + self.preferences.update(self.debug_preferences) + def run_tests(self): # delete the logfile if it already exists if os.access(self.logfile, os.F_OK): os.remove(self.logfile) - # Make a copy of the default env variables and preferences, and update - # them for mobile settings if needed. - self.env = self.default_env.copy() - self.preferences = self.default_preferences.copy() - if self.mobile: - self.preferences.update({'services.sync.client.type' : 'mobile'}) + # Copy the system env variables, and update them for custom settings + self.env = os.environ.copy() + self.env.update(self.extra_env) + + # Update preferences for custom settings + self.update_preferences() # Acquire a lock to make sure no other threads are running tests # at the same time. @@ -366,22 +410,13 @@ class TPSTestRunner(object): def run_test_group(self): self.results = [] - self.extensions = [] - - # set the OS we're running on - os_string = platform.uname()[2] + " " + platform.uname()[3] - if os_string.find("Darwin") > -1: - os_string = "Mac OS X " + platform.mac_ver()[0] - if platform.uname()[0].find("Linux") > -1: - os_string = "Linux " + platform.uname()[5] - if platform.uname()[0].find("Win") > -1: - os_string = "Windows " + platform.uname()[3] # reset number of passed/failed tests self.numpassed = 0 self.numfailed = 0 # build our tps.xpi extension + self.extensions = [] self.extensions.append(os.path.join(self.extensionDir, 'tps')) self.extensions.append(os.path.join(self.extensionDir, "mozmill")) @@ -421,7 +456,7 @@ class TPSTestRunner(object): # generate the postdata we'll use to post the results to the db self.postdata = { 'tests': self.results, - 'os':os_string, + 'os': '%s %sbit' % (mozinfo.version, mozinfo.bits), 'testtype': 'crossweave', 'productversion': self.productversion, 'addonversion': self.addonversion, diff --git a/testing/tps/tps/thread.py b/testing/tps/tps/thread.py deleted file mode 100644 index 24afbd67b..000000000 --- a/testing/tps/tps/thread.py +++ /dev/null @@ -1,64 +0,0 @@ -# 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 threading import Thread - -from testrunner import TPSTestRunner - -class TPSTestThread(Thread): - - def __init__(self, extensionDir, builddata=None, - testfile=None, logfile=None, rlock=None, config=None): - assert(builddata) - assert(config) - self.extensionDir = extensionDir - self.builddata = builddata - self.testfile = testfile - self.logfile = logfile - self.rlock = rlock - self.config = config - Thread.__init__(self) - - def run(self): - # run the tests in normal mode ... - TPS = TPSTestRunner(self.extensionDir, - testfile=self.testfile, - logfile=self.logfile, - binary=self.builddata['buildurl'], - config=self.config, - rlock=self.rlock, - mobile=False) - TPS.run_tests() - - # Get the binary used by this TPS instance, and use it in subsequent - # ones, so it doesn't have to be re-downloaded each time. - binary = TPS.firefoxRunner.binary - - # ... and then again in mobile mode - TPS_mobile = TPSTestRunner(self.extensionDir, - testfile=self.testfile, - logfile=self.logfile, - binary=binary, - config=self.config, - rlock=self.rlock, - mobile=True) - TPS_mobile.run_tests() - - # ... and again via the staging server, if credentials are present - stageaccount = self.config.get('stageaccount') - if stageaccount: - username = stageaccount.get('username') - password = stageaccount.get('password') - passphrase = stageaccount.get('passphrase') - if username and password and passphrase: - stageconfig = self.config.copy() - stageconfig['account'] = stageaccount.copy() - TPS_stage = TPSTestRunner(self.extensionDir, - testfile=self.testfile, - logfile=self.logfile, - binary=binary, - config=stageconfig, - rlock=self.rlock, - mobile=False)#, autolog=self.autolog) - TPS_stage.run_tests() -- cgit v1.2.3