Linter and requirements updates (#575)
* Use black, flake8, and isort instead of just flake8 * Reorganize requirements for simplicity * Update some rules * Add a script for automatically fixing linter errors * Add some docs on linting * Run travis linter on python 3.6
This commit is contained in:
Родитель
db35d9ef03
Коммит
40890ec5b2
|
@ -0,0 +1 @@
|
|||
mozregression tests setup.py gui/mozregui gui/build.py gui/tests
|
29
.travis.yml
29
.travis.yml
|
@ -7,14 +7,23 @@ matrix:
|
|||
dist: trusty # newer dists seem to have some weird ssl error with python2
|
||||
language: python
|
||||
python: '2.7'
|
||||
virtualenv:
|
||||
system_site_packages: true
|
||||
script:
|
||||
- pip install -e .
|
||||
- coverage run -m pytest tests && mv .coverage .coverage.core
|
||||
- coverage combine
|
||||
- pip install coveralls; coveralls
|
||||
- name: python35-linux
|
||||
env: PYTHON=python3.5
|
||||
os: linux
|
||||
language: python
|
||||
python: '3.5'
|
||||
script:
|
||||
- pip install -e .
|
||||
- coverage run -m pytest tests && mv .coverage .coverage.core
|
||||
- coverage combine
|
||||
- pip install coveralls; coveralls
|
||||
- name: python3-linux
|
||||
env: PYTHON=python3.5
|
||||
env: PYTHON=python3.6
|
||||
os: linux
|
||||
addons:
|
||||
apt:
|
||||
|
@ -24,22 +33,22 @@ matrix:
|
|||
services:
|
||||
- xvfb
|
||||
language: python
|
||||
python: '3.5'
|
||||
python: '3.6'
|
||||
script:
|
||||
- pip install -r requirements-gui-dev.txt
|
||||
- pip install -r requirements/all.txt
|
||||
- coverage run -m pytest tests && mv .coverage .coverage.core
|
||||
- coverage run gui/build.py test && mv .coverage .coverage.gui
|
||||
- coverage combine
|
||||
- pip install coveralls; coveralls
|
||||
- python gui/build.py bundle
|
||||
- name: linters
|
||||
env: PYTHON=python3.5
|
||||
env: PYTHON=python3.7
|
||||
os: linux
|
||||
language: python
|
||||
python: '3.5'
|
||||
python: '3.7'
|
||||
script:
|
||||
- flake8 mozregression tests setup.py
|
||||
- flake8 gui/mozregui gui/build.py gui/tests
|
||||
- pip install -r requirements/linters.txt
|
||||
- ./bin/lint-check.sh || (echo "Lint fix results:" && ./bin/lint-fix.sh && git diff && false)
|
||||
|
||||
install:
|
||||
- if [ "$TRAVIS_OS_NAME" == "osx" ]; then
|
||||
|
@ -56,7 +65,7 @@ install:
|
|||
sudo install_name_tool -id $PWD/QtGui.so QtGui.so;
|
||||
cd $MOZPATH;
|
||||
fi
|
||||
- pip install -r requirements-dev.txt
|
||||
- pip install -r requirements/console.txt
|
||||
|
||||
deploy:
|
||||
- provider: releases
|
||||
|
|
19
README.md
19
README.md
|
@ -3,6 +3,7 @@
|
|||
mozregression is an interactive regression rangefinder for quickly tracking down the source of bugs in Mozilla nightly and inbound builds.
|
||||
|
||||
You can start using mozregression today:
|
||||
|
||||
- [start with our installation guide](https://mozilla.github.io/mozregression/install.html), then
|
||||
- take a look at [our Quick Start document](https://mozilla.github.io/mozregression/quickstart.html).
|
||||
|
||||
|
@ -11,8 +12,8 @@ You can start using mozregression today:
|
|||
[![Latest Version](https://img.shields.io/pypi/v/mozregression.svg)](https://pypi.python.org/pypi/mozregression/)
|
||||
[![License](https://img.shields.io/pypi/l/mozregression.svg)](https://pypi.python.org/pypi/mozregression/)
|
||||
|
||||
|
||||
Build status:
|
||||
|
||||
- Linux:
|
||||
[![Linux Build Status](https://travis-ci.org/mozilla/mozregression.svg?branch=master)](https://travis-ci.org/mozilla/mozregression)
|
||||
[![Coverage Status](https://img.shields.io/coveralls/mozilla/mozregression.svg)](https://coveralls.io/r/mozilla/mozregression)
|
||||
|
@ -49,7 +50,7 @@ If you are **really sure** that you only want to hack on the mozregression comma
|
|||
|
||||
```bash
|
||||
mkvirtualenv -p /usr/bin/python3 mozregression
|
||||
pip install -r requirements-dev.txt
|
||||
pip install -r requirements/all-console.txt
|
||||
```
|
||||
|
||||
Or with virtualenv: ::
|
||||
|
@ -57,7 +58,19 @@ If you are **really sure** that you only want to hack on the mozregression comma
|
|||
```bash
|
||||
virtualenv -p /usr/bin/python3 venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements-dev.txt
|
||||
pip install -r requirements/all-console.txt
|
||||
```
|
||||
|
||||
2. lint your code for errors and formatting (we use [black](https://black.readthedocs.io/en/stable/), [flake8](https://flake8.pycqa.org/en/latest/) and [isort](https://isort.readthedocs.io/en/latest/))
|
||||
|
||||
```bash
|
||||
./bin/lint-check.sh
|
||||
```
|
||||
|
||||
If it turns up errors, try using the `lint-fix.sh` script to fix any errors which can be addressed automatically:
|
||||
|
||||
```bash
|
||||
./bin/lint-fix.sh
|
||||
```
|
||||
|
||||
3. run tests (be sure that your virtualenv is activated):
|
||||
|
|
|
@ -8,7 +8,7 @@ init:
|
|||
- "SET PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%"
|
||||
install:
|
||||
# install mozregression code and test dependencies
|
||||
- "pip install -r requirements-gui-dev.txt"
|
||||
- "pip install -r requirements/all.txt"
|
||||
test_script:
|
||||
- "python setup.py test"
|
||||
- "python gui\\build.py test"
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
LINTER_FILES="$(dirname "$0")/../.linter-files"
|
||||
|
||||
cat $LINTER_FILES | xargs isort --check-only --recursive
|
||||
cat $LINTER_FILES | xargs flake8
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
LINTER_FILES="$(dirname "$0")/../.linter-files"
|
||||
|
||||
cat $LINTER_FILES | xargs isort --recursive -y
|
||||
cat $LINTER_FILES | xargs black
|
|
@ -23,18 +23,18 @@ a virtualenv here. See this link
|
|||
about python virtualenvs. You may also consider using virtualenvwrapper
|
||||
(https://virtualenvwrapper.readthedocs.org/en/latest/).
|
||||
|
||||
Python 3.5+ is *required* to develop or use mozregression-gui.
|
||||
Python 3.6+ is *required* to develop or use mozregression-gui.
|
||||
|
||||
Install with virtualenvwrapper: ::
|
||||
|
||||
mkvirtualenv -p /usr/bin/python3 mozregression
|
||||
pip install -r requirements-gui-dev.txt
|
||||
pip install -r requirements/all.txt
|
||||
|
||||
Or with virtualenv: ::
|
||||
|
||||
virtualenv --system-site-packages -p /usr/bin/python3 venv
|
||||
source venv/bin/activate
|
||||
pip install -r requirements-gui-dev.txt
|
||||
pip install -r requirements/all.txt
|
||||
|
||||
Launching the application
|
||||
-------------------------
|
||||
|
|
89
gui/build.py
89
gui/build.py
|
@ -5,63 +5,68 @@ See python build.py --help
|
|||
"""
|
||||
|
||||
import argparse
|
||||
import sys
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
import glob
|
||||
import os
|
||||
import pipes
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tarfile
|
||||
|
||||
|
||||
IS_WIN = os.name == 'nt'
|
||||
IS_MAC = sys.platform == 'darwin'
|
||||
IS_WIN = os.name == "nt"
|
||||
IS_MAC = sys.platform == "darwin"
|
||||
|
||||
|
||||
def call(*args, **kwargs):
|
||||
print('Executing `%s`' % ' '.join(pipes.quote(a) for a in args))
|
||||
print("Executing `%s`" % " ".join(pipes.quote(a) for a in args))
|
||||
subprocess.check_call(args, **kwargs)
|
||||
|
||||
|
||||
def py_script(script_name):
|
||||
python_dir = os.path.dirname(sys.executable)
|
||||
if IS_WIN:
|
||||
return os.path.join(python_dir, 'Scripts',
|
||||
script_name + '.exe')
|
||||
return os.path.join(python_dir, "Scripts", script_name + ".exe")
|
||||
else:
|
||||
return os.path.join(python_dir, script_name)
|
||||
|
||||
|
||||
def do_uic(options, force=False):
|
||||
for uifile in glob.glob('mozregui/ui/*.ui'):
|
||||
pyfile = os.path.splitext(uifile)[0] + '.py'
|
||||
if force or not os.path.isfile(pyfile) or \
|
||||
(os.path.getmtime(uifile) > os.path.getmtime(pyfile)):
|
||||
for uifile in glob.glob("mozregui/ui/*.ui"):
|
||||
pyfile = os.path.splitext(uifile)[0] + ".py"
|
||||
if (
|
||||
force
|
||||
or not os.path.isfile(pyfile)
|
||||
or (os.path.getmtime(uifile) > os.path.getmtime(pyfile))
|
||||
):
|
||||
print("uic'ing %s -> %s" % (uifile, pyfile))
|
||||
os.system('pyside2-uic {} > {}'.format(uifile, pyfile))
|
||||
os.system("pyside2-uic {} > {}".format(uifile, pyfile))
|
||||
|
||||
|
||||
def do_rcc(options, force=False):
|
||||
rccfile = 'resources.qrc'
|
||||
pyfile = 'resources_rc.py'
|
||||
if force or not os.path.isfile(pyfile) or \
|
||||
(os.path.getmtime(rccfile) > os.path.getmtime(pyfile)):
|
||||
rccfile = "resources.qrc"
|
||||
pyfile = "resources_rc.py"
|
||||
if (
|
||||
force
|
||||
or not os.path.isfile(pyfile)
|
||||
or (os.path.getmtime(rccfile) > os.path.getmtime(pyfile))
|
||||
):
|
||||
print("rcc'ing %s -> %s" % (rccfile, pyfile))
|
||||
call('pyside2-rcc', '-o', pyfile, rccfile)
|
||||
call("pyside2-rcc", "-o", pyfile, rccfile)
|
||||
|
||||
|
||||
def do_run(options):
|
||||
do_uic(options)
|
||||
do_rcc(options)
|
||||
call(sys.executable, 'mozregression-gui.py')
|
||||
call(sys.executable, "mozregression-gui.py")
|
||||
|
||||
|
||||
def do_test(options):
|
||||
do_uic(options)
|
||||
do_rcc(options)
|
||||
print('Running tests...')
|
||||
print("Running tests...")
|
||||
import pytest
|
||||
sys.exit(pytest.main(['tests', '-v']))
|
||||
|
||||
sys.exit(pytest.main(["tests", "-v"]))
|
||||
|
||||
|
||||
def do_bundle(options):
|
||||
|
@ -69,45 +74,47 @@ def do_bundle(options):
|
|||
do_rcc(options, True)
|
||||
|
||||
# clean previous runs
|
||||
for dirname in ('build', 'dist'):
|
||||
for dirname in ("build", "dist"):
|
||||
if os.path.isdir(dirname):
|
||||
shutil.rmtree(dirname)
|
||||
# create a bundle for the application
|
||||
call('pyinstaller', 'gui.spec')
|
||||
call("pyinstaller", "gui.spec")
|
||||
# create an installer
|
||||
if IS_WIN:
|
||||
makensis_path = os.path.join(options.nsis_path, "makensis.exe")
|
||||
call(makensis_path, 'wininst.nsi', cwd='wininst')
|
||||
call(makensis_path, "wininst.nsi", cwd="wininst")
|
||||
elif IS_MAC:
|
||||
call('hdiutil', 'create', 'dist/mozregression-gui.dmg',
|
||||
'-srcfolder', 'dist/', '-ov')
|
||||
call(
|
||||
"hdiutil", "create", "dist/mozregression-gui.dmg", "-srcfolder", "dist/", "-ov",
|
||||
)
|
||||
else:
|
||||
with tarfile.open('mozregression-gui.tar.gz', 'w:gz') as tar:
|
||||
tar.add(r'dist/')
|
||||
with tarfile.open("mozregression-gui.tar.gz", "w:gz") as tar:
|
||||
tar.add(r"dist/")
|
||||
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
subparsers = parser.add_subparsers()
|
||||
|
||||
uic = subparsers.add_parser('uic', help='build uic files')
|
||||
uic = subparsers.add_parser("uic", help="build uic files")
|
||||
uic.set_defaults(func=do_uic)
|
||||
|
||||
rcc = subparsers.add_parser('rcc', help='build rcc files')
|
||||
rcc = subparsers.add_parser("rcc", help="build rcc files")
|
||||
rcc.set_defaults(func=do_rcc)
|
||||
|
||||
run = subparsers.add_parser('run', help='run the application')
|
||||
run = subparsers.add_parser("run", help="run the application")
|
||||
run.set_defaults(func=do_run)
|
||||
|
||||
test = subparsers.add_parser('test', help='run the unit tests')
|
||||
test = subparsers.add_parser("test", help="run the unit tests")
|
||||
test.set_defaults(func=do_test)
|
||||
|
||||
bundle = subparsers.add_parser('bundle',
|
||||
help='bundle the application (freeze)')
|
||||
bundle = subparsers.add_parser("bundle", help="bundle the application (freeze)")
|
||||
if IS_WIN:
|
||||
bundle.add_argument('--nsis-path', default='C:\\NSIS',
|
||||
help='your NSIS path on the'
|
||||
' system(default: %(default)r)')
|
||||
bundle.add_argument(
|
||||
"--nsis-path",
|
||||
default="C:\\NSIS",
|
||||
help="your NSIS path on the" " system(default: %(default)r)",
|
||||
)
|
||||
|
||||
bundle.set_defaults(func=do_bundle)
|
||||
|
||||
|
@ -122,8 +129,8 @@ def main():
|
|||
try:
|
||||
options.func(options)
|
||||
except Exception as e:
|
||||
sys.exit('ERROR: %s' % e)
|
||||
sys.exit("ERROR: %s" % e)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from PySide2.QtCore import QAbstractListModel, QModelIndex, Qt, \
|
||||
Slot
|
||||
from PySide2.QtWidgets import QWidget, QFileDialog
|
||||
from PySide2.QtCore import QAbstractListModel, QModelIndex, Qt, Slot
|
||||
from PySide2.QtWidgets import QFileDialog, QWidget
|
||||
|
||||
from mozregui.ui.addons_editor import Ui_AddonsEditor
|
||||
|
||||
|
||||
|
@ -26,8 +26,7 @@ class AddonsModel(QAbstractListModel):
|
|||
def add_addon(self, addon):
|
||||
if addon:
|
||||
addons_list_length = len(self.addons)
|
||||
self.beginInsertRows(QModelIndex(), addons_list_length,
|
||||
addons_list_length)
|
||||
self.beginInsertRows(QModelIndex(), addons_list_length, addons_list_length)
|
||||
self.addons.append(addon)
|
||||
self.endInsertRows()
|
||||
|
||||
|
@ -41,6 +40,7 @@ class AddonsWidgetEditor(QWidget):
|
|||
"""
|
||||
A widget to add or remove addons, and buttons to let the user interact.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
self.ui = Ui_AddonsEditor()
|
||||
|
@ -52,9 +52,7 @@ class AddonsWidgetEditor(QWidget):
|
|||
@Slot()
|
||||
def add_addon(self):
|
||||
(fileNames, _) = QFileDialog.getOpenFileNames(
|
||||
self,
|
||||
"Choose one or more addon files",
|
||||
filter="addon file (*.xpi)",
|
||||
self, "Choose one or more addon files", filter="addon file (*.xpi)",
|
||||
)
|
||||
for fileName in fileNames:
|
||||
self.list_model.add_addon(fileName)
|
||||
|
@ -62,8 +60,7 @@ class AddonsWidgetEditor(QWidget):
|
|||
@Slot()
|
||||
def remove_selected_addons(self):
|
||||
selected_rows = sorted(
|
||||
set(i.row() for i in self.ui.list_view.selectedIndexes()),
|
||||
reverse=True
|
||||
set(i.row() for i in self.ui.list_view.selectedIndexes()), reverse=True
|
||||
)
|
||||
for row in selected_rows:
|
||||
self.list_model.remove_pref(row)
|
||||
|
@ -72,7 +69,7 @@ class AddonsWidgetEditor(QWidget):
|
|||
return self.list_model.addons
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
from PySide2.QtWidgets import QApplication
|
||||
|
||||
app = QApplication([])
|
||||
|
|
|
@ -1,18 +1,23 @@
|
|||
import sys
|
||||
import threading
|
||||
from PySide2.QtCore import QObject, Signal, Slot, QTimer
|
||||
|
||||
from PySide2.QtCore import QObject, QTimer, Signal, Slot
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
|
||||
from mozregression.bisector import Bisector, Bisection, NightlyHandler, \
|
||||
IntegrationHandler, IndexPromise
|
||||
from mozregression.errors import MozRegressionError
|
||||
from mozregression.dates import is_date_or_datetime
|
||||
from mozregression.approx_persist import ApproxPersistChooser
|
||||
from mozregression.bisector import (
|
||||
Bisection,
|
||||
Bisector,
|
||||
IndexPromise,
|
||||
IntegrationHandler,
|
||||
NightlyHandler,
|
||||
)
|
||||
from mozregression.config import DEFAULT_EXPAND
|
||||
|
||||
from mozregression.dates import is_date_or_datetime
|
||||
from mozregression.errors import MozRegressionError
|
||||
from mozregui.build_runner import AbstractBuildRunner
|
||||
from mozregui.skip_chooser import SkipDialog
|
||||
from mozregui.log_report import log
|
||||
from mozregui.skip_chooser import SkipDialog
|
||||
|
||||
Bisection.EXCEPTION = -1 # new possible value of bisection end
|
||||
|
||||
|
@ -27,8 +32,7 @@ class GuiBisector(QObject, Bisector):
|
|||
step_finished = Signal(object, str)
|
||||
handle_merge = Signal(object, str, str, str)
|
||||
|
||||
def __init__(self, fetch_config, test_runner, download_manager,
|
||||
download_in_background=True):
|
||||
def __init__(self, fetch_config, test_runner, download_manager, download_in_background=True):
|
||||
QObject.__init__(self)
|
||||
Bisector.__init__(self, fetch_config, test_runner, download_manager)
|
||||
self.bisection = None
|
||||
|
@ -42,8 +46,7 @@ class GuiBisector(QObject, Bisector):
|
|||
self._persist_files = ()
|
||||
self.should_stop = threading.Event()
|
||||
|
||||
self.download_manager.download_finished.connect(
|
||||
self._build_dl_finished)
|
||||
self.download_manager.download_finished.connect(self._build_dl_finished)
|
||||
self.test_runner.evaluate_finished.connect(self._evaluate_finished)
|
||||
|
||||
def _finish_on_exception(self, bisection):
|
||||
|
@ -66,9 +69,14 @@ class GuiBisector(QObject, Bisector):
|
|||
handler = self.bisection.handler
|
||||
try:
|
||||
nhandler = IntegrationHandler(find_fix=self.bisection.handler.find_fix)
|
||||
Bisector.bisect(self, nhandler, handler.good_revision,
|
||||
handler.bad_revision, expand=DEFAULT_EXPAND,
|
||||
interrupt=self.should_stop.is_set)
|
||||
Bisector.bisect(
|
||||
self,
|
||||
nhandler,
|
||||
handler.good_revision,
|
||||
handler.bad_revision,
|
||||
expand=DEFAULT_EXPAND,
|
||||
interrupt=self.should_stop.is_set,
|
||||
)
|
||||
except MozRegressionError:
|
||||
self._finish_on_exception(None)
|
||||
except StopIteration:
|
||||
|
@ -89,20 +97,22 @@ class GuiBisector(QObject, Bisector):
|
|||
self.handle_merge.emit(self.bisection, *result)
|
||||
|
||||
def _bisect(self, handler, build_range):
|
||||
self.bisection = Bisection(handler, build_range,
|
||||
self.bisection = Bisection(
|
||||
handler,
|
||||
build_range,
|
||||
self.download_manager,
|
||||
self.test_runner,
|
||||
dl_in_background=False,
|
||||
approx_chooser=self.approx_chooser)
|
||||
approx_chooser=self.approx_chooser,
|
||||
)
|
||||
self._bisect_next()
|
||||
|
||||
@Slot()
|
||||
def _bisect_next(self):
|
||||
# this is executed in the working thread
|
||||
if self.test_runner.verdict != 'r':
|
||||
if self.test_runner.verdict != "r":
|
||||
try:
|
||||
self.mid = self.bisection.search_mid_point(
|
||||
interrupt=self.should_stop.is_set)
|
||||
self.mid = self.bisection.search_mid_point(interrupt=self.should_stop.is_set)
|
||||
except MozRegressionError:
|
||||
self._finish_on_exception(self.bisection)
|
||||
return
|
||||
|
@ -111,9 +121,11 @@ class GuiBisector(QObject, Bisector):
|
|||
|
||||
# if our last answer was skip, and that the next build
|
||||
# to use is not chosen yet, ask to choose it.
|
||||
if (self._next_build_index is None and
|
||||
self.test_runner.verdict == 's' and
|
||||
len(self.bisection.build_range) > 3):
|
||||
if (
|
||||
self._next_build_index is None
|
||||
and self.test_runner.verdict == "s"
|
||||
and len(self.bisection.build_range) > 3
|
||||
):
|
||||
self.choose_next_build.emit()
|
||||
return
|
||||
|
||||
|
@ -133,8 +145,12 @@ class GuiBisector(QObject, Bisector):
|
|||
self.finished.emit(self.bisection, result)
|
||||
else:
|
||||
self.build_infos = self.bisection.handler.build_range[self.mid]
|
||||
found, self.mid, self.build_infos, self._persist_files = \
|
||||
self.bisection._find_approx_build(self.mid, self.build_infos)
|
||||
(
|
||||
found,
|
||||
self.mid,
|
||||
self.build_infos,
|
||||
self._persist_files,
|
||||
) = self.bisection._find_approx_build(self.mid, self.build_infos)
|
||||
if not found:
|
||||
self.download_manager.focus_download(self.build_infos)
|
||||
self.step_build_found.emit(self.bisection, self.build_infos)
|
||||
|
@ -149,11 +165,9 @@ class GuiBisector(QObject, Bisector):
|
|||
|
||||
# download in background, if desired and that last verdict was not
|
||||
# a skip.
|
||||
if self.download_in_background and self.test_runner.verdict != 's':
|
||||
if self.download_in_background and self.test_runner.verdict != "s":
|
||||
self.index_promise = IndexPromise(
|
||||
self.mid,
|
||||
self.bisection._download_next_builds,
|
||||
args=(self._persist_files,)
|
||||
self.mid, self.bisection._download_next_builds, args=(self._persist_files,),
|
||||
)
|
||||
# run the build evaluation
|
||||
self.bisection.evaluate(self.build_infos)
|
||||
|
@ -188,8 +202,7 @@ class GuiBisector(QObject, Bisector):
|
|||
self.index_promise = None
|
||||
|
||||
self.step_finished.emit(self.bisection, self.test_runner.verdict)
|
||||
result = self.bisection.handle_verdict(self.mid,
|
||||
self.test_runner.verdict)
|
||||
result = self.bisection.handle_verdict(self.mid, self.test_runner.verdict)
|
||||
if result != Bisection.RUNNING:
|
||||
self.finished.emit(self.bisection, result)
|
||||
else:
|
||||
|
@ -208,17 +221,19 @@ class BisectRunner(AbstractBuildRunner):
|
|||
self.worker.handle_merge.connect(self.handle_merge)
|
||||
self.worker.choose_next_build.connect(self.choose_next_build)
|
||||
|
||||
good, bad = options.pop('good'), options.pop('bad')
|
||||
if is_date_or_datetime(good) and is_date_or_datetime(bad) \
|
||||
and fetch_config.should_use_archive():
|
||||
handler = NightlyHandler(find_fix=options['find_fix'])
|
||||
good, bad = options.pop("good"), options.pop("bad")
|
||||
if (
|
||||
is_date_or_datetime(good)
|
||||
and is_date_or_datetime(bad)
|
||||
and fetch_config.should_use_archive()
|
||||
):
|
||||
handler = NightlyHandler(find_fix=options["find_fix"])
|
||||
else:
|
||||
handler = IntegrationHandler(find_fix=options['find_fix'])
|
||||
handler = IntegrationHandler(find_fix=options["find_fix"])
|
||||
|
||||
self.worker._bisect_args = (handler, good, bad)
|
||||
self.worker.download_in_background = \
|
||||
self.global_prefs['background_downloads']
|
||||
if self.global_prefs['approx_policy']:
|
||||
self.worker.download_in_background = self.global_prefs["background_downloads"]
|
||||
if self.global_prefs["approx_policy"]:
|
||||
self.worker.approx_chooser = ApproxPersistChooser(7)
|
||||
return self.worker.bisect
|
||||
|
||||
|
@ -236,11 +251,12 @@ class BisectRunner(AbstractBuildRunner):
|
|||
QMessageBox.warning(
|
||||
self.mainwindow,
|
||||
"Launcher Error",
|
||||
("An error occured while starting the process, so the build"
|
||||
" will be skipped. Error message:<br><strong>%s</strong>"
|
||||
% err_message)
|
||||
(
|
||||
"An error occured while starting the process, so the build"
|
||||
" will be skipped. Error message:<br><strong>%s</strong>" % err_message
|
||||
),
|
||||
)
|
||||
self.worker.test_runner.finish('s')
|
||||
self.worker.test_runner.finish("s")
|
||||
|
||||
@Slot(str)
|
||||
def set_verdict(self, verdict):
|
||||
|
@ -275,11 +291,10 @@ class BisectRunner(AbstractBuildRunner):
|
|||
dialog = QMessageBox.critical
|
||||
else:
|
||||
fetch_config = self.worker.fetch_config
|
||||
if not getattr(bisection, 'no_more_merge', False):
|
||||
if not getattr(bisection, "no_more_merge", False):
|
||||
if isinstance(bisection.handler, NightlyHandler):
|
||||
handler = bisection.handler
|
||||
fetch_config.set_repo(
|
||||
fetch_config.get_nightly_repo(handler.bad_date))
|
||||
fetch_config.set_repo(fetch_config.get_nightly_repo(handler.bad_date))
|
||||
QTimer.singleShot(0, self.worker.bisect_further)
|
||||
else:
|
||||
# check merge, try to bisect further
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
from PySide2.QtCore import QObject, QThread, Signal, \
|
||||
Slot, QTimer
|
||||
from PySide2.QtCore import QObject, QThread, QTimer, Signal, Slot
|
||||
|
||||
from mozregui.global_prefs import get_prefs, apply_prefs
|
||||
from mozregression.download_manager import BuildDownloadManager
|
||||
from mozregression.test_runner import create_launcher
|
||||
from mozregression.errors import LauncherError
|
||||
from mozregression.network import get_http_session
|
||||
from mozregression.persist_limit import PersistLimit
|
||||
from mozregression.errors import LauncherError
|
||||
|
||||
from mozregression.test_runner import create_launcher
|
||||
from mozregui.global_prefs import apply_prefs, get_prefs
|
||||
from mozregui.log_report import log
|
||||
|
||||
|
||||
|
@ -18,10 +16,9 @@ class GuiBuildDownloadManager(QObject, BuildDownloadManager):
|
|||
|
||||
def __init__(self, destdir, persist_limit, **kwargs):
|
||||
QObject.__init__(self)
|
||||
BuildDownloadManager.__init__(self, destdir,
|
||||
session=get_http_session(),
|
||||
persist_limit=persist_limit,
|
||||
**kwargs)
|
||||
BuildDownloadManager.__init__(
|
||||
self, destdir, session=get_http_session(), persist_limit=persist_limit, **kwargs
|
||||
)
|
||||
|
||||
def _download_started(self, task):
|
||||
self.download_started.emit(task)
|
||||
|
@ -72,7 +69,7 @@ class GuiTestRunner(QObject):
|
|||
self.run_error = True
|
||||
self.evaluate_started.emit(str(exc))
|
||||
else:
|
||||
self.evaluate_started.emit('')
|
||||
self.evaluate_started.emit("")
|
||||
self.run_error = False
|
||||
|
||||
def finish(self, verdict):
|
||||
|
@ -94,6 +91,7 @@ class AbstractBuildRunner(QObject):
|
|||
Create the required test runner and build manager, along with a thread
|
||||
that should be used for blocking tasks.
|
||||
"""
|
||||
|
||||
running_state_changed = Signal(bool)
|
||||
worker_created = Signal(object)
|
||||
worker_class = None
|
||||
|
@ -124,42 +122,36 @@ class AbstractBuildRunner(QObject):
|
|||
# apply the global prefs now
|
||||
apply_prefs(global_prefs)
|
||||
|
||||
fetch_config.set_base_url(global_prefs['archive_base_url'])
|
||||
fetch_config.set_base_url(global_prefs["archive_base_url"])
|
||||
|
||||
download_dir = global_prefs['persist']
|
||||
download_dir = global_prefs["persist"]
|
||||
if not download_dir:
|
||||
download_dir = self.mainwindow.persist
|
||||
persist_limit = PersistLimit(
|
||||
abs(global_prefs['persist_size_limit']) * 1073741824
|
||||
)
|
||||
self.download_manager = GuiBuildDownloadManager(download_dir,
|
||||
persist_limit)
|
||||
persist_limit = PersistLimit(abs(global_prefs["persist_size_limit"]) * 1073741824)
|
||||
self.download_manager = GuiBuildDownloadManager(download_dir, persist_limit)
|
||||
self.test_runner = GuiTestRunner()
|
||||
self.thread = QThread()
|
||||
|
||||
# options for the app launcher
|
||||
launcher_kwargs = {}
|
||||
for name in ('profile', 'preferences'):
|
||||
for name in ("profile", "preferences"):
|
||||
if name in options:
|
||||
value = options[name]
|
||||
if value:
|
||||
launcher_kwargs[name] = value
|
||||
|
||||
# add add-ons paths to the app launcher
|
||||
launcher_kwargs['addons'] = options['addons']
|
||||
launcher_kwargs["addons"] = options["addons"]
|
||||
self.test_runner.launcher_kwargs = launcher_kwargs
|
||||
|
||||
if options['profile_persistence'] in ('clone-first', 'reuse') or options['profile']:
|
||||
launcher_kwargs['cmdargs'] = launcher_kwargs.get('cmdargs', []) + ['--allow-downgrade']
|
||||
if options["profile_persistence"] in ("clone-first", "reuse") or options["profile"]:
|
||||
launcher_kwargs["cmdargs"] = launcher_kwargs.get("cmdargs", []) + ["--allow-downgrade"]
|
||||
|
||||
# Thunderbird will fail to start if passed an URL arg
|
||||
if 'url' in options and fetch_config.app_name != 'thunderbird':
|
||||
launcher_kwargs['cmdargs'] = (
|
||||
launcher_kwargs.get('cmdargs', []) + [options['url']]
|
||||
)
|
||||
if "url" in options and fetch_config.app_name != "thunderbird":
|
||||
launcher_kwargs["cmdargs"] = launcher_kwargs.get("cmdargs", []) + [options["url"]]
|
||||
|
||||
self.worker = self.worker_class(fetch_config, self.test_runner,
|
||||
self.download_manager)
|
||||
self.worker = self.worker_class(fetch_config, self.test_runner, self.download_manager)
|
||||
# Move self.bisector in the thread. This will
|
||||
# allow to the self.bisector slots (connected after the move)
|
||||
# to be automatically called in the thread.
|
||||
|
@ -179,9 +171,8 @@ class AbstractBuildRunner(QObject):
|
|||
def stop(self, wait=True):
|
||||
self.stopped = True
|
||||
if self.options:
|
||||
if self.options['profile'] and \
|
||||
self.options['profile_persistence'] == 'clone-first':
|
||||
self.options['profile'].cleanup()
|
||||
if self.options["profile"] and self.options["profile_persistence"] == "clone-first":
|
||||
self.options["profile"].cleanup()
|
||||
if self.download_manager:
|
||||
self.download_manager.cancel()
|
||||
if self.thread:
|
||||
|
@ -206,7 +197,7 @@ class AbstractBuildRunner(QObject):
|
|||
if self.test_runner:
|
||||
self.test_runner.finish(None)
|
||||
self.running_state_changed.emit(False)
|
||||
log('Stopped')
|
||||
log("Stopped")
|
||||
|
||||
@Slot()
|
||||
def _remove_pending_thread(self):
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
from PySide2.QtCore import QObject, QThread, Slot, Qt, QUrl
|
||||
from PySide2.QtCore import QObject, Qt, QThread, QUrl, Slot
|
||||
from PySide2.QtGui import QDesktopServices
|
||||
from PySide2.QtWidgets import QLabel
|
||||
from mozregression.network import retry_get
|
||||
|
||||
from mozregression import __version__ as mozregression_version
|
||||
from mozregression.network import retry_get
|
||||
|
||||
|
||||
class CheckReleaseThread(QThread):
|
||||
GITHUB_LATEST_RELEASE_URL = (
|
||||
"https://api.github.com/repos/mozilla/mozregression/releases/latest"
|
||||
)
|
||||
GITHUB_LATEST_RELEASE_URL = "https://api.github.com/repos/mozilla/mozregression/releases/latest"
|
||||
|
||||
def __init__(self):
|
||||
QThread.__init__(self)
|
||||
|
@ -18,8 +16,8 @@ class CheckReleaseThread(QThread):
|
|||
|
||||
def run(self):
|
||||
data = retry_get(self.GITHUB_LATEST_RELEASE_URL).json()
|
||||
self.tag_name = data['tag_name']
|
||||
self.release_url = data['html_url']
|
||||
self.tag_name = data["tag_name"]
|
||||
self.release_url = data["html_url"]
|
||||
|
||||
|
||||
class CheckRelease(QObject):
|
||||
|
@ -44,9 +42,9 @@ class CheckRelease(QObject):
|
|||
return
|
||||
|
||||
self.label.setText(
|
||||
'There is a new release available! Download the new'
|
||||
' <a href="%s">release %s</a>.'
|
||||
% (self.thread.release_url, release_name))
|
||||
"There is a new release available! Download the new"
|
||||
' <a href="%s">release %s</a>.' % (self.thread.release_url, release_name)
|
||||
)
|
||||
self.mainwindow.ui.status_bar.addWidget(self.label)
|
||||
|
||||
@Slot(str)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import platform
|
||||
import sys
|
||||
import traceback
|
||||
import platform
|
||||
|
||||
from PySide2.QtCore import QObject, Qt, Signal, Slot
|
||||
from PySide2.QtWidgets import QDialog
|
||||
|
||||
import mozregression
|
||||
|
||||
from PySide2.QtCore import QObject, Slot, Signal, Qt
|
||||
from PySide2.QtWidgets import QDialog
|
||||
from .ui.crash_reporter import Ui_CrashDialog
|
||||
|
||||
|
||||
|
@ -23,15 +25,18 @@ traceback: %(traceback)s
|
|||
self.ui.setupUi(self)
|
||||
|
||||
def set_exception(self, type, value, tb):
|
||||
frozen = ' FROZEN' if getattr(sys, 'frozen', False) else ''
|
||||
self.ui.information.setPlainText(self.ERR_TEMPLATE % dict(
|
||||
frozen = " FROZEN" if getattr(sys, "frozen", False) else ""
|
||||
self.ui.information.setPlainText(
|
||||
self.ERR_TEMPLATE
|
||||
% dict(
|
||||
mozregression=mozregression.__version__,
|
||||
message="%s: %s" % (type.__name__, value),
|
||||
traceback=''.join(traceback.format_tb(tb)) if tb else 'NONE',
|
||||
traceback="".join(traceback.format_tb(tb)) if tb else "NONE",
|
||||
platform=platform.platform(),
|
||||
python=platform.python_version() + frozen,
|
||||
arch=platform.architecture()[0],
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
class CrashReporter(QObject):
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import os
|
||||
|
||||
from configobj import ConfigObj
|
||||
from PySide2.QtWidgets import QDialog
|
||||
|
||||
from mozregui.ui.global_prefs import Ui_GlobalPrefs
|
||||
|
||||
from mozregression.config import ARCHIVE_BASE_URL, DEFAULT_CONF_FNAME, get_defaults
|
||||
from mozregression.network import set_http_session
|
||||
from mozregression.config import (DEFAULT_CONF_FNAME, get_defaults,
|
||||
ARCHIVE_BASE_URL)
|
||||
from configobj import ConfigObj
|
||||
from mozregui.ui.global_prefs import Ui_GlobalPrefs
|
||||
|
||||
|
||||
def get_prefs():
|
||||
|
@ -15,14 +14,15 @@ def get_prefs():
|
|||
"""
|
||||
settings = get_defaults(DEFAULT_CONF_FNAME)
|
||||
options = dict()
|
||||
options['persist'] = settings['persist']
|
||||
options['http_timeout'] = float(settings['http-timeout'])
|
||||
options['persist_size_limit'] = float(settings['persist-size-limit'])
|
||||
options['background_downloads'] = \
|
||||
False if settings.get('background_downloads') == 'no' else True
|
||||
options['approx_policy'] = settings['approx-policy'] == 'auto'
|
||||
options['archive_base_url'] = settings["archive-base-url"]
|
||||
options['cmdargs'] = settings['cmdargs']
|
||||
options["persist"] = settings["persist"]
|
||||
options["http_timeout"] = float(settings["http-timeout"])
|
||||
options["persist_size_limit"] = float(settings["persist-size-limit"])
|
||||
options["background_downloads"] = (
|
||||
False if settings.get("background_downloads") == "no" else True
|
||||
)
|
||||
options["approx_policy"] = settings["approx-policy"] == "auto"
|
||||
options["archive_base_url"] = settings["archive-base-url"]
|
||||
options["cmdargs"] = settings["cmdargs"]
|
||||
return options
|
||||
|
||||
|
||||
|
@ -31,23 +31,23 @@ def save_prefs(options):
|
|||
if not os.path.isdir(conf_dir):
|
||||
os.makedirs(conf_dir)
|
||||
settings = ConfigObj(DEFAULT_CONF_FNAME)
|
||||
settings.update({
|
||||
'persist': options['persist'] or '',
|
||||
'http-timeout': options['http_timeout'],
|
||||
'persist-size-limit': options['persist_size_limit'],
|
||||
'background_downloads':
|
||||
'yes' if options['background_downloads'] else 'no',
|
||||
'approx-policy': 'auto' if options['approx_policy'] else 'none',
|
||||
})
|
||||
settings.update(
|
||||
{
|
||||
"persist": options["persist"] or "",
|
||||
"http-timeout": options["http_timeout"],
|
||||
"persist-size-limit": options["persist_size_limit"],
|
||||
"background_downloads": "yes" if options["background_downloads"] else "no",
|
||||
"approx-policy": "auto" if options["approx_policy"] else "none",
|
||||
}
|
||||
)
|
||||
# only save base url in the file if it differs from the default.
|
||||
if options['archive_base_url'] and \
|
||||
options['archive_base_url'] != ARCHIVE_BASE_URL:
|
||||
settings['archive-base-url'] = options['archive_base_url']
|
||||
elif 'archive-base-url' in settings:
|
||||
del settings['archive-base-url']
|
||||
if options["archive_base_url"] and options["archive_base_url"] != ARCHIVE_BASE_URL:
|
||||
settings["archive-base-url"] = options["archive_base_url"]
|
||||
elif "archive-base-url" in settings:
|
||||
del settings["archive-base-url"]
|
||||
# likewise only save args if it has a value
|
||||
if 'cmdargs' in settings and not settings['cmdargs']:
|
||||
del settings['cmdargs']
|
||||
if "cmdargs" in settings and not settings["cmdargs"]:
|
||||
del settings["cmdargs"]
|
||||
settings.write()
|
||||
|
||||
|
||||
|
@ -55,16 +55,13 @@ def set_default_prefs():
|
|||
"""Set the default prefs for a first launch of the application."""
|
||||
if not os.path.isfile(DEFAULT_CONF_FNAME):
|
||||
options = get_prefs()
|
||||
options["persist"] = os.path.join(os.path.dirname(DEFAULT_CONF_FNAME),
|
||||
"persist")
|
||||
options["persist"] = os.path.join(os.path.dirname(DEFAULT_CONF_FNAME), "persist")
|
||||
options["persist_size_limit"] = 2.0
|
||||
save_prefs(options)
|
||||
|
||||
|
||||
def apply_prefs(options):
|
||||
set_http_session(get_defaults={
|
||||
"timeout": options['http_timeout'],
|
||||
})
|
||||
set_http_session(get_defaults={"timeout": options["http_timeout"]})
|
||||
# persist options have to be passed in the bisection, not handled here.
|
||||
|
||||
|
||||
|
@ -76,12 +73,12 @@ class ChangePrefsDialog(QDialog):
|
|||
|
||||
# set default values
|
||||
options = get_prefs()
|
||||
self.ui.persist.line_edit.setText(options['persist'] or '')
|
||||
self.ui.http_timeout.setValue(options['http_timeout'])
|
||||
self.ui.persist_size_limit.setValue(options['persist_size_limit'])
|
||||
self.ui.bg_downloads.setChecked(options['background_downloads'])
|
||||
self.ui.approx.setChecked(options['approx_policy'])
|
||||
self.ui.archive_base_url.setText(options['archive_base_url'])
|
||||
self.ui.persist.line_edit.setText(options["persist"] or "")
|
||||
self.ui.http_timeout.setValue(options["http_timeout"])
|
||||
self.ui.persist_size_limit.setValue(options["persist_size_limit"])
|
||||
self.ui.bg_downloads.setChecked(options["background_downloads"])
|
||||
self.ui.approx.setChecked(options["approx_policy"])
|
||||
self.ui.archive_base_url.setText(options["archive_base_url"])
|
||||
self.ui.advanced_options.setText("Show Advanced Options")
|
||||
self.toggle_visibility(False)
|
||||
self.ui.advanced_options.clicked.connect(self.toggle_adv_options)
|
||||
|
@ -106,12 +103,12 @@ class ChangePrefsDialog(QDialog):
|
|||
options = get_prefs()
|
||||
ui = self.ui
|
||||
|
||||
options['persist'] = str(ui.persist.line_edit.text()) or None
|
||||
options['http_timeout'] = ui.http_timeout.value()
|
||||
options['persist_size_limit'] = ui.persist_size_limit.value()
|
||||
options['background_downloads'] = ui.bg_downloads.isChecked()
|
||||
options['approx_policy'] = ui.approx.isChecked()
|
||||
options['archive_base_url'] = str(ui.archive_base_url.text())
|
||||
options["persist"] = str(ui.persist.line_edit.text()) or None
|
||||
options["http_timeout"] = ui.http_timeout.value()
|
||||
options["persist_size_limit"] = ui.persist_size_limit.value()
|
||||
options["background_downloads"] = ui.bg_downloads.isChecked()
|
||||
options["approx_policy"] = ui.approx.isChecked()
|
||||
options["archive_base_url"] = str(ui.archive_base_url.text())
|
||||
save_prefs(options)
|
||||
|
||||
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
from PySide2.QtCore import (QObject, Slot, Signal)
|
||||
from PySide2.QtWidgets import (QAction, QActionGroup, QMenu, QPlainTextEdit)
|
||||
from PySide2.QtGui import (QTextCursor, QColor,
|
||||
QTextCharFormat,
|
||||
QTextBlockUserData)
|
||||
from datetime import datetime
|
||||
from mozlog.structuredlog import log_levels
|
||||
|
||||
from mozlog import get_default_logger
|
||||
from mozlog.structuredlog import log_levels
|
||||
from PySide2.QtCore import QObject, Signal, Slot
|
||||
from PySide2.QtGui import QColor, QTextBlockUserData, QTextCharFormat, QTextCursor
|
||||
from PySide2.QtWidgets import QAction, QActionGroup, QMenu, QPlainTextEdit
|
||||
|
||||
COLORS = {
|
||||
'DEBUG': QColor(6, 146, 6), # green
|
||||
'INFO': QColor(250, 184, 4), # deep yellow
|
||||
'WARNING': QColor(255, 0, 0, 127), # red
|
||||
'CRITICAL': QColor(255, 0, 0, 127),
|
||||
'ERROR': QColor(255, 0, 0, 127),
|
||||
"DEBUG": QColor(6, 146, 6), # green
|
||||
"INFO": QColor(250, 184, 4), # deep yellow
|
||||
"WARNING": QColor(255, 0, 0, 127), # red
|
||||
"CRITICAL": QColor(255, 0, 0, 127),
|
||||
"ERROR": QColor(255, 0, 0, 127),
|
||||
}
|
||||
|
||||
|
||||
|
@ -29,16 +27,17 @@ class LogView(QPlainTextEdit):
|
|||
self.setMaximumBlockCount(1000)
|
||||
|
||||
self.group = QActionGroup(self)
|
||||
self.actions = [QAction(log_lvl, self.group) for log_lvl in
|
||||
["Debug", "Info", "Warning", "Error", "Critical"]]
|
||||
self.actions = [
|
||||
QAction(log_lvl, self.group)
|
||||
for log_lvl in ["Debug", "Info", "Warning", "Error", "Critical"]
|
||||
]
|
||||
|
||||
for action in self.actions:
|
||||
action.setCheckable(True)
|
||||
action.triggered.connect(self.on_log_filter)
|
||||
self.actions[0].setChecked(True)
|
||||
|
||||
self.customContextMenuRequested.connect(
|
||||
self.on_custom_context_menu_requested)
|
||||
self.customContextMenuRequested.connect(self.on_custom_context_menu_requested)
|
||||
|
||||
self.log_lvl = log_levels["INFO"]
|
||||
|
||||
|
@ -50,24 +49,22 @@ class LogView(QPlainTextEdit):
|
|||
|
||||
@Slot(dict)
|
||||
def on_log_received(self, data):
|
||||
time_info = datetime.fromtimestamp((data['time'] / 1000)).isoformat()
|
||||
log_message = '%s: %s : %s' % (
|
||||
time_info, data['level'], data['message'])
|
||||
time_info = datetime.fromtimestamp((data["time"] / 1000)).isoformat()
|
||||
log_message = "%s: %s : %s" % (time_info, data["level"], data["message"])
|
||||
message_document = self.document()
|
||||
cursor_to_add = QTextCursor(message_document)
|
||||
cursor_to_add.movePosition(cursor_to_add.End)
|
||||
cursor_to_add.insertText(log_message + '\n')
|
||||
cursor_to_add.insertText(log_message + "\n")
|
||||
|
||||
if data['level'] in COLORS:
|
||||
if data["level"] in COLORS:
|
||||
fmt = QTextCharFormat()
|
||||
fmt.setForeground(COLORS[data['level']])
|
||||
fmt.setForeground(COLORS[data["level"]])
|
||||
cursor_to_add.movePosition(cursor_to_add.PreviousBlock)
|
||||
log_lvl_data = LogLevelData(log_levels[data['level'].upper()])
|
||||
log_lvl_data = LogLevelData(log_levels[data["level"].upper()])
|
||||
cursor_to_add.block().setUserData(log_lvl_data)
|
||||
cursor_to_add_fmt = message_document.find(data['level'],
|
||||
cursor_to_add.position())
|
||||
cursor_to_add_fmt = message_document.find(data["level"], cursor_to_add.position())
|
||||
cursor_to_add_fmt.mergeCharFormat(fmt)
|
||||
if log_levels[data['level']] > self.log_lvl:
|
||||
if log_levels[data["level"]] > self.log_lvl:
|
||||
cursor_to_add.block().setVisible(False)
|
||||
self.ensureCursorVisible()
|
||||
|
||||
|
@ -103,11 +100,12 @@ class LogModel(QObject):
|
|||
|
||||
def log(text, log=True, status_bar=True, status_bar_timeout=2.0):
|
||||
if log:
|
||||
logger = get_default_logger('mozregui')
|
||||
logger = get_default_logger("mozregui")
|
||||
if logger:
|
||||
logger.info(text)
|
||||
if status_bar:
|
||||
from mozregui.mainwindow import MainWindow
|
||||
|
||||
mw = MainWindow.INSTANCE
|
||||
if mw:
|
||||
mw.ui.status_bar.showMessage(text, int(status_bar_timeout * 1000))
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import sys
|
||||
|
||||
from mozlog.structuredlog import StructuredLogger, set_default_logger
|
||||
from PySide2.QtWidgets import QApplication
|
||||
|
||||
from mozlog.structuredlog import set_default_logger, StructuredLogger
|
||||
|
||||
from .log_report import LogModel
|
||||
from .check_release import CheckRelease
|
||||
from .crash_reporter import CrashReporter
|
||||
from .mainwindow import MainWindow
|
||||
from .global_prefs import set_default_prefs
|
||||
from .log_report import LogModel
|
||||
from .mainwindow import MainWindow
|
||||
|
||||
|
||||
def main():
|
||||
logger = StructuredLogger('mozregression-gui')
|
||||
logger = StructuredLogger("mozregression-gui")
|
||||
set_default_logger(logger)
|
||||
# Create a Qt application
|
||||
log_model = LogModel()
|
||||
|
@ -20,9 +20,9 @@ def main():
|
|||
app = QApplication(argv)
|
||||
crash_reporter = CrashReporter(app)
|
||||
crash_reporter.install()
|
||||
app.setOrganizationName('mozilla')
|
||||
app.setOrganizationDomain('mozilla.org')
|
||||
app.setApplicationName('mozregression-gui')
|
||||
app.setOrganizationName("mozilla")
|
||||
app.setOrganizationDomain("mozilla.org")
|
||||
app.setApplicationName("mozregression-gui")
|
||||
set_default_prefs()
|
||||
# Create the main window and show it
|
||||
win = MainWindow()
|
||||
|
@ -37,5 +37,5 @@ def main():
|
|||
sys.exit(app.exec_())
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -1,18 +1,16 @@
|
|||
|
||||
import mozregression
|
||||
import mozfile
|
||||
|
||||
from tempfile import mkdtemp
|
||||
from PySide2.QtCore import Slot, QSettings
|
||||
|
||||
import mozfile
|
||||
from PySide2.QtCore import QSettings, Slot
|
||||
from PySide2.QtWidgets import QMainWindow, QMessageBox
|
||||
|
||||
from mozregui.ui.mainwindow import Ui_MainWindow
|
||||
from mozregui.wizard import BisectionWizard, SingleRunWizard
|
||||
import mozregression
|
||||
from mozregui.bisection import BisectRunner
|
||||
from mozregui.single_runner import SingleBuildRunner
|
||||
from mozregui.global_prefs import change_prefs_dialog
|
||||
from mozregui.report_delegate import ReportItemDelegate
|
||||
|
||||
from mozregui.single_runner import SingleBuildRunner
|
||||
from mozregui.ui.mainwindow import Ui_MainWindow
|
||||
from mozregui.wizard import BisectionWizard, SingleRunWizard
|
||||
|
||||
ABOUT_TEXT = """\
|
||||
<p><strong>mozregression-gui</strong> is a desktop interface for
|
||||
|
@ -27,7 +25,9 @@ http://mozilla.github.io/mozregression/</a></p>
|
|||
from <a href="http://www.flaticon.com" title="Flaticon">www.flaticon.com</a>
|
||||
and licensed under <a href="http://creativecommons.org/licenses/by/3.0/"
|
||||
title="Creative Commons BY 3.0">CC BY 3.0</a></p>
|
||||
""" % (mozregression.__version__)
|
||||
""" % (
|
||||
mozregression.__version__
|
||||
)
|
||||
|
||||
|
||||
class MainWindow(QMainWindow):
|
||||
|
@ -43,29 +43,22 @@ class MainWindow(QMainWindow):
|
|||
self.single_runner = SingleBuildRunner(self)
|
||||
self.current_runner = None
|
||||
|
||||
self.bisect_runner.worker_created.connect(
|
||||
self.ui.report_view.model().attach_bisector)
|
||||
self.single_runner.worker_created.connect(
|
||||
self.ui.report_view.model().attach_single_runner)
|
||||
self.bisect_runner.worker_created.connect(self.ui.report_view.model().attach_bisector)
|
||||
self.single_runner.worker_created.connect(self.ui.report_view.model().attach_single_runner)
|
||||
|
||||
self.ui.report_view.model().need_evaluate_editor.connect(
|
||||
self.bisect_runner.open_evaluate_editor)
|
||||
|
||||
self.ui.report_view.step_report_changed.connect(
|
||||
self.ui.build_info_browser.update_content)
|
||||
self.report_delegate = ReportItemDelegate()
|
||||
self.report_delegate.got_verdict.connect(
|
||||
self.bisect_runner.set_verdict
|
||||
self.bisect_runner.open_evaluate_editor
|
||||
)
|
||||
|
||||
self.ui.report_view.step_report_changed.connect(self.ui.build_info_browser.update_content)
|
||||
self.report_delegate = ReportItemDelegate()
|
||||
self.report_delegate.got_verdict.connect(self.bisect_runner.set_verdict)
|
||||
self.ui.report_view.setItemDelegateForColumn(0, self.report_delegate)
|
||||
|
||||
for runner in (self.bisect_runner, self.single_runner):
|
||||
runner.running_state_changed.connect(
|
||||
self.ui.actionStart_a_new_bisection.setDisabled)
|
||||
runner.running_state_changed.connect(
|
||||
self.ui.actionStop_the_bisection.setEnabled)
|
||||
runner.running_state_changed.connect(
|
||||
self.ui.actionRun_a_single_build.setDisabled)
|
||||
runner.running_state_changed.connect(self.ui.actionStart_a_new_bisection.setDisabled)
|
||||
runner.running_state_changed.connect(self.ui.actionStop_the_bisection.setEnabled)
|
||||
runner.running_state_changed.connect(self.ui.actionRun_a_single_build.setDisabled)
|
||||
|
||||
self.persist = mkdtemp()
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import sys
|
||||
import os
|
||||
import sys
|
||||
|
||||
# only True when running from frozen (bundled) app
|
||||
IS_FROZEN = getattr(sys, 'frozen', False)
|
||||
IS_FROZEN = getattr(sys, "frozen", False)
|
||||
|
||||
|
||||
def cacert_path():
|
||||
|
@ -15,11 +15,12 @@ def patch():
|
|||
# patch requests.request so taskcluster can use the right cacert.pem file.
|
||||
if IS_FROZEN:
|
||||
import requests
|
||||
|
||||
pem = cacert_path()
|
||||
old_request = requests.request
|
||||
|
||||
def _patched_request(*args, **kwargs):
|
||||
kwargs['verify'] = pem
|
||||
kwargs["verify"] = pem
|
||||
return old_request(*args, **kwargs)
|
||||
|
||||
requests.request = _patched_request
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
from PySide2.QtCore import QAbstractTableModel, QModelIndex, Qt, \
|
||||
Slot
|
||||
from PySide2.QtWidgets import QFileDialog, QWidget
|
||||
from mozprofile.prefs import Preferences
|
||||
from PySide2.QtCore import QAbstractTableModel, QModelIndex, Qt, Slot
|
||||
from PySide2.QtWidgets import QFileDialog, QWidget
|
||||
|
||||
from mozregui.ui.pref_editor import Ui_PrefEditor
|
||||
|
||||
|
@ -10,6 +9,7 @@ class PreferencesModel(QAbstractTableModel):
|
|||
"""
|
||||
A Qt model that can edit preferences.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
QAbstractTableModel.__init__(self)
|
||||
self.prefs = []
|
||||
|
@ -22,7 +22,7 @@ class PreferencesModel(QAbstractTableModel):
|
|||
|
||||
def headerData(self, section, orientation, role=Qt.DisplayRole):
|
||||
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
|
||||
return ('name', 'value')[section]
|
||||
return ("name", "value")[section]
|
||||
|
||||
def data(self, index, role=Qt.DisplayRole):
|
||||
if role in (Qt.DisplayRole, Qt.EditRole):
|
||||
|
@ -52,15 +52,14 @@ class PreferencesModel(QAbstractTableModel):
|
|||
def add_empty_pref(self):
|
||||
nb_prefs = len(self.prefs)
|
||||
self.beginInsertRows(QModelIndex(), nb_prefs, nb_prefs)
|
||||
self.prefs.append(('', ''))
|
||||
self.prefs.append(("", ""))
|
||||
self.endInsertRows()
|
||||
|
||||
def add_prefs_from_file(self, fname):
|
||||
prefs = Preferences.read(fname)
|
||||
if prefs:
|
||||
nb_prefs = len(self.prefs)
|
||||
self.beginInsertRows(QModelIndex(), nb_prefs,
|
||||
nb_prefs + len(prefs) - 1)
|
||||
self.beginInsertRows(QModelIndex(), nb_prefs, nb_prefs + len(prefs) - 1)
|
||||
self.prefs.extend(list(prefs.items()))
|
||||
self.endInsertRows()
|
||||
|
||||
|
@ -75,6 +74,7 @@ class PreferencesWidgetEditor(QWidget):
|
|||
A widget to edit preferences, using PreferencesModel in a table view
|
||||
and buttons to let the user interact.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
self.ui = Ui_PrefEditor()
|
||||
|
@ -93,9 +93,7 @@ class PreferencesWidgetEditor(QWidget):
|
|||
@Slot()
|
||||
def add_prefs_from_file(self):
|
||||
(fileName, _) = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"Choose a preference file",
|
||||
filter="pref file (*.json *.ini)",
|
||||
self, "Choose a preference file", filter="pref file (*.json *.ini)",
|
||||
)
|
||||
if fileName:
|
||||
self.pref_model.add_prefs_from_file(fileName)
|
||||
|
@ -103,8 +101,7 @@ class PreferencesWidgetEditor(QWidget):
|
|||
@Slot()
|
||||
def remove_selected_prefs(self):
|
||||
selected_rows = sorted(
|
||||
set(i.row() for i in self.ui.pref_view.selectedIndexes()),
|
||||
reverse=True
|
||||
set(i.row() for i in self.ui.pref_view.selectedIndexes()), reverse=True
|
||||
)
|
||||
for row in selected_rows:
|
||||
self.pref_model.remove_pref(row)
|
||||
|
@ -113,7 +110,7 @@ class PreferencesWidgetEditor(QWidget):
|
|||
return self.pref_model.prefs[:]
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
from PySide2.QtWidgets import QApplication
|
||||
|
||||
app = QApplication([])
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
from PySide2.QtCore import (QAbstractTableModel, QModelIndex, Qt,
|
||||
Slot, Signal, QUrl)
|
||||
from PySide2.QtGui import QDesktopServices, QColor
|
||||
from PySide2.QtCore import QAbstractTableModel, QModelIndex, Qt, QUrl, Signal, Slot
|
||||
from PySide2.QtGui import QColor, QDesktopServices
|
||||
from PySide2.QtWidgets import QTableView, QTextBrowser
|
||||
|
||||
from mozregression.bisector import NightlyHandler
|
||||
|
@ -19,6 +18,7 @@ class ReportItem(object):
|
|||
"""
|
||||
A base item in the report view
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.data = {}
|
||||
self.downloading = False
|
||||
|
@ -27,10 +27,10 @@ class ReportItem(object):
|
|||
|
||||
def update_pushlogurl(self, bisection):
|
||||
if bisection.handler.found_repo:
|
||||
self.data['pushlog_url'] = bisection.handler.get_pushlog_url()
|
||||
self.data["pushlog_url"] = bisection.handler.get_pushlog_url()
|
||||
else:
|
||||
self.data['pushlog_url'] = 'Not available'
|
||||
self.data['repo_name'] = bisection.build_range[0].repo_name
|
||||
self.data["pushlog_url"] = "Not available"
|
||||
self.data["repo_name"] = bisection.build_range[0].repo_name
|
||||
|
||||
def status_text(self):
|
||||
return "Looking for build data..."
|
||||
|
@ -43,6 +43,7 @@ class StartItem(ReportItem):
|
|||
"""
|
||||
Report a started bisection
|
||||
"""
|
||||
|
||||
def update_pushlogurl(self, bisection):
|
||||
ReportItem.update_pushlogurl(self, bisection)
|
||||
handler = bisection.handler
|
||||
|
@ -50,7 +51,7 @@ class StartItem(ReportItem):
|
|||
self.build_type = "nightly"
|
||||
else:
|
||||
self.build_type = "integration"
|
||||
if self.build_type == 'nightly':
|
||||
if self.build_type == "nightly":
|
||||
self.first, self.last = handler.get_date_range()
|
||||
else:
|
||||
self.first, self.last = handler.get_range()
|
||||
|
@ -60,32 +61,31 @@ class StartItem(ReportItem):
|
|||
self.first, self.last = self.last, self.first
|
||||
|
||||
def status_text(self):
|
||||
if 'pushlog_url' not in self.data:
|
||||
if "pushlog_url" not in self.data:
|
||||
return ReportItem.status_text(self)
|
||||
return 'Bisecting on %s [%s - %s]' % (self.data['repo_name'],
|
||||
self.first, self.last)
|
||||
return "Bisecting on %s [%s - %s]" % (self.data["repo_name"], self.first, self.last,)
|
||||
|
||||
|
||||
class StepItem(ReportItem):
|
||||
"""
|
||||
Report a bisection step
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
ReportItem.__init__(self)
|
||||
self.state_text = 'Found'
|
||||
self.state_text = "Found"
|
||||
self.verdict = None
|
||||
|
||||
def status_text(self):
|
||||
if not self.data:
|
||||
return ReportItem.status_text(self)
|
||||
if self.data['build_type'] == 'nightly':
|
||||
desc = self.data['build_date']
|
||||
if self.data["build_type"] == "nightly":
|
||||
desc = self.data["build_date"]
|
||||
else:
|
||||
desc = self.data['changeset'][:8]
|
||||
desc = self.data["changeset"][:8]
|
||||
if self.verdict is not None:
|
||||
desc = '%s (verdict: %s)' % (desc, self.verdict)
|
||||
return "%s %s build: %s" % (self.state_text, self.data['repo_name'],
|
||||
desc)
|
||||
desc = "%s (verdict: %s)" % (desc, self.verdict)
|
||||
return "%s %s build: %s" % (self.state_text, self.data["repo_name"], desc)
|
||||
|
||||
|
||||
def _bulk_action_slots(action, slots, signal_object, slot_object):
|
||||
|
@ -111,44 +111,39 @@ class ReportModel(QAbstractTableModel):
|
|||
|
||||
@Slot(object)
|
||||
def attach_bisector(self, bisector):
|
||||
bisector_slots = ('step_started',
|
||||
'step_build_found',
|
||||
'step_testing',
|
||||
'step_finished',
|
||||
'started',
|
||||
'finished')
|
||||
downloader_slots = ('download_progress', )
|
||||
bisector_slots = (
|
||||
"step_started",
|
||||
"step_build_found",
|
||||
"step_testing",
|
||||
"step_finished",
|
||||
"started",
|
||||
"finished",
|
||||
)
|
||||
downloader_slots = ("download_progress",)
|
||||
|
||||
if bisector:
|
||||
self.attach_single_runner(None)
|
||||
_bulk_action_slots('connect',
|
||||
bisector_slots,
|
||||
bisector,
|
||||
self)
|
||||
_bulk_action_slots('connect',
|
||||
downloader_slots,
|
||||
bisector.download_manager,
|
||||
self)
|
||||
_bulk_action_slots("connect", bisector_slots, bisector, self)
|
||||
_bulk_action_slots("connect", downloader_slots, bisector.download_manager, self)
|
||||
|
||||
self.bisector = bisector
|
||||
|
||||
@Slot(object)
|
||||
def attach_single_runner(self, single_runner):
|
||||
sr_slots = ('started', 'step_build_found', 'step_testing')
|
||||
downloader_slots = ('download_progress', )
|
||||
sr_slots = ("started", "step_build_found", "step_testing")
|
||||
downloader_slots = ("download_progress",)
|
||||
|
||||
if single_runner:
|
||||
self.attach_bisector(None)
|
||||
_bulk_action_slots('connect', sr_slots, single_runner, self)
|
||||
_bulk_action_slots('connect', downloader_slots,
|
||||
single_runner.download_manager, self)
|
||||
_bulk_action_slots("connect", sr_slots, single_runner, self)
|
||||
_bulk_action_slots("connect", downloader_slots, single_runner.download_manager, self)
|
||||
|
||||
self.single_runner = single_runner
|
||||
|
||||
@Slot(object, int, int)
|
||||
def download_progress(self, dl, current, total):
|
||||
item = self.items[-1]
|
||||
item.state_text = 'Downloading'
|
||||
item.state_text = "Downloading"
|
||||
item.downloading = True
|
||||
item.set_progress(current, total)
|
||||
self.update_item(item)
|
||||
|
@ -168,9 +163,7 @@ class ReportModel(QAbstractTableModel):
|
|||
return item.status_text()
|
||||
elif role == Qt.BackgroundRole:
|
||||
if isinstance(item, StepItem) and item.verdict:
|
||||
return VERDICT_TO_ROW_COLORS.get(
|
||||
str(item.verdict),
|
||||
GRAY_WHITE)
|
||||
return VERDICT_TO_ROW_COLORS.get(str(item.verdict), GRAY_WHITE)
|
||||
else:
|
||||
return GRAY_WHITE
|
||||
|
||||
|
@ -196,7 +189,7 @@ class ReportModel(QAbstractTableModel):
|
|||
last_item = self.items[-1]
|
||||
if isinstance(last_item, StepItem):
|
||||
# update the pushlog for the start step
|
||||
if hasattr(bisection, 'handler'):
|
||||
if hasattr(bisection, "handler"):
|
||||
last_item.update_pushlogurl(bisection)
|
||||
self.update_item(last_item)
|
||||
# and add a new step
|
||||
|
@ -208,7 +201,7 @@ class ReportModel(QAbstractTableModel):
|
|||
|
||||
if isinstance(last_item, StartItem):
|
||||
# update the pushlog for the start step
|
||||
if hasattr(bisection, 'handler'):
|
||||
if hasattr(bisection, "handler"):
|
||||
last_item.update_pushlogurl(bisection)
|
||||
self.update_item(last_item)
|
||||
else:
|
||||
|
@ -230,14 +223,14 @@ class ReportModel(QAbstractTableModel):
|
|||
last_item = self.items[-1]
|
||||
last_item.downloading = False
|
||||
last_item.waiting_evaluation = True
|
||||
last_item.state_text = 'Testing'
|
||||
last_item.state_text = "Testing"
|
||||
# we may have more build data now that the build has been installed
|
||||
last_item.data.update(build_infos.to_dict())
|
||||
if hasattr(bisection, 'handler'):
|
||||
if hasattr(bisection, "handler"):
|
||||
last_item.update_pushlogurl(bisection)
|
||||
|
||||
self.update_item(last_item)
|
||||
if hasattr(bisection, 'handler'):
|
||||
if hasattr(bisection, "handler"):
|
||||
# not a single runner
|
||||
index = self.createIndex(self.rowCount() - 1, 0)
|
||||
self.need_evaluate_editor.emit(True, index)
|
||||
|
@ -247,10 +240,10 @@ class ReportModel(QAbstractTableModel):
|
|||
# step finished, just store the verdict
|
||||
item = self.items[-1]
|
||||
item.waiting_evaluation = False
|
||||
item.state_text = 'Tested'
|
||||
item.state_text = "Tested"
|
||||
item.verdict = verdict
|
||||
self.update_item(item)
|
||||
if hasattr(bisection, 'handler'):
|
||||
if hasattr(bisection, "handler"):
|
||||
# not a single runner
|
||||
index = self.createIndex(self.rowCount() - 1, 0)
|
||||
self.need_evaluate_editor.emit(False, index)
|
||||
|
@ -307,12 +300,12 @@ class BuildInfoTextBrowser(QTextBrowser):
|
|||
for k in sorted(item.data):
|
||||
v = item.data[k]
|
||||
if v is not None:
|
||||
html += '<strong>%s</strong>: ' % k
|
||||
html += "<strong>%s</strong>: " % k
|
||||
if isinstance(v, str):
|
||||
url = QUrl(v)
|
||||
if url.isValid() and url.scheme():
|
||||
v = '<a href="%s">%s</a>' % (v, v)
|
||||
html += '{}<br>'.format(v)
|
||||
html += "{}<br>".format(v)
|
||||
self.setHtml(html)
|
||||
|
||||
@Slot(QUrl)
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
from PySide2.QtCore import QRect, Qt, Signal
|
||||
from PySide2.QtGui import QIcon, QPainter, QPixmap
|
||||
from PySide2.QtWidgets import QStyledItemDelegate, QStyleOptionProgressBar, \
|
||||
QApplication, QStyle, QWidget
|
||||
from PySide2.QtCore import Qt, QRect, Signal
|
||||
from PySide2.QtWidgets import (
|
||||
QApplication,
|
||||
QStyle,
|
||||
QStyledItemDelegate,
|
||||
QStyleOptionProgressBar,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from mozregui.ui.ask_verdict import Ui_AskVerdict
|
||||
from mozregui.report import VERDICT_TO_ROW_COLORS
|
||||
from mozregui.ui.ask_verdict import Ui_AskVerdict
|
||||
|
||||
VERDICTS = ("good", "bad", "skip", "retry", "other...")
|
||||
|
||||
|
@ -33,7 +38,7 @@ class AskVerdict(QWidget):
|
|||
AskVerdict.icons_cache[text] = QIcon(pixmap)
|
||||
|
||||
# set combo verdict
|
||||
for text in ('other...', 'skip', 'retry'):
|
||||
for text in ("other...", "skip", "retry"):
|
||||
self.ui.comboVerdict.addItem(AskVerdict.icons_cache[text], text)
|
||||
model = self.ui.comboVerdict.model()
|
||||
model.itemFromIndex(model.index(0, 0)).setSelectable(False)
|
||||
|
@ -47,15 +52,11 @@ class AskVerdict(QWidget):
|
|||
self.ui.badVerdict.setIcon(AskVerdict.icons_cache["bad"])
|
||||
|
||||
def on_dropdown_item_activated(self):
|
||||
self.delegate.got_verdict.emit(
|
||||
str(self.ui.comboVerdict.currentText())[0]
|
||||
)
|
||||
self.delegate.got_verdict.emit(str(self.ui.comboVerdict.currentText())[0])
|
||||
self.emitted = True
|
||||
|
||||
def on_good_bad_button_clicked(self):
|
||||
self.delegate.got_verdict.emit(
|
||||
str(self.sender().text())[0]
|
||||
)
|
||||
self.delegate.got_verdict.emit(str(self.sender().text())[0])
|
||||
self.emitted = True
|
||||
|
||||
|
||||
|
@ -69,8 +70,7 @@ class ReportItemDelegate(QStyledItemDelegate):
|
|||
if index.model().get_item(index).waiting_evaluation:
|
||||
return AskVerdict(parent, self)
|
||||
else:
|
||||
return QStyledItemDelegate.createEditor(self, parent, option,
|
||||
index)
|
||||
return QStyledItemDelegate.createEditor(self, parent, option, index)
|
||||
|
||||
def paint(self, painter, option, index):
|
||||
# if item selected, override default theme
|
||||
|
@ -88,17 +88,14 @@ class ReportItemDelegate(QStyledItemDelegate):
|
|||
progressBarHeight = option.rect.height() / 4
|
||||
progressBarOption.rect = QRect(
|
||||
option.rect.x(),
|
||||
option.rect.y() +
|
||||
(option.rect.height() - progressBarHeight),
|
||||
option.rect.y() + (option.rect.height() - progressBarHeight),
|
||||
option.rect.width(),
|
||||
progressBarHeight)
|
||||
progressBarHeight,
|
||||
)
|
||||
progressBarOption.minimum = 0
|
||||
progressBarOption.maximum = 100
|
||||
progressBarOption.textAlignment = Qt.AlignCenter
|
||||
|
||||
progressBarOption.progress = item.progress
|
||||
|
||||
QApplication.style().drawControl(
|
||||
QStyle.CE_ProgressBar,
|
||||
progressBarOption,
|
||||
painter)
|
||||
QApplication.style().drawControl(QStyle.CE_ProgressBar, progressBarOption, painter)
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
from PySide2.QtCore import QObject, Slot, Signal
|
||||
from PySide2.QtCore import QObject, Signal, Slot
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
|
||||
from mozregression.errors import MozRegressionError
|
||||
from mozregression.dates import is_date_or_datetime
|
||||
from mozregression.fetch_build_info import (NightlyInfoFetcher,
|
||||
IntegrationInfoFetcher)
|
||||
|
||||
from mozregression.errors import MozRegressionError
|
||||
from mozregression.fetch_build_info import IntegrationInfoFetcher, NightlyInfoFetcher
|
||||
from mozregui.build_runner import AbstractBuildRunner
|
||||
|
||||
|
||||
|
@ -58,14 +56,12 @@ class SingleBuildRunner(AbstractBuildRunner):
|
|||
|
||||
def init_worker(self, fetch_config, options):
|
||||
AbstractBuildRunner.init_worker(self, fetch_config, options)
|
||||
self.download_manager.download_finished.connect(
|
||||
self.worker._on_downloaded)
|
||||
self.worker.launch_arg = options.pop('launch')
|
||||
self.download_manager.download_finished.connect(self.worker._on_downloaded)
|
||||
self.worker.launch_arg = options.pop("launch")
|
||||
# evaluate_started will be called if we have an error
|
||||
self.test_runner.evaluate_started.connect(self.on_error)
|
||||
self.worker.error.connect(self.on_error)
|
||||
if is_date_or_datetime(self.worker.launch_arg) and \
|
||||
fetch_config.should_use_archive():
|
||||
if is_date_or_datetime(self.worker.launch_arg) and fetch_config.should_use_archive():
|
||||
return self.worker.launch_nightlies
|
||||
else:
|
||||
return self.worker.launch_integration
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
from PySide2.QtCore import Qt, Signal
|
||||
from PySide2.QtGui import QBrush
|
||||
from PySide2.QtWidgets import (QGraphicsRectItem, QGraphicsScene, QGraphicsView,
|
||||
QToolTip, QDialog, QMessageBox)
|
||||
from PySide2.QtWidgets import (
|
||||
QDialog,
|
||||
QGraphicsRectItem,
|
||||
QGraphicsScene,
|
||||
QGraphicsView,
|
||||
QMessageBox,
|
||||
QToolTip,
|
||||
)
|
||||
|
||||
|
||||
class BuildItem(QGraphicsRectItem):
|
||||
|
@ -40,7 +46,7 @@ class SkipChooserScene(QGraphicsScene):
|
|||
future,
|
||||
column * BuildItem.WIDTH + self.SPACE * column,
|
||||
row * BuildItem.WIDTH + self.SPACE * row,
|
||||
selectable=i not in bounds
|
||||
selectable=i not in bounds,
|
||||
)
|
||||
if i == mid:
|
||||
item.setBrush(QBrush(Qt.blue))
|
||||
|
@ -89,6 +95,7 @@ class SkipDialog(QDialog):
|
|||
QDialog.__init__(self, parent)
|
||||
assert len(build_range) > 3
|
||||
from mozregui.ui.skip_dialog import Ui_SkipDialog
|
||||
|
||||
self.ui = Ui_SkipDialog()
|
||||
self.ui.setupUi(self)
|
||||
self.scene = SkipChooserScene(build_range)
|
||||
|
@ -102,9 +109,7 @@ class SkipDialog(QDialog):
|
|||
self.ui.lbl_status.setText("Selected build to test, %s" % items[0])
|
||||
|
||||
def build_index(self, item):
|
||||
return self.scene.build_range.future_build_infos.index(
|
||||
item.future_build_info
|
||||
)
|
||||
return self.scene.build_range.future_build_infos.index(item.future_build_info)
|
||||
|
||||
def choose_next_build(self):
|
||||
if self.exec_() == self.Accepted:
|
||||
|
@ -113,18 +118,23 @@ class SkipDialog(QDialog):
|
|||
return self.build_index(items[0])
|
||||
|
||||
def closeEvent(self, evt):
|
||||
if QMessageBox.warning(
|
||||
self, "Stop the bisection ?",
|
||||
if (
|
||||
QMessageBox.warning(
|
||||
self,
|
||||
"Stop the bisection ?",
|
||||
"Closing this dialog will end the bisection. Are you sure"
|
||||
" you want to end the bisection now ?",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No) == QMessageBox.Yes:
|
||||
QMessageBox.No,
|
||||
)
|
||||
== QMessageBox.Yes
|
||||
):
|
||||
evt.accept()
|
||||
else:
|
||||
evt.ignore()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
from mozregression.build_range import BuildRange, FutureBuildInfo
|
||||
|
||||
class FInfo(FutureBuildInfo):
|
||||
|
@ -134,6 +144,7 @@ if __name__ == '__main__':
|
|||
build_range = BuildRange(None, [FInfo(None, i) for i in range(420)])
|
||||
|
||||
from PySide2.QtWidgets import QApplication, QMainWindow
|
||||
|
||||
app = QApplication([])
|
||||
win = QMainWindow()
|
||||
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
from PySide2.QtCore import QDir
|
||||
from PySide2.QtWidgets import (QCompleter, QLineEdit, QPushButton,
|
||||
QHBoxLayout, QFileDialog,
|
||||
QFileSystemModel, QWidget)
|
||||
from PySide2.QtWidgets import (
|
||||
QCompleter,
|
||||
QFileDialog,
|
||||
QFileSystemModel,
|
||||
QHBoxLayout,
|
||||
QLineEdit,
|
||||
QPushButton,
|
||||
QWidget,
|
||||
)
|
||||
|
||||
from mozregression.releases import date_of_release, releases
|
||||
from mozregression.dates import parse_date
|
||||
from mozregression.errors import DateFormatError
|
||||
from mozregression.releases import date_of_release, releases
|
||||
from mozregui.ui.build_selection_helper import Ui_BuildSelectionHelper
|
||||
|
||||
|
||||
|
@ -13,6 +19,7 @@ class FSLineEdit(QLineEdit):
|
|||
"""
|
||||
A line edit with auto completion for file system folders.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QLineEdit.__init__(self, parent)
|
||||
self.fsmodel = QFileSystemModel()
|
||||
|
@ -20,8 +27,7 @@ class FSLineEdit(QLineEdit):
|
|||
self.completer = QCompleter()
|
||||
self.completer.setModel(self.fsmodel)
|
||||
self.setCompleter(self.completer)
|
||||
self.fsmodel.setFilter(QDir.Drives | QDir.AllDirs | QDir.Hidden |
|
||||
QDir.NoDotAndDotDot)
|
||||
self.fsmodel.setFilter(QDir.Drives | QDir.AllDirs | QDir.Hidden | QDir.NoDotAndDotDot)
|
||||
|
||||
def setPath(self, path):
|
||||
self.setText(path)
|
||||
|
@ -33,6 +39,7 @@ class DirectorySelectWidget(QWidget):
|
|||
A FSLineEdit with a "browse" button on the right. Allow to select a
|
||||
directory.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
layout = QHBoxLayout(self)
|
||||
|
@ -46,9 +53,7 @@ class DirectorySelectWidget(QWidget):
|
|||
self.button.clicked.connect(self.browse_dialog)
|
||||
|
||||
def browse_dialog(self):
|
||||
path = QFileDialog.getExistingDirectory(
|
||||
self, "Find file"
|
||||
)
|
||||
path = QFileDialog.getExistingDirectory(self, "Find file")
|
||||
if path:
|
||||
self.line_edit.setPath(path)
|
||||
|
||||
|
@ -58,21 +63,20 @@ class BuildSelection(QWidget):
|
|||
Allow to select a date, a build id, a release number or an arbitrary
|
||||
changeset.
|
||||
"""
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QWidget.__init__(self, parent)
|
||||
self.ui = Ui_BuildSelectionHelper()
|
||||
self.ui.setupUi(self)
|
||||
self.ui.release.addItems([str(k) for k in sorted(releases())])
|
||||
self.ui.combo_helper.currentIndexChanged.connect(
|
||||
self.ui.stackedWidget.setCurrentIndex)
|
||||
self.ui.combo_helper.currentIndexChanged.connect(self.ui.stackedWidget.setCurrentIndex)
|
||||
|
||||
def get_value(self):
|
||||
currentw = self.ui.stackedWidget.currentWidget()
|
||||
if currentw == self.ui.s_date:
|
||||
return self.ui.date.date().toPython()
|
||||
elif currentw == self.ui.s_release:
|
||||
return parse_date(
|
||||
date_of_release(str(self.ui.release.currentText())))
|
||||
return parse_date(date_of_release(str(self.ui.release.currentText())))
|
||||
elif currentw == self.ui.s_buildid:
|
||||
buildid = self.ui.buildid.text()
|
||||
try:
|
||||
|
|
|
@ -1,23 +1,25 @@
|
|||
from __future__ import absolute_import
|
||||
import mozinfo
|
||||
import datetime
|
||||
from PySide2.QtWidgets import (QApplication, QCompleter, QWizard, QWizardPage, QMessageBox)
|
||||
from PySide2.QtCore import QStringListModel, QDate, Slot, Qt, SIGNAL
|
||||
|
||||
from .ui.intro import Ui_Intro
|
||||
import datetime
|
||||
|
||||
import mozinfo
|
||||
from PySide2.QtCore import SIGNAL, QDate, QStringListModel, Qt, Slot
|
||||
from PySide2.QtWidgets import QApplication, QCompleter, QMessageBox, QWizard, QWizardPage
|
||||
|
||||
from mozregression.branches import get_branches
|
||||
from mozregression.dates import to_datetime
|
||||
from mozregression.errors import DateFormatError, LauncherNotRunnable
|
||||
from mozregression.fetch_configs import REGISTRY, create_config
|
||||
from mozregression.launchers import REGISTRY as LAUNCHER_REGISTRY
|
||||
|
||||
from .ui.build_selection import Ui_BuildSelectionPage
|
||||
from .ui.intro import Ui_Intro
|
||||
from .ui.profile import Ui_Profile
|
||||
from .ui.single_build_selection import Ui_SingleBuildSelectionPage
|
||||
|
||||
from mozregression.fetch_configs import create_config, REGISTRY
|
||||
from mozregression.launchers import REGISTRY as LAUNCHER_REGISTRY
|
||||
from mozregression.errors import LauncherNotRunnable, DateFormatError
|
||||
from mozregression.dates import to_datetime
|
||||
from mozregression.branches import get_branches
|
||||
|
||||
|
||||
def resolve_obj_name(obj, name):
|
||||
names = name.split('.')
|
||||
names = name.split(".")
|
||||
while names:
|
||||
obj = getattr(obj, names.pop(0))
|
||||
return obj
|
||||
|
@ -25,8 +27,8 @@ def resolve_obj_name(obj, name):
|
|||
|
||||
class WizardPage(QWizardPage):
|
||||
UI_CLASS = None
|
||||
TITLE = ''
|
||||
SUBTITLE = ''
|
||||
TITLE = ""
|
||||
SUBTITLE = ""
|
||||
FIELDS = {}
|
||||
|
||||
def __init__(self):
|
||||
|
@ -52,27 +54,31 @@ class WizardPage(QWizardPage):
|
|||
class IntroPage(WizardPage):
|
||||
UI_CLASS = Ui_Intro
|
||||
TITLE = "Basic configuration"
|
||||
SUBTITLE = ("Please choose an application and other options to specify"
|
||||
" what you want to test.")
|
||||
FIELDS = {'application': 'app_combo', "repository": "repository",
|
||||
'bits': 'bits_combo', "build_type": "build_type", "url": "url"}
|
||||
SUBTITLE = "Please choose an application and other options to specify" " what you want to test."
|
||||
FIELDS = {
|
||||
"application": "app_combo",
|
||||
"repository": "repository",
|
||||
"bits": "bits_combo",
|
||||
"build_type": "build_type",
|
||||
"url": "url",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
WizardPage.__init__(self)
|
||||
self.fetch_config = None
|
||||
self.app_model = QStringListModel(
|
||||
REGISTRY.names(
|
||||
lambda klass: not getattr(klass, 'disable_in_gui', None)))
|
||||
REGISTRY.names(lambda klass: not getattr(klass, "disable_in_gui", None))
|
||||
)
|
||||
self.ui.app_combo.setModel(self.app_model)
|
||||
if mozinfo.bits == 64:
|
||||
if mozinfo.os == 'mac':
|
||||
self.bits_model = QStringListModel(['64'])
|
||||
if mozinfo.os == "mac":
|
||||
self.bits_model = QStringListModel(["64"])
|
||||
bits_index = 0
|
||||
else:
|
||||
self.bits_model = QStringListModel(['32', '64'])
|
||||
self.bits_model = QStringListModel(["32", "64"])
|
||||
bits_index = 1
|
||||
elif mozinfo.bits == 32:
|
||||
self.bits_model = QStringListModel(['32'])
|
||||
self.bits_model = QStringListModel(["32"])
|
||||
bits_index = 0
|
||||
self.ui.bits_combo.setModel(self.bits_model)
|
||||
self.ui.bits_combo.setCurrentIndex(bits_index)
|
||||
|
@ -80,8 +86,7 @@ class IntroPage(WizardPage):
|
|||
|
||||
self.ui.app_combo.currentIndexChanged.connect(self._set_fetch_config)
|
||||
self.ui.bits_combo.currentIndexChanged.connect(self._set_fetch_config)
|
||||
self.ui.app_combo.setCurrentIndex(
|
||||
self.ui.app_combo.findText("firefox"))
|
||||
self.ui.app_combo.setCurrentIndex(self.ui.app_combo.findText("firefox"))
|
||||
|
||||
self.ui.repository.textChanged.connect(self._on_repo_changed)
|
||||
|
||||
|
@ -91,13 +96,12 @@ class IntroPage(WizardPage):
|
|||
QApplication.instance().focusChanged.connect(self._on_focus_changed)
|
||||
|
||||
def _on_repo_changed(self, text):
|
||||
enable_release = (not text or text == 'mozilla-central')
|
||||
enable_release = not text or text == "mozilla-central"
|
||||
build_select_page = self.wizard().page(2)
|
||||
if type(build_select_page) == SingleBuildSelectionPage:
|
||||
build_menus = [build_select_page.ui.build]
|
||||
else:
|
||||
build_menus = [build_select_page.ui.start,
|
||||
build_select_page.ui.end]
|
||||
build_menus = [build_select_page.ui.start, build_select_page.ui.end]
|
||||
for menu in build_menus:
|
||||
menu.ui.combo_helper.model().item(1).setEnabled(enable_release)
|
||||
if menu.ui.combo_helper.currentIndex() == 1:
|
||||
|
@ -112,11 +116,9 @@ class IntroPage(WizardPage):
|
|||
app_name = str(self.ui.app_combo.currentText())
|
||||
bits = int(self.ui.bits_combo.currentText())
|
||||
|
||||
self.fetch_config = create_config(app_name, mozinfo.os, bits,
|
||||
mozinfo.processor)
|
||||
self.fetch_config = create_config(app_name, mozinfo.os, bits, mozinfo.processor)
|
||||
|
||||
self.build_type_model = QStringListModel(
|
||||
self.fetch_config.available_build_types())
|
||||
self.build_type_model = QStringListModel(self.fetch_config.available_build_types())
|
||||
self.ui.build_type.setModel(self.build_type_model)
|
||||
|
||||
if not self.fetch_config.available_bits():
|
||||
|
@ -127,7 +129,7 @@ class IntroPage(WizardPage):
|
|||
self.ui.label_4.show()
|
||||
|
||||
# URL doesn't make sense for Thunderbird
|
||||
if app_name == 'thunderbird':
|
||||
if app_name == "thunderbird":
|
||||
self.ui.url.hide()
|
||||
self.ui.url_label.hide()
|
||||
else:
|
||||
|
@ -141,41 +143,37 @@ class IntroPage(WizardPage):
|
|||
launcher_class.check_is_runnable()
|
||||
return True
|
||||
except LauncherNotRunnable as exc:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"%s is not runnable" % app_name,
|
||||
str(exc)
|
||||
)
|
||||
QMessageBox.critical(self, "%s is not runnable" % app_name, str(exc))
|
||||
return False
|
||||
|
||||
|
||||
class ProfilePage(WizardPage):
|
||||
UI_CLASS = Ui_Profile
|
||||
TITLE = "Profile selection"
|
||||
SUBTITLE = ("Choose a specific profile. You can choose an existing profile"
|
||||
", or let this blank to use a new one.")
|
||||
FIELDS = {"profile": "profile_widget.line_edit",
|
||||
"profile_persistence": "profile_persistence_combo"}
|
||||
SUBTITLE = (
|
||||
"Choose a specific profile. You can choose an existing profile"
|
||||
", or let this blank to use a new one."
|
||||
)
|
||||
FIELDS = {
|
||||
"profile": "profile_widget.line_edit",
|
||||
"profile_persistence": "profile_persistence_combo",
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
WizardPage.__init__(self)
|
||||
profile_persistence_options = ["clone",
|
||||
"clone-first",
|
||||
"reuse"]
|
||||
self.profile_persistence_model = \
|
||||
QStringListModel(profile_persistence_options)
|
||||
self.ui.profile_persistence_combo.setModel(
|
||||
self.profile_persistence_model)
|
||||
profile_persistence_options = ["clone", "clone-first", "reuse"]
|
||||
self.profile_persistence_model = QStringListModel(profile_persistence_options)
|
||||
self.ui.profile_persistence_combo.setModel(self.profile_persistence_model)
|
||||
self.ui.profile_persistence_combo.setCurrentIndex(0)
|
||||
|
||||
def set_options(self, options):
|
||||
WizardPage.set_options(self, options)
|
||||
# get the prefs
|
||||
options['preferences'] = self.get_prefs()
|
||||
options["preferences"] = self.get_prefs()
|
||||
# get the addons
|
||||
options['addons'] = self.get_addons()
|
||||
options["addons"] = self.get_addons()
|
||||
# get the profile-persistence
|
||||
options['profile_persistence'] = self.get_profile_persistence()
|
||||
options["profile_persistence"] = self.get_profile_persistence()
|
||||
|
||||
def get_prefs(self):
|
||||
return self.ui.pref_widget.get_prefs()
|
||||
|
@ -190,8 +188,8 @@ class ProfilePage(WizardPage):
|
|||
class BuildSelectionPage(WizardPage):
|
||||
UI_CLASS = Ui_BuildSelectionPage
|
||||
TITLE = "Build selection"
|
||||
SUBTITLE = ("Select the range to bisect.")
|
||||
FIELDS = {'find_fix': 'find_fix'}
|
||||
SUBTITLE = "Select the range to bisect."
|
||||
FIELDS = {"find_fix": "find_fix"}
|
||||
|
||||
def __init__(self):
|
||||
WizardPage.__init__(self)
|
||||
|
@ -202,10 +200,10 @@ class BuildSelectionPage(WizardPage):
|
|||
|
||||
def set_options(self, options):
|
||||
WizardPage.set_options(self, options)
|
||||
options['good'] = self.get_start()
|
||||
options['bad'] = self.get_end()
|
||||
if options['find_fix']:
|
||||
options['good'], options['bad'] = options['bad'], options['good']
|
||||
options["good"] = self.get_start()
|
||||
options["bad"] = self.get_end()
|
||||
if options["find_fix"]:
|
||||
options["good"], options["bad"] = options["bad"], options["good"]
|
||||
|
||||
@Slot()
|
||||
def change_labels(self):
|
||||
|
@ -239,15 +237,11 @@ class BuildSelectionPage(WizardPage):
|
|||
if end_date <= current:
|
||||
return True
|
||||
else:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Error",
|
||||
"You can't define a date in the future.")
|
||||
QMessageBox.critical(self, "Error", "You can't define a date in the future.")
|
||||
else:
|
||||
QMessageBox.critical(
|
||||
self,
|
||||
"Error",
|
||||
"The first date must be earlier than the second one.")
|
||||
self, "Error", "The first date must be earlier than the second one."
|
||||
)
|
||||
|
||||
return False
|
||||
|
||||
|
@ -259,8 +253,7 @@ class Wizard(QWizard):
|
|||
self.resize(800, 600)
|
||||
|
||||
# associate current text to comboboxes fields instead of current index
|
||||
self.setDefaultProperty("QComboBox", "currentText",
|
||||
SIGNAL('currentIndexChanged(QString)'))
|
||||
self.setDefaultProperty("QComboBox", "currentText", SIGNAL("currentIndexChanged(QString)"))
|
||||
|
||||
for klass in class_pages:
|
||||
self.addPage(klass())
|
||||
|
@ -271,32 +264,33 @@ class Wizard(QWizard):
|
|||
self.page(page_id).set_options(options)
|
||||
|
||||
fetch_config = self.page(self.pageIds()[0]).fetch_config
|
||||
fetch_config.set_repo(options['repository'])
|
||||
fetch_config.set_build_type(options['build_type'])
|
||||
fetch_config.set_repo(options["repository"])
|
||||
fetch_config.set_build_type(options["build_type"])
|
||||
|
||||
# create a profile if required
|
||||
launcher_class = LAUNCHER_REGISTRY.get(fetch_config.app_name)
|
||||
if options['profile_persistence'] in ('clone-first', 'reuse'):
|
||||
options['profile'] = launcher_class.create_profile(
|
||||
profile=options['profile'],
|
||||
addons=options['addons'],
|
||||
preferences=options['preferences'],
|
||||
clone=options['profile_persistence'] == 'clone-first')
|
||||
if options["profile_persistence"] in ("clone-first", "reuse"):
|
||||
options["profile"] = launcher_class.create_profile(
|
||||
profile=options["profile"],
|
||||
addons=options["addons"],
|
||||
preferences=options["preferences"],
|
||||
clone=options["profile_persistence"] == "clone-first",
|
||||
)
|
||||
|
||||
return fetch_config, options
|
||||
|
||||
|
||||
class BisectionWizard(Wizard):
|
||||
def __init__(self, parent=None):
|
||||
Wizard.__init__(self, "Bisection wizard",
|
||||
(IntroPage, ProfilePage, BuildSelectionPage),
|
||||
parent=parent)
|
||||
Wizard.__init__(
|
||||
self, "Bisection wizard", (IntroPage, ProfilePage, BuildSelectionPage), parent=parent,
|
||||
)
|
||||
|
||||
|
||||
class SingleBuildSelectionPage(WizardPage):
|
||||
UI_CLASS = Ui_SingleBuildSelectionPage
|
||||
TITLE = "Build selection"
|
||||
SUBTITLE = ("Select the build you want to run.")
|
||||
SUBTITLE = "Select the build you want to run."
|
||||
|
||||
def __init__(self):
|
||||
WizardPage.__init__(self)
|
||||
|
@ -305,11 +299,14 @@ class SingleBuildSelectionPage(WizardPage):
|
|||
|
||||
def set_options(self, options):
|
||||
WizardPage.set_options(self, options)
|
||||
options['launch'] = self.ui.build.get_value()
|
||||
options["launch"] = self.ui.build.get_value()
|
||||
|
||||
|
||||
class SingleRunWizard(Wizard):
|
||||
def __init__(self, parent=None):
|
||||
Wizard.__init__(self, "Single run wizard",
|
||||
Wizard.__init__(
|
||||
self,
|
||||
"Single run wizard",
|
||||
(IntroPage, ProfilePage, SingleBuildSelectionPage),
|
||||
parent=parent)
|
||||
parent=parent,
|
||||
)
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from PySide2.QtWidgets import QApplication
|
||||
from PySide2.QtCore import QEventLoop, QTimer
|
||||
from contextlib import contextmanager
|
||||
|
||||
from PySide2.QtCore import QEventLoop, QTimer
|
||||
from PySide2.QtWidgets import QApplication
|
||||
|
||||
APP = QApplication([]) # we need an application to create widgets
|
||||
|
||||
|
||||
|
@ -15,9 +16,11 @@ def wait_signal(signal, timeout=1):
|
|||
|
||||
timed_out = []
|
||||
if timeout is not None:
|
||||
|
||||
def quit_with_error():
|
||||
timed_out.append(1)
|
||||
loop.quit()
|
||||
|
||||
QTimer.singleShot(timeout * 1000, quit_with_error)
|
||||
loop.exec_()
|
||||
if timed_out:
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import pytest
|
||||
import tempfile
|
||||
import mozfile
|
||||
|
||||
from PySide2.QtCore import Qt
|
||||
import mozfile
|
||||
import pytest
|
||||
from mock import patch
|
||||
from PySide2.QtCore import Qt
|
||||
|
||||
from mozregui.addons_editor import AddonsWidgetEditor
|
||||
|
||||
|
||||
|
@ -27,7 +28,7 @@ def test_create_addons_editor(addons_editor):
|
|||
@pytest.fixture
|
||||
def addons_file(request):
|
||||
# create a temp addons file
|
||||
f = tempfile.NamedTemporaryFile(suffix='.xpi', dir='.', delete=False)
|
||||
f = tempfile.NamedTemporaryFile(suffix=".xpi", dir=".", delete=False)
|
||||
f.close()
|
||||
request.addfinalizer(lambda: mozfile.remove(f.name))
|
||||
return f.name
|
||||
|
@ -37,26 +38,19 @@ def test_add_addon(qtbot, addons_editor, addons_file):
|
|||
with patch("mozregui.addons_editor.QFileDialog") as dlg:
|
||||
filePath = addons_file
|
||||
dlg.getOpenFileNames.return_value = ([filePath], "addon file (*.xpi)")
|
||||
qtbot.mouseClick(
|
||||
addons_editor.ui.add_addon,
|
||||
Qt.LeftButton
|
||||
)
|
||||
qtbot.mouseClick(addons_editor.ui.add_addon, Qt.LeftButton)
|
||||
dlg.getOpenFileNames.assert_called_once_with(
|
||||
addons_editor,
|
||||
"Choose one or more addon files",
|
||||
filter="addon file (*.xpi)",
|
||||
addons_editor, "Choose one or more addon files", filter="addon file (*.xpi)",
|
||||
)
|
||||
|
||||
# check addons
|
||||
assert addons_editor.list_model.rowCount() == len(
|
||||
[filePath])
|
||||
assert addons_editor.list_model.rowCount() == len([filePath])
|
||||
assert addons_editor.get_addons() == [filePath]
|
||||
|
||||
|
||||
def test_remove_addon(qtbot, addons_editor, addons_file):
|
||||
test_add_addon(qtbot, addons_editor, addons_file)
|
||||
addons_editor.ui.list_view.setCurrentIndex(addons_editor.list_model.index
|
||||
(0))
|
||||
addons_editor.ui.list_view.setCurrentIndex(addons_editor.list_model.index(0))
|
||||
assert addons_editor.ui.list_view.selectedIndexes()
|
||||
qtbot.mouseClick(addons_editor.ui.remove_addon, Qt.LeftButton)
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
import unittest
|
||||
import time
|
||||
import tempfile
|
||||
import shutil
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from mock import Mock, patch
|
||||
from . import wait_signal
|
||||
from PySide2.QtCore import QObject, QThread, Signal, \
|
||||
Slot
|
||||
from PySide2.QtCore import QObject, QThread, Signal, Slot
|
||||
|
||||
from mozregui import build_runner
|
||||
from mozregression.persist_limit import PersistLimit
|
||||
from mozregression.fetch_configs import create_config
|
||||
from mozregression.persist_limit import PersistLimit
|
||||
from mozregui import build_runner
|
||||
|
||||
from . import wait_signal
|
||||
|
||||
|
||||
def mock_session():
|
||||
|
@ -29,7 +29,7 @@ def mock_response(response, data, wait=0):
|
|||
rest = rest[chunk_size:]
|
||||
yield chunk
|
||||
|
||||
response.headers = {'Content-length': str(len(data))}
|
||||
response.headers = {"Content-length": str(len(data))}
|
||||
response.iter_content = iter_content
|
||||
|
||||
|
||||
|
@ -39,33 +39,29 @@ class TestGuiBuildDownloadManager(unittest.TestCase):
|
|||
tmpdir = tempfile.mkdtemp()
|
||||
tpersist = PersistLimit(10 * 1073741824)
|
||||
self.addCleanup(shutil.rmtree, tmpdir)
|
||||
self.dl_manager = \
|
||||
build_runner.GuiBuildDownloadManager(tmpdir, tpersist)
|
||||
self.dl_manager = build_runner.GuiBuildDownloadManager(tmpdir, tpersist)
|
||||
self.dl_manager.session = self.session
|
||||
self.signals = {}
|
||||
for sig in ('download_progress', 'download_started',
|
||||
'download_finished'):
|
||||
for sig in ("download_progress", "download_started", "download_finished"):
|
||||
self.signals[sig] = Mock()
|
||||
getattr(self.dl_manager, sig).connect(self.signals[sig])
|
||||
|
||||
@patch(
|
||||
'mozregui.build_runner.GuiBuildDownloadManager._extract_download_info')
|
||||
@patch("mozregui.build_runner.GuiBuildDownloadManager._extract_download_info")
|
||||
def test_focus_download(self, extract_info):
|
||||
extract_info.return_value = ('http://foo', 'foo')
|
||||
mock_response(self.session_response, b'this is some data' * 10000, 0.01)
|
||||
extract_info.return_value = ("http://foo", "foo")
|
||||
mock_response(self.session_response, b"this is some data" * 10000, 0.01)
|
||||
build_info = Mock()
|
||||
|
||||
with wait_signal(self.dl_manager.download_finished):
|
||||
self.dl_manager.focus_download(build_info)
|
||||
|
||||
# build_path is defined
|
||||
self.assertEqual(build_info.build_file,
|
||||
self.dl_manager.get_dest('foo'))
|
||||
self.assertEqual(build_info.build_file, self.dl_manager.get_dest("foo"))
|
||||
|
||||
# signals have been emitted
|
||||
self.assertEqual(self.signals['download_started'].call_count, 1)
|
||||
self.assertEqual(self.signals['download_finished'].call_count, 1)
|
||||
self.assertGreater(self.signals['download_progress'].call_count, 0)
|
||||
self.assertEqual(self.signals["download_started"].call_count, 1)
|
||||
self.assertEqual(self.signals["download_finished"].call_count, 1)
|
||||
self.assertGreater(self.signals["download_progress"].call_count, 0)
|
||||
|
||||
# well, file has been downloaded finally
|
||||
self.assertTrue(os.path.isfile(build_info.build_file))
|
||||
|
@ -79,9 +75,9 @@ class TestGuiTestRunner(unittest.TestCase):
|
|||
self.test_runner.evaluate_started.connect(self.evaluate_started)
|
||||
self.test_runner.evaluate_finished.connect(self.evaluate_finished)
|
||||
|
||||
@patch('mozregui.build_runner.create_launcher')
|
||||
@patch("mozregui.build_runner.create_launcher")
|
||||
def test_basic(self, create_launcher):
|
||||
launcher = Mock(get_app_info=lambda: 'app_info')
|
||||
launcher = Mock(get_app_info=lambda: "app_info")
|
||||
create_launcher.return_value = launcher
|
||||
|
||||
# nothing called yet
|
||||
|
@ -96,13 +92,13 @@ class TestGuiTestRunner(unittest.TestCase):
|
|||
# launcher is defined
|
||||
self.assertEqual(self.test_runner.launcher, launcher)
|
||||
|
||||
self.test_runner.finish('g')
|
||||
self.test_runner.finish("g")
|
||||
|
||||
# now evaluate_finished has been called
|
||||
self.assertEqual(self.evaluate_started.call_count, 1)
|
||||
self.assertEqual(self.evaluate_finished.call_count, 1)
|
||||
# verdict is defined, launcher is None
|
||||
self.assertEqual(self.test_runner.verdict, 'g')
|
||||
self.assertEqual(self.test_runner.verdict, "g")
|
||||
|
||||
|
||||
def test_abstract_build_runner(qtbot):
|
||||
|
@ -125,23 +121,21 @@ def test_abstract_build_runner(qtbot):
|
|||
worker_class = Worker
|
||||
|
||||
def init_worker(self, fetch_config, options):
|
||||
build_runner.AbstractBuildRunner.init_worker(self, fetch_config,
|
||||
options)
|
||||
build_runner.AbstractBuildRunner.init_worker(self, fetch_config, options)
|
||||
self.thread.finished.connect(self.thread_finished)
|
||||
self.worker.call_started.connect(self.call_started)
|
||||
return self.worker.my_slot
|
||||
|
||||
# instantiate the runner
|
||||
runner = BuildRunner(Mock(persist='.'))
|
||||
runner = BuildRunner(Mock(persist="."))
|
||||
|
||||
assert not runner.thread
|
||||
|
||||
with qtbot.waitSignal(runner.thread_finished, raising=True):
|
||||
with qtbot.waitSignal(runner.call_started, raising=True):
|
||||
runner.start(
|
||||
create_config('firefox', 'linux', 64, 'x86_64'),
|
||||
{'addons': (), 'profile': '/path/to/profile',
|
||||
'profile_persistence': 'clone'},
|
||||
create_config("firefox", "linux", 64, "x86_64"),
|
||||
{"addons": (), "profile": "/path/to/profile", "profile_persistence": "clone"},
|
||||
)
|
||||
|
||||
runner.stop(True)
|
||||
|
@ -158,15 +152,17 @@ def test_runner_started_multiple_times():
|
|||
worker_class = Worker
|
||||
|
||||
def init_worker(self, fetch_config, options):
|
||||
build_runner.AbstractBuildRunner.init_worker(self, fetch_config,
|
||||
options)
|
||||
build_runner.AbstractBuildRunner.init_worker(self, fetch_config, options)
|
||||
return lambda: 1
|
||||
|
||||
fetch_config = create_config('firefox', 'linux', 64, 'x86_64')
|
||||
options = {'addons': (), 'profile': '/path/to/profile',
|
||||
'profile_persistence': 'clone'}
|
||||
fetch_config = create_config("firefox", "linux", 64, "x86_64")
|
||||
options = {
|
||||
"addons": (),
|
||||
"profile": "/path/to/profile",
|
||||
"profile_persistence": "clone",
|
||||
}
|
||||
|
||||
runner = BuildRunner(Mock(persist='.'))
|
||||
runner = BuildRunner(Mock(persist="."))
|
||||
assert not runner.stopped
|
||||
runner.start(fetch_config, options)
|
||||
assert not runner.stopped
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import pytest
|
||||
|
||||
from mozregression import __version__
|
||||
from mozregui.main import MainWindow
|
||||
from mozregui.check_release import CheckRelease, QLabel, QUrl
|
||||
from mozregui.main import MainWindow
|
||||
|
||||
|
||||
@pytest.yield_fixture
|
||||
|
@ -15,23 +15,18 @@ def mainwindow(qtbot):
|
|||
|
||||
def test_check_release(qtbot, mocker, mainwindow):
|
||||
retry_get = mocker.patch("mozregui.check_release.retry_get")
|
||||
retry_get.return_value = mocker.Mock(
|
||||
json=lambda *a: {
|
||||
'tag_name': '0.0',
|
||||
'html_url': 'url'
|
||||
}
|
||||
)
|
||||
retry_get.return_value = mocker.Mock(json=lambda *a: {"tag_name": "0.0", "html_url": "url"})
|
||||
status_bar = mainwindow.ui.status_bar
|
||||
assert status_bar.findChild(QLabel, '') is None
|
||||
assert status_bar.findChild(QLabel, "") is None
|
||||
|
||||
checker = CheckRelease(mainwindow)
|
||||
with qtbot.waitSignal(checker.thread.finished, raising=True):
|
||||
checker.check()
|
||||
|
||||
lbl = status_bar.findChild(QLabel, '')
|
||||
lbl = status_bar.findChild(QLabel, "")
|
||||
assert lbl
|
||||
assert "There is a new release available!" in str(lbl.text())
|
||||
assert '0.0' in str(lbl.text())
|
||||
assert "0.0" in str(lbl.text())
|
||||
|
||||
# simulate click on the link
|
||||
open_url = mocker.patch("mozregui.check_release.QDesktopServices.openUrl")
|
||||
|
@ -44,16 +39,13 @@ def test_check_release(qtbot, mocker, mainwindow):
|
|||
def test_check_release_no_update(qtbot, mocker, mainwindow):
|
||||
retry_get = mocker.patch("mozregui.check_release.retry_get")
|
||||
retry_get.return_value = mocker.Mock(
|
||||
json=lambda *a: {
|
||||
'tag_name': __version__,
|
||||
'html_url': 'url'
|
||||
}
|
||||
json=lambda *a: {"tag_name": __version__, "html_url": "url"}
|
||||
)
|
||||
status_bar = mainwindow.ui.status_bar
|
||||
assert status_bar.findChild(QLabel, '') is None
|
||||
assert status_bar.findChild(QLabel, "") is None
|
||||
|
||||
checker = CheckRelease(mainwindow)
|
||||
with qtbot.waitSignal(checker.thread.finished, raising=True):
|
||||
checker.check()
|
||||
|
||||
assert status_bar.findChild(QLabel, '') is None
|
||||
assert status_bar.findChild(QLabel, "") is None
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import pytest
|
||||
|
||||
from PySide2.QtCore import Qt
|
||||
from PySide2.QtWidgets import QApplication, QPushButton
|
||||
from mozregui.crash_reporter import CrashReporter, CrashDialog
|
||||
|
||||
from mozregui.crash_reporter import CrashDialog, CrashReporter
|
||||
|
||||
|
||||
class CrashDlgTest(CrashDialog):
|
||||
|
@ -46,4 +46,4 @@ def test_report_exception(crash_reporter, qtbot, mocker):
|
|||
dlg = CrashDlgTest.INSTANCE
|
||||
qtbot.waitForWindowShown(dlg)
|
||||
text = str(dlg.ui.information.toPlainText())
|
||||
assert 'oh, no!' in text
|
||||
assert "oh, no!" in text
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import pytest
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
from PySide2.QtWidgets import QDialog
|
||||
import pytest
|
||||
from mock import Mock
|
||||
from PySide2.QtWidgets import QDialog
|
||||
|
||||
from mozregui import global_prefs
|
||||
|
||||
|
@ -16,7 +16,7 @@ def write_default_conf():
|
|||
conf_file.close()
|
||||
|
||||
def write_conf(text):
|
||||
with open(conf_file.name, 'w') as f:
|
||||
with open(conf_file.name, "w") as f:
|
||||
f.write(text)
|
||||
|
||||
yield write_conf
|
||||
|
@ -26,10 +26,12 @@ def write_default_conf():
|
|||
|
||||
|
||||
def test_change_prefs_dialog(write_default_conf, qtbot):
|
||||
write_default_conf("""
|
||||
write_default_conf(
|
||||
"""
|
||||
http-timeout = 32.1
|
||||
persist-size-limit = 2.5
|
||||
""")
|
||||
"""
|
||||
)
|
||||
|
||||
pref_dialog = global_prefs.ChangePrefsDialog()
|
||||
qtbot.add_widget(pref_dialog)
|
||||
|
@ -37,7 +39,7 @@ persist-size-limit = 2.5
|
|||
qtbot.waitForWindowShown(pref_dialog)
|
||||
|
||||
# defaults are set
|
||||
assert str(pref_dialog.ui.persist.line_edit.text()) == ''
|
||||
assert str(pref_dialog.ui.persist.line_edit.text()) == ""
|
||||
assert pref_dialog.ui.http_timeout.value() == 32.1
|
||||
assert pref_dialog.ui.persist_size_limit.value() == 2.5
|
||||
|
||||
|
@ -48,15 +50,12 @@ persist-size-limit = 2.5
|
|||
pref_dialog.save_prefs()
|
||||
|
||||
# check they have been registered
|
||||
assert global_prefs.get_prefs().get('persist') == "/path/to"
|
||||
assert global_prefs.get_prefs().get("persist") == "/path/to"
|
||||
|
||||
|
||||
@pytest.mark.parametrize('dlg_result,saved', [
|
||||
(QDialog.Accepted, True),
|
||||
(QDialog.Rejected, False),
|
||||
])
|
||||
@pytest.mark.parametrize("dlg_result,saved", [(QDialog.Accepted, True), (QDialog.Rejected, False)])
|
||||
def test_change_prefs_dialog_saves_prefs(dlg_result, saved, mocker):
|
||||
Dlg = mocker.patch('mozregui.global_prefs.ChangePrefsDialog')
|
||||
Dlg = mocker.patch("mozregui.global_prefs.ChangePrefsDialog")
|
||||
dlg = Mock()
|
||||
Dlg.return_value = dlg
|
||||
dlg.exec_.return_value = dlg_result
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import pytest
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
from mozregui import log_report
|
||||
|
||||
|
||||
|
@ -17,42 +18,40 @@ def log_view(qtbot):
|
|||
|
||||
def test_log_report_report_log_line(log_view):
|
||||
# view is first empty
|
||||
assert str(log_view.toPlainText()) == ''
|
||||
assert str(log_view.toPlainText()) == ""
|
||||
assert log_view.blockCount() == 1 # 1 for an empty document
|
||||
|
||||
# send a log line
|
||||
log_view.log_model({'message': 'my message', 'level': 'INFO',
|
||||
'time': time.time()})
|
||||
log_view.log_model({"message": "my message", "level": "INFO", "time": time.time()})
|
||||
|
||||
assert log_view.blockCount() == 2
|
||||
assert 'INFO : my message' in str(log_view.toPlainText())
|
||||
assert "INFO : my message" in str(log_view.toPlainText())
|
||||
|
||||
|
||||
def test_log_report_report_no_more_than_1000_lines(log_view):
|
||||
for i in range(1001):
|
||||
log_view.log_model({'message': str(i), 'level': 'INFO',
|
||||
'time': time.time()})
|
||||
log_view.log_model({"message": str(i), "level": "INFO", "time": time.time()})
|
||||
|
||||
assert log_view.blockCount() == 1000
|
||||
lines = str(log_view.toPlainText()).splitlines()
|
||||
assert len(lines) == 999
|
||||
assert 'INFO : 1000' in lines[-1]
|
||||
assert 'INFO : 2' in lines[0] # 2 first lines were dropped
|
||||
assert "INFO : 1000" in lines[-1]
|
||||
assert "INFO : 2" in lines[0] # 2 first lines were dropped
|
||||
|
||||
|
||||
def test_log_report_sets_correct_user_data(log_view):
|
||||
"""Assumes that only the correct log levels are entered"""
|
||||
# Inserts a log message for each log user level
|
||||
for log_level in log_report.log_levels.keys():
|
||||
log_view.log_model({'message': "%s message" % log_level,
|
||||
'level': '%s' % log_level, 'time': time.time()})
|
||||
log_view.log_model(
|
||||
{"message": "%s message" % log_level, "level": "%s" % log_level, "time": time.time()}
|
||||
)
|
||||
# Checks each log level message to make sure the correct
|
||||
# user data is entered
|
||||
for current_block in log_view.text_blocks():
|
||||
for log_level in log_report.log_levels.keys():
|
||||
if log_level in current_block.text():
|
||||
assert current_block.userData().log_lvl == \
|
||||
log_report.log_levels[log_level]
|
||||
assert current_block.userData().log_lvl == log_report.log_levels[log_level]
|
||||
|
||||
|
||||
def test_log_report_filters_data_below_current_log_level(log_view):
|
||||
|
@ -61,8 +60,9 @@ def test_log_report_filters_data_below_current_log_level(log_view):
|
|||
current_log_level = log_view.log_lvl
|
||||
# Inserts a log message for each log user level
|
||||
for log_level in log_report.log_levels.keys():
|
||||
log_view.log_model({'message': "%s message" % log_level,
|
||||
'level': '%s' % log_level, 'time': time.time()})
|
||||
log_view.log_model(
|
||||
{"message": "%s message" % log_level, "level": "%s" % log_level, "time": time.time()}
|
||||
)
|
||||
# Check that log messages above the current log level are visible
|
||||
# and log messages below the log level are invisible
|
||||
for current_block in log_view.text_blocks():
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import os
|
||||
|
||||
from mock import patch
|
||||
from PySide2.QtCore import QTimer
|
||||
from . import APP
|
||||
|
||||
from mozregui import main # noqa
|
||||
|
||||
from . import APP
|
||||
|
||||
|
||||
@patch("mozregui.main.QApplication")
|
||||
@patch("mozregui.main.CheckRelease")
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import pytest
|
||||
import tempfile
|
||||
import mozfile
|
||||
import os
|
||||
import mozinfo
|
||||
import tempfile
|
||||
|
||||
from PySide2.QtCore import Qt
|
||||
import mozfile
|
||||
import mozinfo
|
||||
import pytest
|
||||
from mock import patch
|
||||
from PySide2.QtCore import Qt
|
||||
|
||||
from mozregui.pref_editor import PreferencesWidgetEditor
|
||||
|
||||
|
||||
|
@ -54,7 +55,7 @@ def test_add_empty_pref_then_fill_it(qtbot, pref_editor):
|
|||
# under TRAVIS and mac, the edition fail somewhere. not sure
|
||||
# why, since on my ubuntu it works well even with Xvfb.
|
||||
# TODO: look at this later
|
||||
assert pref_editor.get_prefs() == [('hello', 'world')]
|
||||
assert pref_editor.get_prefs() == [("hello", "world")]
|
||||
|
||||
|
||||
def test_remove_pref(qtbot, pref_editor):
|
||||
|
@ -76,7 +77,7 @@ def test_remove_pref(qtbot, pref_editor):
|
|||
@pytest.fixture
|
||||
def pref_file(request):
|
||||
# create a temp file with prefs
|
||||
with tempfile.NamedTemporaryFile(suffix='.json', delete=False) as f:
|
||||
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
|
||||
f.write(b'{ "browser.tabs.remote.autostart": false, "toto": 1 }')
|
||||
request.addfinalizer(lambda: mozfile.remove(f.name))
|
||||
return f.name
|
||||
|
@ -84,20 +85,14 @@ def pref_file(request):
|
|||
|
||||
def test_add_prefs_using_file(qtbot, pref_editor, pref_file):
|
||||
with patch("mozregui.pref_editor.QFileDialog") as dlg:
|
||||
dlg.getOpenFileName.return_value = (pref_file, 'pref file (*.json *.ini)')
|
||||
qtbot.mouseClick(
|
||||
pref_editor.ui.add_prefs_from_file,
|
||||
Qt.LeftButton
|
||||
)
|
||||
dlg.getOpenFileName.return_value = (pref_file, "pref file (*.json *.ini)")
|
||||
qtbot.mouseClick(pref_editor.ui.add_prefs_from_file, Qt.LeftButton)
|
||||
dlg.getOpenFileName.assert_called_once_with(
|
||||
pref_editor,
|
||||
"Choose a preference file",
|
||||
filter="pref file (*.json *.ini)"
|
||||
pref_editor, "Choose a preference file", filter="pref file (*.json *.ini)"
|
||||
)
|
||||
|
||||
# check prefs
|
||||
assert pref_editor.pref_model.rowCount() == 2
|
||||
assert set(pref_editor.get_prefs()) == set([
|
||||
("browser.tabs.remote.autostart", False),
|
||||
("toto", 1)
|
||||
])
|
||||
assert set(pref_editor.get_prefs()) == set(
|
||||
[("browser.tabs.remote.autostart", False), ("toto", 1)]
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
from PySide2.QtCore import Qt
|
||||
from mock import Mock
|
||||
from PySide2.QtCore import Qt
|
||||
|
||||
from mozregression.build_info import NightlyBuildInfo
|
||||
from mozregui.report import ReportView
|
||||
|
@ -24,30 +24,25 @@ def test_report_basic(qtbot):
|
|||
assert index.isValid()
|
||||
|
||||
# simulate a build found
|
||||
data = dict(build_type='nightly', build_date='date',
|
||||
repo_name='mozilla-central')
|
||||
build_infos = Mock(
|
||||
spec=NightlyBuildInfo,
|
||||
to_dict=lambda: data,
|
||||
**data
|
||||
)
|
||||
bisection = Mock(build_range=[Mock(repo_name='mozilla-central')])
|
||||
data = dict(build_type="nightly", build_date="date", repo_name="mozilla-central")
|
||||
build_infos = Mock(spec=NightlyBuildInfo, to_dict=lambda: data, **data)
|
||||
bisection = Mock(build_range=[Mock(repo_name="mozilla-central")])
|
||||
bisection.handler.find_fix = False
|
||||
bisection.handler.get_range.return_value = ('1', '2')
|
||||
bisection.handler.get_range.return_value = ("1", "2")
|
||||
view.model().step_build_found(bisection, build_infos)
|
||||
# now we have two rows
|
||||
assert view.model().rowCount() == 2
|
||||
# data is updated
|
||||
index2 = view.model().index(1, 0)
|
||||
assert '[1 - 2]' in view.model().data(index)
|
||||
assert 'mozilla-central' in view.model().data(index2)
|
||||
assert "[1 - 2]" in view.model().data(index)
|
||||
assert "mozilla-central" in view.model().data(index2)
|
||||
|
||||
# simulate a build evaluation
|
||||
view.model().step_finished(None, 'g')
|
||||
view.model().step_finished(None, "g")
|
||||
# still two rows
|
||||
assert view.model().rowCount() == 2
|
||||
# data is updated
|
||||
assert 'g' in view.model().data(index2)
|
||||
assert "g" in view.model().data(index2)
|
||||
|
||||
# let's click on the index
|
||||
view.scrollTo(index)
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import pytest
|
||||
|
||||
from PySide2.QtCore import Qt
|
||||
from PySide2.QtGui import QCloseEvent
|
||||
from PySide2.QtWidgets import QMessageBox
|
||||
|
||||
from mozregression.build_range import BuildRange, FutureBuildInfo
|
||||
from mozregui.skip_chooser import SkipDialog
|
||||
|
||||
|
@ -16,8 +16,7 @@ class DialogBuilder(object):
|
|||
def _fetch(self):
|
||||
return self.data
|
||||
|
||||
build_range = BuildRange(None, [FInfo(None, i)
|
||||
for i in range(nb_builds)])
|
||||
build_range = BuildRange(None, [FInfo(None, i) for i in range(nb_builds)])
|
||||
dialog = SkipDialog(build_range)
|
||||
dialog.exec_ = lambda: return_exec_code
|
||||
self.qtbot.addWidget(dialog)
|
||||
|
@ -72,8 +71,7 @@ def test_dbl_click_btn(qtbot, dialog_builder):
|
|||
def test_close_event(mocker, dialog_builder, close):
|
||||
dialog = dialog_builder.build(5)
|
||||
warning = mocker.patch("PySide2.QtWidgets.QMessageBox.warning")
|
||||
warning.return_value = (QMessageBox.Yes if close
|
||||
else QMessageBox.No)
|
||||
warning.return_value = QMessageBox.Yes if close else QMessageBox.No
|
||||
evt = QCloseEvent()
|
||||
|
||||
dialog.closeEvent(evt)
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
import pytest
|
||||
import datetime
|
||||
|
||||
from mozregui.utils import BuildSelection
|
||||
import pytest
|
||||
|
||||
from mozregression.errors import DateFormatError
|
||||
from mozregui.utils import BuildSelection
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
|
@ -15,7 +16,7 @@ def build_selection(qtbot):
|
|||
|
||||
|
||||
def test_date_widget_is_visible_by_default(build_selection):
|
||||
assert build_selection.ui.combo_helper.currentText() == 'date'
|
||||
assert build_selection.ui.combo_helper.currentText() == "date"
|
||||
assert build_selection.ui.date.isVisible()
|
||||
assert not build_selection.ui.buildid.isVisible()
|
||||
assert not build_selection.ui.release.isVisible()
|
||||
|
@ -23,18 +24,20 @@ def test_date_widget_is_visible_by_default(build_selection):
|
|||
|
||||
def test_switch_to_release_widget(build_selection, qtbot):
|
||||
qtbot.keyClicks(build_selection.ui.combo_helper, "release")
|
||||
assert build_selection.ui.combo_helper.currentText() == 'release'
|
||||
assert build_selection.ui.combo_helper.currentText() == "release"
|
||||
assert not build_selection.ui.date.isVisible()
|
||||
assert not build_selection.ui.buildid.isVisible()
|
||||
assert build_selection.ui.release.isVisible()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("widname,value,expected", [
|
||||
("buildid", "20150102101112",
|
||||
datetime.datetime(2015, 1, 2, 10, 11, 12)),
|
||||
@pytest.mark.parametrize(
|
||||
"widname,value,expected",
|
||||
[
|
||||
("buildid", "20150102101112", datetime.datetime(2015, 1, 2, 10, 11, 12)),
|
||||
("release", "40", datetime.date(2015, 5, 11)),
|
||||
("changeset", "abc123", "abc123"),
|
||||
])
|
||||
],
|
||||
)
|
||||
def test_get_value(build_selection, qtbot, widname, value, expected):
|
||||
qtbot.keyClicks(build_selection.ui.combo_helper, widname)
|
||||
qtbot.keyClicks(getattr(build_selection.ui, widname), value)
|
||||
|
@ -51,8 +54,7 @@ def test_date_picker(build_selection, qtbot):
|
|||
|
||||
def test_get_invalid_buildid(build_selection, qtbot):
|
||||
qtbot.keyClicks(build_selection.ui.combo_helper, "buildid")
|
||||
qtbot.keyClicks(build_selection.ui.
|
||||
stackedWidget.currentWidget(), "12345")
|
||||
qtbot.keyClicks(build_selection.ui.stackedWidget.currentWidget(), "12345")
|
||||
with pytest.raises(DateFormatError) as ctx:
|
||||
build_selection.get_value()
|
||||
assert 'build id' in str(ctx.value)
|
||||
assert "build id" in str(ctx.value)
|
||||
|
|
|
@ -1,27 +1,33 @@
|
|||
import pytest
|
||||
|
||||
from PySide2.QtCore import QDate
|
||||
|
||||
from mozregression.fetch_configs import CommonConfig
|
||||
|
||||
from mozregui.wizard import BisectionWizard, IntroPage, ProfilePage, \
|
||||
BuildSelectionPage, SingleBuildSelectionPage, SingleRunWizard
|
||||
|
||||
from mozregui.wizard import (
|
||||
BisectionWizard,
|
||||
BuildSelectionPage,
|
||||
IntroPage,
|
||||
ProfilePage,
|
||||
SingleBuildSelectionPage,
|
||||
SingleRunWizard,
|
||||
)
|
||||
|
||||
PAGES_BISECTION_WIZARD = (IntroPage, ProfilePage, BuildSelectionPage)
|
||||
PAGES_SINGLE_RUN_WIZARD = (IntroPage, ProfilePage, SingleBuildSelectionPage)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('os, bits, wizard_class, pages', [
|
||||
('linux', 64, BisectionWizard, PAGES_BISECTION_WIZARD,),
|
||||
('win', 32, BisectionWizard, PAGES_BISECTION_WIZARD),
|
||||
('mac', 64, BisectionWizard, PAGES_BISECTION_WIZARD),
|
||||
('linux', 64, SingleRunWizard, PAGES_SINGLE_RUN_WIZARD),
|
||||
('win', 32, SingleRunWizard, PAGES_SINGLE_RUN_WIZARD),
|
||||
('mac', 64, SingleRunWizard, PAGES_SINGLE_RUN_WIZARD),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"os, bits, wizard_class, pages",
|
||||
[
|
||||
("linux", 64, BisectionWizard, PAGES_BISECTION_WIZARD,),
|
||||
("win", 32, BisectionWizard, PAGES_BISECTION_WIZARD),
|
||||
("mac", 64, BisectionWizard, PAGES_BISECTION_WIZARD),
|
||||
("linux", 64, SingleRunWizard, PAGES_SINGLE_RUN_WIZARD),
|
||||
("win", 32, SingleRunWizard, PAGES_SINGLE_RUN_WIZARD),
|
||||
("mac", 64, SingleRunWizard, PAGES_SINGLE_RUN_WIZARD),
|
||||
],
|
||||
)
|
||||
def test_wizard(mocker, qtbot, os, bits, wizard_class, pages):
|
||||
mozinfo = mocker.patch('mozregui.wizard.mozinfo')
|
||||
mozinfo = mocker.patch("mozregui.wizard.mozinfo")
|
||||
mozinfo.os = os
|
||||
mozinfo.bits = bits
|
||||
|
||||
|
@ -42,15 +48,15 @@ def test_wizard(mocker, qtbot, os, bits, wizard_class, pages):
|
|||
now = QDate.currentDate()
|
||||
|
||||
assert isinstance(fetch_config, CommonConfig)
|
||||
assert fetch_config.app_name == 'firefox' # this is the default
|
||||
assert fetch_config.app_name == "firefox" # this is the default
|
||||
assert fetch_config.os == os
|
||||
assert fetch_config.bits == bits
|
||||
assert fetch_config.build_type == 'shippable'
|
||||
assert fetch_config.build_type == "shippable"
|
||||
assert not fetch_config.repo
|
||||
|
||||
assert options['profile'] == ''
|
||||
assert options["profile"] == ""
|
||||
if isinstance(wizard, SingleRunWizard):
|
||||
assert options['launch'] == now.addDays(-3).toPython()
|
||||
assert options["launch"] == now.addDays(-3).toPython()
|
||||
else:
|
||||
assert options['good'] == now.addYears(-1).toPython()
|
||||
assert options['bad'] == now.toPython()
|
||||
assert options["good"] == now.addYears(-1).toPython()
|
||||
assert options["bad"] == now.toPython()
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from six.moves import range
|
||||
|
||||
|
||||
|
@ -27,6 +29,7 @@ class ApproxPersistChooser(object):
|
|||
2015-07-9
|
||||
2015-07-11
|
||||
"""
|
||||
|
||||
def __init__(self, one_every):
|
||||
self.one_every = abs(one_every)
|
||||
|
||||
|
|
|
@ -3,22 +3,28 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import absolute_import
|
||||
import os
|
||||
|
||||
import math
|
||||
import os
|
||||
import threading
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import six
|
||||
from mozlog import get_proxy_logger
|
||||
|
||||
from mozregression.branches import find_branch_in_merge_commit, get_name
|
||||
from mozregression.build_range import get_integration_range, get_nightly_range
|
||||
from mozregression.dates import to_datetime
|
||||
from mozregression.errors import LauncherError, MozRegressionError, \
|
||||
GoodBadExpectationError, EmptyPushlogError
|
||||
from mozregression.errors import (
|
||||
EmptyPushlogError,
|
||||
GoodBadExpectationError,
|
||||
LauncherError,
|
||||
MozRegressionError,
|
||||
)
|
||||
from mozregression.history import BisectionHistory
|
||||
from mozregression.branches import find_branch_in_merge_commit, get_name
|
||||
from mozregression.json_pushes import JsonPushes
|
||||
from abc import ABCMeta, abstractmethod
|
||||
import six
|
||||
|
||||
LOG = get_proxy_logger('Bisector')
|
||||
LOG = get_proxy_logger("Bisector")
|
||||
|
||||
|
||||
def compute_steps_left(steps):
|
||||
|
@ -76,17 +82,15 @@ class BisectorHandler(six.with_metaclass(ABCMeta)):
|
|||
# do not update repo if we can' find it now
|
||||
# else we may override a previously defined one
|
||||
self.found_repo = repo
|
||||
self.good_revision, self.bad_revision = \
|
||||
self._reverse_if_find_fix(self.build_range[0].changeset,
|
||||
self.build_range[-1].changeset)
|
||||
self.good_revision, self.bad_revision = self._reverse_if_find_fix(
|
||||
self.build_range[0].changeset, self.build_range[-1].changeset
|
||||
)
|
||||
|
||||
def get_pushlog_url(self):
|
||||
first_rev, last_rev = self.get_range()
|
||||
if first_rev == last_rev:
|
||||
return "%s/pushloghtml?changeset=%s" % (
|
||||
self.found_repo, first_rev)
|
||||
return "%s/pushloghtml?fromchange=%s&tochange=%s" % (
|
||||
self.found_repo, first_rev, last_rev)
|
||||
return "%s/pushloghtml?changeset=%s" % (self.found_repo, first_rev)
|
||||
return "%s/pushloghtml?fromchange=%s&tochange=%s" % (self.found_repo, first_rev, last_rev,)
|
||||
|
||||
def get_range(self):
|
||||
return self._reverse_if_find_fix(self.good_revision, self.bad_revision)
|
||||
|
@ -98,13 +102,17 @@ class BisectorHandler(six.with_metaclass(ABCMeta)):
|
|||
"""
|
||||
if full:
|
||||
if good_date and bad_date:
|
||||
good_date = ' (%s)' % good_date
|
||||
bad_date = ' (%s)' % bad_date
|
||||
words = self._reverse_if_find_fix('Last', 'First')
|
||||
LOG.info("%s good revision: %s%s" % (
|
||||
words[0], self.good_revision, good_date if good_date else ''))
|
||||
LOG.info("%s bad revision: %s%s" % (
|
||||
words[1], self.bad_revision, bad_date if bad_date else ''))
|
||||
good_date = " (%s)" % good_date
|
||||
bad_date = " (%s)" % bad_date
|
||||
words = self._reverse_if_find_fix("Last", "First")
|
||||
LOG.info(
|
||||
"%s good revision: %s%s"
|
||||
% (words[0], self.good_revision, good_date if good_date else "")
|
||||
)
|
||||
LOG.info(
|
||||
"%s bad revision: %s%s"
|
||||
% (words[1], self.bad_revision, bad_date if bad_date else "")
|
||||
)
|
||||
LOG.info("Pushlog:\n%s\n" % self.get_pushlog_url())
|
||||
|
||||
def build_good(self, mid, new_data):
|
||||
|
@ -147,35 +155,33 @@ class NightlyHandler(BisectorHandler):
|
|||
def initialize(self):
|
||||
BisectorHandler.initialize(self)
|
||||
# register dates
|
||||
self.good_date, self.bad_date = \
|
||||
self._reverse_if_find_fix(
|
||||
self.build_range[0].build_date,
|
||||
self.build_range[-1].build_date
|
||||
self.good_date, self.bad_date = self._reverse_if_find_fix(
|
||||
self.build_range[0].build_date, self.build_range[-1].build_date
|
||||
)
|
||||
|
||||
def _print_progress(self, new_data):
|
||||
next_good_date = new_data[0].build_date
|
||||
next_bad_date = new_data[-1].build_date
|
||||
next_days_range = abs((to_datetime(next_bad_date) -
|
||||
to_datetime(next_good_date)).days)
|
||||
LOG.info("Narrowed nightly regression window from"
|
||||
next_days_range = abs((to_datetime(next_bad_date) - to_datetime(next_good_date)).days)
|
||||
LOG.info(
|
||||
"Narrowed nightly regression window from"
|
||||
" [%s, %s] (%d days) to [%s, %s] (%d days)"
|
||||
" (~%d steps left)"
|
||||
% (self.good_date,
|
||||
% (
|
||||
self.good_date,
|
||||
self.bad_date,
|
||||
abs((to_datetime(self.bad_date) -
|
||||
to_datetime(self.good_date)).days),
|
||||
abs((to_datetime(self.bad_date) - to_datetime(self.good_date)).days),
|
||||
next_good_date,
|
||||
next_bad_date,
|
||||
next_days_range,
|
||||
compute_steps_left(next_days_range)))
|
||||
compute_steps_left(next_days_range),
|
||||
)
|
||||
)
|
||||
|
||||
def _print_date_range(self):
|
||||
words = self._reverse_if_find_fix('Newest', 'Oldest')
|
||||
LOG.info('%s known good nightly: %s' % (words[0],
|
||||
self.good_date))
|
||||
LOG.info('%s known bad nightly: %s' % (words[1],
|
||||
self.bad_date))
|
||||
words = self._reverse_if_find_fix("Newest", "Oldest")
|
||||
LOG.info("%s known good nightly: %s" % (words[0], self.good_date))
|
||||
LOG.info("%s known bad nightly: %s" % (words[1], self.bad_date))
|
||||
|
||||
def user_exit(self, mid):
|
||||
self._print_date_range()
|
||||
|
@ -190,14 +196,15 @@ class NightlyHandler(BisectorHandler):
|
|||
if self.found_repo is None:
|
||||
# this may happen if we are bisecting old builds without
|
||||
# enough tests of the builds.
|
||||
LOG.error("Sorry, but mozregression was unable to get"
|
||||
" a repository - no pushlog url available.")
|
||||
LOG.error(
|
||||
"Sorry, but mozregression was unable to get"
|
||||
" a repository - no pushlog url available."
|
||||
)
|
||||
# still we can print date range
|
||||
if full:
|
||||
self._print_date_range()
|
||||
elif self.are_revisions_available():
|
||||
BisectorHandler.print_range(self, self.good_date,
|
||||
self.bad_date, full=full)
|
||||
BisectorHandler.print_range(self, self.good_date, self.bad_date, full=full)
|
||||
else:
|
||||
if full:
|
||||
self._print_date_range()
|
||||
|
@ -209,31 +216,32 @@ class NightlyHandler(BisectorHandler):
|
|||
return BisectorHandler.get_pushlog_url(self)
|
||||
else:
|
||||
start, end = self.get_date_range()
|
||||
return ("%s/pushloghtml?startdate=%s&enddate=%s\n"
|
||||
% (self.found_repo, start, end))
|
||||
return "%s/pushloghtml?startdate=%s&enddate=%s\n" % (self.found_repo, start, end,)
|
||||
|
||||
|
||||
class IntegrationHandler(BisectorHandler):
|
||||
create_range = staticmethod(get_integration_range)
|
||||
|
||||
def _print_progress(self, new_data):
|
||||
LOG.info("Narrowed integration regression window from [%s, %s]"
|
||||
LOG.info(
|
||||
"Narrowed integration regression window from [%s, %s]"
|
||||
" (%d builds) to [%s, %s] (%d builds)"
|
||||
" (~%d steps left)"
|
||||
% (self.build_range[0].short_changeset,
|
||||
% (
|
||||
self.build_range[0].short_changeset,
|
||||
self.build_range[-1].short_changeset,
|
||||
len(self.build_range),
|
||||
new_data[0].short_changeset,
|
||||
new_data[-1].short_changeset,
|
||||
len(new_data),
|
||||
compute_steps_left(len(new_data))))
|
||||
compute_steps_left(len(new_data)),
|
||||
)
|
||||
)
|
||||
|
||||
def user_exit(self, mid):
|
||||
words = self._reverse_if_find_fix('Newest', 'Oldest')
|
||||
LOG.info('%s known good integration revision: %s'
|
||||
% (words[0], self.good_revision))
|
||||
LOG.info('%s known bad integration revision: %s'
|
||||
% (words[1], self.bad_revision))
|
||||
words = self._reverse_if_find_fix("Newest", "Oldest")
|
||||
LOG.info("%s known good integration revision: %s" % (words[0], self.good_revision))
|
||||
LOG.info("%s known bad integration revision: %s" % (words[1], self.bad_revision))
|
||||
|
||||
def _choose_integration_branch(self, changeset):
|
||||
"""
|
||||
|
@ -246,7 +254,7 @@ class IntegrationHandler(BisectorHandler):
|
|||
jp = JsonPushes(k)
|
||||
|
||||
try:
|
||||
push = jp.push(changeset, full='1')
|
||||
push = jp.push(changeset, full="1")
|
||||
landings[k] = push.timestamp
|
||||
except EmptyPushlogError:
|
||||
LOG.debug("Didn't find %s in %s" % (changeset, k))
|
||||
|
@ -264,36 +272,42 @@ class IntegrationHandler(BisectorHandler):
|
|||
# we have to check the commit of the most recent push
|
||||
most_recent_push = self.build_range[1]
|
||||
jp = JsonPushes(most_recent_push.repo_name)
|
||||
push = jp.push(most_recent_push.changeset, full='1')
|
||||
msg = push.changeset['desc']
|
||||
push = jp.push(most_recent_push.changeset, full="1")
|
||||
msg = push.changeset["desc"]
|
||||
LOG.debug("Found commit message:\n%s\n" % msg)
|
||||
branch = find_branch_in_merge_commit(msg, most_recent_push.repo_name)
|
||||
if not (branch and len(push.changesets) >= 2):
|
||||
# We did not find a branch, lets check the integration branches if we are bisecting m-c
|
||||
LOG.debug("Did not find a branch, checking all integration branches")
|
||||
if get_name(most_recent_push.repo_name) == 'mozilla-central' and \
|
||||
len(push.changesets) >= 2:
|
||||
if (
|
||||
get_name(most_recent_push.repo_name) == "mozilla-central"
|
||||
and len(push.changesets) >= 2
|
||||
):
|
||||
branch = self._choose_integration_branch(most_recent_push.changeset)
|
||||
oldest = push.changesets[0]['node']
|
||||
youngest = push.changesets[-1]['node']
|
||||
LOG.info("************* Switching to %s by"
|
||||
oldest = push.changesets[0]["node"]
|
||||
youngest = push.changesets[-1]["node"]
|
||||
LOG.info(
|
||||
"************* Switching to %s by"
|
||||
" process of elimination (no branch detected in"
|
||||
" commit message)" % branch)
|
||||
" commit message)" % branch
|
||||
)
|
||||
else:
|
||||
return
|
||||
else:
|
||||
# so, this is a merge. see how many changesets are in it, if it
|
||||
# is just one, we have our answer
|
||||
if len(push.changesets) == 2:
|
||||
LOG.info("Merge commit has only two revisions (one of which "
|
||||
"is the merge): we are done")
|
||||
LOG.info(
|
||||
"Merge commit has only two revisions (one of which "
|
||||
"is the merge): we are done"
|
||||
)
|
||||
return
|
||||
|
||||
# Otherwise, we can find the oldest and youngest
|
||||
# changesets, and the branch where the merge comes from.
|
||||
oldest = push.changesets[0]['node']
|
||||
oldest = push.changesets[0]["node"]
|
||||
# exclude the merge commit
|
||||
youngest = push.changesets[-2]['node']
|
||||
youngest = push.changesets[-2]["node"]
|
||||
LOG.info("************* Switching to %s" % branch)
|
||||
|
||||
# we can't use directly the oldest changeset because we
|
||||
|
@ -307,12 +321,8 @@ class IntegrationHandler(BisectorHandler):
|
|||
# changeset. This needs to be done on the right branch.
|
||||
try:
|
||||
jp2 = JsonPushes(branch)
|
||||
raw = [int(p.push_id) for p in
|
||||
jp2.pushes_within_changes(oldest, youngest)]
|
||||
data = jp2.pushes(
|
||||
startID=str(min(raw) - 2),
|
||||
endID=str(max(raw)),
|
||||
)
|
||||
raw = [int(p.push_id) for p in jp2.pushes_within_changes(oldest, youngest)]
|
||||
data = jp2.pushes(startID=str(min(raw) - 2), endID=str(max(raw)),)
|
||||
|
||||
older = data[0].changeset
|
||||
youngest = data[-1].changeset
|
||||
|
@ -325,12 +335,10 @@ class IntegrationHandler(BisectorHandler):
|
|||
raise MozRegressionError(
|
||||
"Unable to exploit the merge commit. Origin branch is {}, and"
|
||||
" the commit message for {} was:\n{}".format(
|
||||
most_recent_push.repo_name,
|
||||
most_recent_push.short_changeset,
|
||||
msg
|
||||
most_recent_push.repo_name, most_recent_push.short_changeset, msg
|
||||
)
|
||||
)
|
||||
LOG.debug('End merge handling')
|
||||
LOG.debug("End merge handling")
|
||||
return result
|
||||
|
||||
|
||||
|
@ -340,14 +348,12 @@ class IndexPromise(object):
|
|||
|
||||
Provide a callable object that gives the next index when called.
|
||||
"""
|
||||
|
||||
def __init__(self, index, callback=None, args=()):
|
||||
self.thread = None
|
||||
self.index = index
|
||||
if callback:
|
||||
self.thread = threading.Thread(
|
||||
target=self._run,
|
||||
args=(callback,) + args
|
||||
)
|
||||
self.thread = threading.Thread(target=self._run, args=(callback,) + args)
|
||||
self.thread.start()
|
||||
|
||||
def _run(self, callback, *args):
|
||||
|
@ -365,8 +371,15 @@ class Bisection(object):
|
|||
FINISHED = 2
|
||||
USER_EXIT = 3
|
||||
|
||||
def __init__(self, handler, build_range, download_manager, test_runner,
|
||||
dl_in_background=True, approx_chooser=None):
|
||||
def __init__(
|
||||
self,
|
||||
handler,
|
||||
build_range,
|
||||
download_manager,
|
||||
test_runner,
|
||||
dl_in_background=True,
|
||||
approx_chooser=None,
|
||||
):
|
||||
self.handler = handler
|
||||
self.build_range = build_range
|
||||
self.download_manager = download_manager
|
||||
|
@ -406,8 +419,7 @@ class Bisection(object):
|
|||
is the dict of build infos for the build.
|
||||
"""
|
||||
build_infos = self.handler.build_range[mid_point]
|
||||
return self._download_build(mid_point, build_infos,
|
||||
allow_bg_download=allow_bg_download)
|
||||
return self._download_build(mid_point, build_infos, allow_bg_download=allow_bg_download)
|
||||
|
||||
def _find_approx_build(self, mid_point, build_infos):
|
||||
approx_index, persist_files = None, ()
|
||||
|
@ -418,27 +430,24 @@ class Bisection(object):
|
|||
# just act as usual, the downloader will take care of it.
|
||||
if build_infos.persist_filename not in persist_files:
|
||||
approx_index = self.approx_chooser.index(
|
||||
self.build_range,
|
||||
build_infos,
|
||||
persist_files
|
||||
self.build_range, build_infos, persist_files
|
||||
)
|
||||
if approx_index is not None:
|
||||
# we found an approx build. First, stop possible background
|
||||
# downloads, then update the mid point and build info.
|
||||
if self.download_manager.background_dl_policy == 'cancel':
|
||||
if self.download_manager.background_dl_policy == "cancel":
|
||||
self.download_manager.cancel()
|
||||
|
||||
old_url = build_infos.build_url
|
||||
mid_point = approx_index
|
||||
build_infos = self.build_range[approx_index]
|
||||
fname = self.download_manager.get_dest(
|
||||
build_infos.persist_filename)
|
||||
LOG.info("Using `%s` as an acceptable approximated"
|
||||
" build file instead of downloading %s"
|
||||
% (fname, old_url))
|
||||
fname = self.download_manager.get_dest(build_infos.persist_filename)
|
||||
LOG.info(
|
||||
"Using `%s` as an acceptable approximated"
|
||||
" build file instead of downloading %s" % (fname, old_url)
|
||||
)
|
||||
build_infos.build_file = fname
|
||||
return (approx_index is not None, mid_point, build_infos,
|
||||
persist_files)
|
||||
return (approx_index is not None, mid_point, build_infos, persist_files)
|
||||
|
||||
def _download_build(self, mid_point, build_infos, allow_bg_download=True):
|
||||
found, mid_point, build_infos, persist_files = self._find_approx_build(
|
||||
|
@ -451,8 +460,7 @@ class Bisection(object):
|
|||
callback = None
|
||||
if self.dl_in_background and allow_bg_download:
|
||||
callback = self._download_next_builds
|
||||
return (IndexPromise(mid_point, callback, args=(persist_files,)),
|
||||
build_infos)
|
||||
return (IndexPromise(mid_point, callback, args=(persist_files,)), build_infos)
|
||||
|
||||
def _download_next_builds(self, mid_point, persist_files=()):
|
||||
# start downloading the next builds.
|
||||
|
@ -466,11 +474,14 @@ class Bisection(object):
|
|||
m = r.mid_point()
|
||||
if len(r) != 0:
|
||||
# non-blocking download of the build
|
||||
if self.approx_chooser and self.approx_chooser.index(
|
||||
r, r[m], persist_files) is not None:
|
||||
if (
|
||||
self.approx_chooser
|
||||
and self.approx_chooser.index(r, r[m], persist_files) is not None
|
||||
):
|
||||
pass # nothing to download, we have an approx build
|
||||
else:
|
||||
self.download_manager.download_in_background(r[m])
|
||||
|
||||
bdata = self.build_range[mid_point]
|
||||
# download next left mid point
|
||||
start_dl(self.build_range[mid_point:])
|
||||
|
@ -483,8 +494,7 @@ class Bisection(object):
|
|||
return self.build_range.index(bdata)
|
||||
|
||||
def evaluate(self, build_infos):
|
||||
verdict = self.test_runner.evaluate(build_infos,
|
||||
allow_back=bool(self.history))
|
||||
verdict = self.test_runner.evaluate(build_infos, allow_back=bool(self.history))
|
||||
# old builds do not have metadata about the repo. But once
|
||||
# the build is installed, we may have it
|
||||
if self.handler.found_repo is None:
|
||||
|
@ -496,8 +506,7 @@ class Bisection(object):
|
|||
if self.handler.find_fix:
|
||||
good, bad = bad, good
|
||||
|
||||
LOG.info("Testing good and bad builds to ensure that they are"
|
||||
" really good and bad...")
|
||||
LOG.info("Testing good and bad builds to ensure that they are" " really good and bad...")
|
||||
self.download_manager.focus_download(good)
|
||||
if self.dl_in_background:
|
||||
self.download_manager.download_in_background(bad)
|
||||
|
@ -507,28 +516,29 @@ class Bisection(object):
|
|||
res = self.test_runner.evaluate(build_info)
|
||||
if res == expected[0]:
|
||||
return True
|
||||
elif res == 's':
|
||||
elif res == "s":
|
||||
LOG.info("You can not skip this build.")
|
||||
elif res == 'e':
|
||||
elif res == "e":
|
||||
return
|
||||
elif res == 'r':
|
||||
elif res == "r":
|
||||
pass
|
||||
else:
|
||||
raise GoodBadExpectationError(
|
||||
"Build was expected to be %s! The initial good/bad"
|
||||
" range seems incorrect." % expected
|
||||
)
|
||||
if _evaluate(good, 'good'):
|
||||
|
||||
if _evaluate(good, "good"):
|
||||
self.download_manager.focus_download(bad)
|
||||
if self.dl_in_background:
|
||||
# download next build (mid) in background
|
||||
self.download_manager.download_in_background(
|
||||
self.build_range[self.build_range.mid_point()]
|
||||
)
|
||||
return _evaluate(bad, 'bad')
|
||||
return _evaluate(bad, "bad")
|
||||
|
||||
def handle_verdict(self, mid_point, verdict):
|
||||
if verdict == 'g':
|
||||
if verdict == "g":
|
||||
# if build is good and we are looking for a regression, we
|
||||
# have to split from
|
||||
# [G, ?, ?, G, ?, B]
|
||||
|
@ -540,7 +550,7 @@ class Bisection(object):
|
|||
else:
|
||||
self.build_range = self.build_range[: mid_point + 1]
|
||||
self.handler.build_good(mid_point, self.build_range)
|
||||
elif verdict == 'b':
|
||||
elif verdict == "b":
|
||||
# if build is bad and we are looking for a regression, we
|
||||
# have to split from
|
||||
# [G, ?, ?, B, ?, B]
|
||||
|
@ -552,13 +562,13 @@ class Bisection(object):
|
|||
else:
|
||||
self.build_range = self.build_range[mid_point:]
|
||||
self.handler.build_bad(mid_point, self.build_range)
|
||||
elif verdict == 'r':
|
||||
elif verdict == "r":
|
||||
self.handler.build_retry(mid_point)
|
||||
elif verdict == 's':
|
||||
elif verdict == "s":
|
||||
self.handler.build_skip(mid_point)
|
||||
self.history.add(self.build_range, mid_point, verdict)
|
||||
self.build_range = self.build_range.deleted(mid_point)
|
||||
elif verdict == 'back':
|
||||
elif verdict == "back":
|
||||
self.build_range = self.history[-1].build_range
|
||||
else:
|
||||
# user exit
|
||||
|
@ -572,8 +582,15 @@ class Bisector(object):
|
|||
Handle the logic of the bisection process, and report events to a given
|
||||
:class:`BisectorHandler`.
|
||||
"""
|
||||
def __init__(self, fetch_config, test_runner, download_manager,
|
||||
dl_in_background=True, approx_chooser=None):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
fetch_config,
|
||||
test_runner,
|
||||
download_manager,
|
||||
dl_in_background=True,
|
||||
approx_chooser=None,
|
||||
):
|
||||
self.fetch_config = fetch_config
|
||||
self.test_runner = test_runner
|
||||
self.download_manager = download_manager
|
||||
|
@ -583,10 +600,7 @@ class Bisector(object):
|
|||
def bisect(self, handler, good, bad, **kwargs):
|
||||
if handler.find_fix:
|
||||
good, bad = bad, good
|
||||
build_range = handler.create_range(self.fetch_config,
|
||||
good,
|
||||
bad,
|
||||
**kwargs)
|
||||
build_range = handler.create_range(self.fetch_config, good, bad, **kwargs)
|
||||
|
||||
return self._bisect(handler, build_range)
|
||||
|
||||
|
@ -595,10 +609,14 @@ class Bisector(object):
|
|||
Starts a bisection for a :class:`mozregression.build_range.BuildData`.
|
||||
"""
|
||||
|
||||
bisection = Bisection(handler, build_range, self.download_manager,
|
||||
bisection = Bisection(
|
||||
handler,
|
||||
build_range,
|
||||
self.download_manager,
|
||||
self.test_runner,
|
||||
dl_in_background=self.dl_in_background,
|
||||
approx_chooser=self.approx_chooser)
|
||||
approx_chooser=self.approx_chooser,
|
||||
)
|
||||
|
||||
previous_verdict = None
|
||||
|
||||
|
@ -609,41 +627,36 @@ class Bisector(object):
|
|||
return result
|
||||
if previous_verdict is None and handler.ensure_good_and_bad:
|
||||
if bisection.ensure_good_and_bad():
|
||||
LOG.info("Good and bad builds are correct. Let's"
|
||||
" continue the bisection.")
|
||||
LOG.info("Good and bad builds are correct. Let's" " continue the bisection.")
|
||||
else:
|
||||
return bisection.USER_EXIT
|
||||
bisection.handler.print_range(full=False)
|
||||
|
||||
if previous_verdict == 'back':
|
||||
if previous_verdict == "back":
|
||||
index = bisection.history.pop(-1).index
|
||||
|
||||
allow_bg_download = True
|
||||
if previous_verdict == 's':
|
||||
if previous_verdict == "s":
|
||||
# disallow background download since we are not sure of what
|
||||
# to download next.
|
||||
allow_bg_download = False
|
||||
index = self.test_runner.index_to_try_after_skip(
|
||||
bisection.build_range
|
||||
)
|
||||
index = self.test_runner.index_to_try_after_skip(bisection.build_range)
|
||||
|
||||
index_promise = None
|
||||
build_info = bisection.build_range[index]
|
||||
try:
|
||||
if previous_verdict != 'r' and build_info:
|
||||
if previous_verdict != "r" and build_info:
|
||||
# if the last verdict was retry, do not download
|
||||
# the build. Futhermore trying to download if we are
|
||||
# in background download mode would stop the next builds
|
||||
# from downloading.
|
||||
index_promise, build_info = bisection.download_build(
|
||||
index,
|
||||
allow_bg_download=allow_bg_download
|
||||
index, allow_bg_download=allow_bg_download
|
||||
)
|
||||
|
||||
if not build_info:
|
||||
LOG.info(
|
||||
"Unable to find build info. Skipping this build...")
|
||||
verdict = 's'
|
||||
LOG.info("Unable to find build info. Skipping this build...")
|
||||
verdict = "s"
|
||||
else:
|
||||
try:
|
||||
verdict = bisection.evaluate(build_info)
|
||||
|
@ -652,7 +665,7 @@ class Bisector(object):
|
|||
# to run the tested app. We can just fallback
|
||||
# to skip the build.
|
||||
LOG.info("Error: %s. Skipping this build..." % exc)
|
||||
verdict = 's'
|
||||
verdict = "s"
|
||||
finally:
|
||||
# be sure to terminate the index_promise thread in all
|
||||
# circumstances.
|
||||
|
|
|
@ -3,26 +3,27 @@ Access to mozilla branches information.
|
|||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
from collections import defaultdict
|
||||
|
||||
import six
|
||||
from mozlog import get_proxy_logger
|
||||
|
||||
from mozregression.errors import MozRegressionError
|
||||
import six
|
||||
|
||||
LOG = get_proxy_logger('Branches')
|
||||
LOG = get_proxy_logger("Branches")
|
||||
|
||||
|
||||
class Branches(object):
|
||||
DEFAULT_REPO_URL = 'https://hg.mozilla.org/'
|
||||
DEFAULT_REPO_URL = "https://hg.mozilla.org/"
|
||||
|
||||
def __init__(self):
|
||||
self._branches = {}
|
||||
self._aliases = {}
|
||||
self._categories = defaultdict(list)
|
||||
|
||||
def set_branch(self, name, path, category='default'):
|
||||
def set_branch(self, name, path, category="default"):
|
||||
assert name not in self._branches, "branch %s already defined" % name
|
||||
self._branches[name] = self.DEFAULT_REPO_URL + path
|
||||
self._categories[category].append(name)
|
||||
|
@ -41,8 +42,7 @@ class Branches(object):
|
|||
try:
|
||||
return self._branches[self.get_name(branch_name_or_alias)]
|
||||
except KeyError:
|
||||
raise MozRegressionError(
|
||||
"No such branch '%s'." % branch_name_or_alias)
|
||||
raise MozRegressionError("No such branch '%s'." % branch_name_or_alias)
|
||||
|
||||
def get_name(self, branch_name_or_alias):
|
||||
return self._aliases.get(branch_name_or_alias) or branch_name_or_alias
|
||||
|
@ -62,22 +62,21 @@ def create_branches():
|
|||
|
||||
# integration branches
|
||||
for name in ("autoland", "mozilla-inbound"):
|
||||
branches.set_branch(name, "integration/%s" % name,
|
||||
category='integration')
|
||||
branches.set_branch(name, "integration/%s" % name, category="integration")
|
||||
|
||||
# release branches
|
||||
for name in ("comm-beta", "comm-release",
|
||||
"mozilla-beta", "mozilla-release"):
|
||||
branches.set_branch(name, "releases/%s" % name, category='releases')
|
||||
for name in ("comm-beta", "comm-release", "mozilla-beta", "mozilla-release"):
|
||||
branches.set_branch(name, "releases/%s" % name, category="releases")
|
||||
|
||||
branches.set_branch('try', 'try', category='try')
|
||||
branches.set_branch("try", "try", category="try")
|
||||
|
||||
# aliases
|
||||
for name, aliases in (
|
||||
("mozilla-central", ("m-c", "central")),
|
||||
("mozilla-inbound", ("m-i", "inbound", "mozilla inbound")),
|
||||
("mozilla-beta", ("m-b", "beta")),
|
||||
("mozilla-release", ("m-r", "release"))):
|
||||
("mozilla-release", ("m-r", "release")),
|
||||
):
|
||||
for alias in aliases:
|
||||
branches.set_alias(alias, name)
|
||||
return branches
|
||||
|
|
|
@ -1,20 +1,22 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
from mozregression.json_pushes import JsonPushes
|
||||
|
||||
RE_BUG_ID = re.compile(r'bug\s+(\d+)', re.I)
|
||||
RE_BUG_ID = re.compile(r"bug\s+(\d+)", re.I)
|
||||
|
||||
|
||||
def find_bugids_in_push(branch, changeset):
|
||||
jp = JsonPushes(branch)
|
||||
push = jp.push(changeset, full='1')
|
||||
push = jp.push(changeset, full="1")
|
||||
branches = set()
|
||||
for chset in push.changesets:
|
||||
res = RE_BUG_ID.search(chset['desc'])
|
||||
res = RE_BUG_ID.search(chset["desc"])
|
||||
if res:
|
||||
branches.add(res.group(1))
|
||||
return [b for b in branches]
|
||||
|
||||
|
||||
def bug_url(bugid):
|
||||
return 'https://bugzilla.mozilla.org/show_bug.cgi?id=%s' % bugid
|
||||
return "https://bugzilla.mozilla.org/show_bug.cgi?id=%s" % bugid
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
The BuildInfo classes, that are used to store information a build.
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import re
|
||||
import datetime
|
||||
from six.moves.urllib.parse import urlparse
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from six.moves.urllib.parse import urlparse
|
||||
|
||||
FIELDS = []
|
||||
|
||||
|
@ -26,8 +27,18 @@ class BuildInfo(object):
|
|||
a BuildInfo is built by calling
|
||||
:meth:`mozregression.fetch_build_info.FetchBuildInfo.find_build_info`.
|
||||
"""
|
||||
def __init__(self, fetch_config, build_type, build_url, build_date,
|
||||
changeset, repo_url, repo_name, task_id=None):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
fetch_config,
|
||||
build_type,
|
||||
build_url,
|
||||
build_date,
|
||||
changeset,
|
||||
repo_url,
|
||||
repo_name,
|
||||
task_id=None,
|
||||
):
|
||||
self._fetch_config = fetch_config
|
||||
self._build_type = build_type
|
||||
self._build_url = build_url
|
||||
|
@ -130,9 +141,9 @@ class BuildInfo(object):
|
|||
This helps to build the pushlog url for old nightlies.
|
||||
"""
|
||||
if self._changeset is None:
|
||||
self._changeset = app_info.get('application_changeset')
|
||||
self._changeset = app_info.get("application_changeset")
|
||||
if self._repo_url is None:
|
||||
self._repo_url = app_info.get('application_repository')
|
||||
self._repo_url = app_info.get("application_repository")
|
||||
|
||||
def persist_filename_for(self, data, regex=True):
|
||||
"""
|
||||
|
@ -146,34 +157,33 @@ class BuildInfo(object):
|
|||
The pattern only allows the build name to be different, by using
|
||||
the fetch_config.build_regex() value. For example, it can return:
|
||||
|
||||
'2015-01-11--mozilla-central--firefox.*linux-x86_64\.tar.bz2$'
|
||||
'2015-01-11--mozilla-central--firefox.*linux-x86_64\\.tar.bz2$'
|
||||
"""
|
||||
if self.build_type == 'nightly':
|
||||
if self.build_type == "nightly":
|
||||
if isinstance(data, datetime.datetime):
|
||||
prefix = data.strftime("%Y-%m-%d-%H-%M-%S")
|
||||
else:
|
||||
prefix = str(data)
|
||||
persist_part = ''
|
||||
persist_part = ""
|
||||
else:
|
||||
prefix = str(data[:12])
|
||||
persist_part = self._fetch_config.integration_persist_part()
|
||||
if persist_part:
|
||||
persist_part = '-' + persist_part
|
||||
full_prefix = '{}{}--{}--'.format(prefix, persist_part, self.repo_name)
|
||||
persist_part = "-" + persist_part
|
||||
full_prefix = "{}{}--{}--".format(prefix, persist_part, self.repo_name)
|
||||
if regex:
|
||||
full_prefix = re.escape(full_prefix)
|
||||
appname = self._fetch_config.build_regex()
|
||||
else:
|
||||
appname = urlparse(self.build_url). \
|
||||
path.replace('%2F', '/').split('/')[-1]
|
||||
return '{}{}'.format(full_prefix, appname)
|
||||
appname = urlparse(self.build_url).path.replace("%2F", "/").split("/")[-1]
|
||||
return "{}{}".format(full_prefix, appname)
|
||||
|
||||
@property
|
||||
def persist_filename(self):
|
||||
"""
|
||||
Compute and return the persist filename to use to store this build.
|
||||
"""
|
||||
if self.build_type == 'nightly':
|
||||
if self.build_type == "nightly":
|
||||
data = self.build_date
|
||||
else:
|
||||
data = self.changeset
|
||||
|
@ -187,17 +197,29 @@ class BuildInfo(object):
|
|||
|
||||
|
||||
class NightlyBuildInfo(BuildInfo):
|
||||
def __init__(self, fetch_config, build_url, build_date, changeset,
|
||||
repo_url):
|
||||
BuildInfo.__init__(self, fetch_config, 'nightly', build_url,
|
||||
build_date, changeset, repo_url,
|
||||
fetch_config.get_nightly_repo(build_date))
|
||||
def __init__(self, fetch_config, build_url, build_date, changeset, repo_url):
|
||||
BuildInfo.__init__(
|
||||
self,
|
||||
fetch_config,
|
||||
"nightly",
|
||||
build_url,
|
||||
build_date,
|
||||
changeset,
|
||||
repo_url,
|
||||
fetch_config.get_nightly_repo(build_date),
|
||||
)
|
||||
|
||||
|
||||
class IntegrationBuildInfo(BuildInfo):
|
||||
def __init__(self, fetch_config, build_url, build_date, changeset,
|
||||
repo_url, task_id=None):
|
||||
BuildInfo.__init__(self, fetch_config, 'integration', build_url,
|
||||
build_date, changeset, repo_url,
|
||||
def __init__(self, fetch_config, build_url, build_date, changeset, repo_url, task_id=None):
|
||||
BuildInfo.__init__(
|
||||
self,
|
||||
fetch_config,
|
||||
"integration",
|
||||
build_url,
|
||||
build_date,
|
||||
changeset,
|
||||
repo_url,
|
||||
fetch_config.integration_branch,
|
||||
task_id=task_id)
|
||||
task_id=task_id,
|
||||
)
|
||||
|
|
|
@ -8,19 +8,18 @@ objects that are loaded on demand. A BuildRange is used for bisecting builds.
|
|||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import copy
|
||||
import datetime
|
||||
|
||||
from threading import Thread
|
||||
from mozlog import get_proxy_logger
|
||||
|
||||
from mozregression.dates import to_date, is_date_or_datetime, \
|
||||
to_datetime
|
||||
from mozregression.errors import BuildInfoNotFound
|
||||
from mozregression.fetch_build_info import (IntegrationInfoFetcher,
|
||||
NightlyInfoFetcher)
|
||||
from mozlog import get_proxy_logger
|
||||
from six.moves import range
|
||||
|
||||
from mozregression.dates import is_date_or_datetime, to_date, to_datetime
|
||||
from mozregression.errors import BuildInfoNotFound
|
||||
from mozregression.fetch_build_info import IntegrationInfoFetcher, NightlyInfoFetcher
|
||||
|
||||
LOG = get_proxy_logger("Bisector")
|
||||
|
||||
|
||||
|
@ -76,6 +75,7 @@ class BuildRange(object):
|
|||
- build_range[0:5] # slice operation, return a new build_range object
|
||||
- build_range.deleted(5) # return a new build_range without item 5
|
||||
"""
|
||||
|
||||
def __init__(self, build_info_fetcher, future_build_infos):
|
||||
self.build_info_fetcher = build_info_fetcher
|
||||
self._future_build_infos = future_build_infos
|
||||
|
@ -90,7 +90,7 @@ class BuildRange(object):
|
|||
def __getitem__(self, item):
|
||||
if isinstance(item, slice):
|
||||
if item.step not in (1, None):
|
||||
raise ValueError('only step=1 supported')
|
||||
raise ValueError("only step=1 supported")
|
||||
new_range = copy.copy(self)
|
||||
new_range._future_build_infos = self._future_build_infos[item.start : item.stop]
|
||||
return new_range
|
||||
|
@ -99,26 +99,23 @@ class BuildRange(object):
|
|||
|
||||
def deleted(self, pos, count=1):
|
||||
new_range = copy.copy(self)
|
||||
new_range._future_build_infos = \
|
||||
self._future_build_infos[:pos] + \
|
||||
self._future_build_infos[pos + count:]
|
||||
new_range._future_build_infos = (
|
||||
self._future_build_infos[:pos] + self._future_build_infos[pos + count :]
|
||||
)
|
||||
return new_range
|
||||
|
||||
def filter_invalid_builds(self):
|
||||
"""
|
||||
Remove items that were unable to load BuildInfos.
|
||||
"""
|
||||
self._future_build_infos = \
|
||||
[b for b in self._future_build_infos if b.is_valid()]
|
||||
self._future_build_infos = [b for b in self._future_build_infos if b.is_valid()]
|
||||
|
||||
def _fetch(self, indexes):
|
||||
indexes = set(indexes)
|
||||
need_fetch = any(not self._future_build_infos[i].is_available()
|
||||
for i in indexes)
|
||||
need_fetch = any(not self._future_build_infos[i].is_available() for i in indexes)
|
||||
if not need_fetch:
|
||||
return
|
||||
threads = [Thread(target=self.__getitem__, args=(i,))
|
||||
for i in indexes]
|
||||
threads = [Thread(target=self.__getitem__, args=(i,)) for i in indexes]
|
||||
for thread in threads:
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
@ -206,23 +203,25 @@ class BuildRange(object):
|
|||
if self.get_future(0) != first:
|
||||
new_first = search_last(range_before(first, expand))
|
||||
if new_first:
|
||||
LOG.info(
|
||||
"Expanding lower limit of the range to %s" % new_first)
|
||||
LOG.info("Expanding lower limit of the range to %s" % new_first)
|
||||
self._future_build_infos.insert(0, new_first)
|
||||
else:
|
||||
LOG.critical("First build %s is missing, but mozregression"
|
||||
LOG.critical(
|
||||
"First build %s is missing, but mozregression"
|
||||
" can't find a build before - so it is excluded,"
|
||||
" but it could contain the regression!" % first)
|
||||
" but it could contain the regression!" % first
|
||||
)
|
||||
if self.get_future(-1) != last:
|
||||
new_last = search_first(range_after(last, expand))
|
||||
if new_last:
|
||||
LOG.info(
|
||||
"Expanding higher limit of the range to %s" % new_last)
|
||||
LOG.info("Expanding higher limit of the range to %s" % new_last)
|
||||
self._future_build_infos.append(new_last)
|
||||
else:
|
||||
LOG.critical("Last build %s is missing, but mozregression"
|
||||
LOG.critical(
|
||||
"Last build %s is missing, but mozregression"
|
||||
" can't find a build after - so it is excluded,"
|
||||
" but it could contain the regression!" % last)
|
||||
" but it could contain the regression!" % last
|
||||
)
|
||||
|
||||
def index(self, build_info):
|
||||
"""
|
||||
|
@ -257,8 +256,7 @@ def _tc_build_range(future_tc, start_id, end_id):
|
|||
|
||||
def tc_range_after(future_tc, size):
|
||||
"""Create a build range after a TCFutureBuildInfo"""
|
||||
return _tc_build_range(future_tc, future_tc.data.push_id,
|
||||
int(future_tc.data.push_id) + size)
|
||||
return _tc_build_range(future_tc, future_tc.data.push_id, int(future_tc.data.push_id) + size)
|
||||
|
||||
|
||||
def tc_range_before(future_tc, size):
|
||||
|
@ -267,24 +265,23 @@ def tc_range_before(future_tc, size):
|
|||
return _tc_build_range(future_tc, p_id - size, p_id)
|
||||
|
||||
|
||||
def get_integration_range(fetch_config, start_rev, end_rev, time_limit=None,
|
||||
expand=0, interrupt=None):
|
||||
def get_integration_range(
|
||||
fetch_config, start_rev, end_rev, time_limit=None, expand=0, interrupt=None
|
||||
):
|
||||
"""
|
||||
Creates a BuildRange for integration builds.
|
||||
"""
|
||||
info_fetcher = IntegrationInfoFetcher(fetch_config)
|
||||
jpushes = info_fetcher.jpushes
|
||||
|
||||
time_limit = time_limit or (datetime.datetime.now() +
|
||||
datetime.timedelta(days=-365))
|
||||
time_limit = time_limit or (datetime.datetime.now() + datetime.timedelta(days=-365))
|
||||
|
||||
def _check_date(obj):
|
||||
if is_date_or_datetime(obj):
|
||||
if to_datetime(obj) < time_limit:
|
||||
LOG.info(
|
||||
"TaskCluster only keeps builds for one year."
|
||||
" Using %s instead of %s."
|
||||
% (time_limit, obj)
|
||||
" Using %s instead of %s." % (time_limit, obj)
|
||||
)
|
||||
obj = time_limit
|
||||
return obj
|
||||
|
@ -292,18 +289,17 @@ def get_integration_range(fetch_config, start_rev, end_rev, time_limit=None,
|
|||
start_rev = _check_date(start_rev)
|
||||
end_rev = _check_date(end_rev)
|
||||
|
||||
futures_builds = [TCFutureBuildInfo(info_fetcher, push)
|
||||
for push in jpushes.pushes_within_changes(start_rev,
|
||||
end_rev)]
|
||||
futures_builds = [
|
||||
TCFutureBuildInfo(info_fetcher, push)
|
||||
for push in jpushes.pushes_within_changes(start_rev, end_rev)
|
||||
]
|
||||
br = BuildRange(info_fetcher, futures_builds)
|
||||
if expand > 0:
|
||||
br.check_expand(expand, tc_range_before, tc_range_after,
|
||||
interrupt=interrupt)
|
||||
br.check_expand(expand, tc_range_before, tc_range_after, interrupt=interrupt)
|
||||
return br
|
||||
|
||||
|
||||
def get_nightly_range(fetch_config, start_date, end_date, expand=0,
|
||||
interrupt=None):
|
||||
def get_nightly_range(fetch_config, start_date, end_date, expand=0, interrupt=None):
|
||||
"""
|
||||
Creates a BuildRange for nightlies.
|
||||
"""
|
||||
|
@ -312,12 +308,7 @@ def get_nightly_range(fetch_config, start_date, end_date, expand=0,
|
|||
# build the build range using only dates
|
||||
sd = to_date(start_date)
|
||||
for i in range((to_date(end_date) - sd).days + 1):
|
||||
futures_builds.append(
|
||||
FutureBuildInfo(
|
||||
info_fetcher,
|
||||
sd + datetime.timedelta(days=i)
|
||||
)
|
||||
)
|
||||
futures_builds.append(FutureBuildInfo(info_fetcher, sd + datetime.timedelta(days=i)))
|
||||
# and now put back the real start and end dates
|
||||
# in case they were datetime instances (coming from buildid)
|
||||
futures_builds[0].data = start_date
|
||||
|
|
|
@ -11,7 +11,8 @@ class ClassRegistry(object):
|
|||
:param attr_name: On each registered class, the unique name will be saved
|
||||
under this class attribute name.
|
||||
"""
|
||||
def __init__(self, attr_name='name'):
|
||||
|
||||
def __init__(self, attr_name="name"):
|
||||
self._classes = {}
|
||||
self.attr_name = attr_name
|
||||
|
||||
|
@ -32,6 +33,7 @@ class ClassRegistry(object):
|
|||
for key, value in six.iteritems(kwargs):
|
||||
setattr(klass, key, value)
|
||||
return klass
|
||||
|
||||
return wrapper
|
||||
|
||||
def get(self, name):
|
||||
|
|
|
@ -10,37 +10,39 @@ application.
|
|||
:func:`cli` is intended to be the only public interface of this module.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import mozinfo
|
||||
import datetime
|
||||
import mozprofile
|
||||
import re
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
from argparse import ArgumentParser, Action, SUPPRESS
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
from argparse import SUPPRESS, Action, ArgumentParser
|
||||
|
||||
import mozinfo
|
||||
import mozprofile
|
||||
from mozlog.structuredlog import get_default_logger
|
||||
|
||||
from mozregression import __version__
|
||||
from mozregression.branches import get_name
|
||||
from mozregression.dates import to_datetime, parse_date, is_date_or_datetime
|
||||
from mozregression.config import get_defaults, DEFAULT_CONF_FNAME, write_conf
|
||||
from mozregression.config import DEFAULT_CONF_FNAME, get_defaults, write_conf
|
||||
from mozregression.dates import is_date_or_datetime, parse_date, to_datetime
|
||||
from mozregression.errors import DateFormatError, MozRegressionError, UnavailableRelease
|
||||
from mozregression.fetch_configs import REGISTRY as FC_REGISTRY
|
||||
from mozregression.fetch_configs import create_config
|
||||
from mozregression.log import colorize, init_logger
|
||||
from mozregression.releases import (
|
||||
date_of_release,
|
||||
formatted_valid_release_dates,
|
||||
tag_of_beta,
|
||||
tag_of_release,
|
||||
)
|
||||
from mozregression.tc_authenticate import tc_authenticate
|
||||
from mozregression.fetch_configs import REGISTRY as FC_REGISTRY, create_config
|
||||
from mozregression.errors import (MozRegressionError, DateFormatError,
|
||||
UnavailableRelease)
|
||||
from mozregression.releases import (formatted_valid_release_dates,
|
||||
date_of_release, tag_of_release,
|
||||
tag_of_beta)
|
||||
from mozregression.log import init_logger, colorize
|
||||
|
||||
|
||||
class _StopAction(Action):
|
||||
def __init__(self, option_strings, dest=SUPPRESS, default=SUPPRESS,
|
||||
help=None):
|
||||
super(_StopAction, self).__init__(option_strings=option_strings,
|
||||
dest=dest, default=default,
|
||||
nargs=0, help=help)
|
||||
def __init__(self, option_strings, dest=SUPPRESS, default=SUPPRESS, help=None):
|
||||
super(_StopAction, self).__init__(
|
||||
option_strings=option_strings, dest=dest, default=default, nargs=0, help=help,
|
||||
)
|
||||
|
||||
def __call__(self, parser, namespace, values, option_string=None):
|
||||
raise NotImplementedError
|
||||
|
@ -80,7 +82,8 @@ def create_parser(defaults):
|
|||
"""
|
||||
Create the mozregression command line parser (ArgumentParser instance).
|
||||
"""
|
||||
usage = ("\n"
|
||||
usage = (
|
||||
"\n"
|
||||
" %(prog)s [OPTIONS]"
|
||||
" [--bad DATE|BUILDID|RELEASE|CHANGESET]"
|
||||
" [--good DATE|BUILDID|RELEASE|CHANGESET]"
|
||||
|
@ -91,16 +94,23 @@ def create_parser(defaults):
|
|||
"\n"
|
||||
" %(prog)s --list-releases"
|
||||
"\n"
|
||||
" %(prog)s --write-conf")
|
||||
" %(prog)s --write-conf"
|
||||
)
|
||||
|
||||
parser = ArgumentParser(usage=usage)
|
||||
parser.add_argument("--version", action="version", version=__version__,
|
||||
help=("print the mozregression version number and"
|
||||
" exits."))
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version=__version__,
|
||||
help=("print the mozregression version number and" " exits."),
|
||||
)
|
||||
|
||||
parser.add_argument("-b", "--bad",
|
||||
parser.add_argument(
|
||||
"-b",
|
||||
"--bad",
|
||||
metavar="DATE|BUILDID|RELEASE|CHANGESET",
|
||||
help=("first known bad build, default is today."
|
||||
help=(
|
||||
"first known bad build, default is today."
|
||||
" It can be a date (YYYY-MM-DD), a build id,"
|
||||
" a release number or a changeset. If it is"
|
||||
" a changeset, the default branch will be the"
|
||||
|
@ -108,56 +118,81 @@ def create_parser(defaults):
|
|||
" (e.g. mozilla-inbound for firefox), else"
|
||||
" the default release branch for the application"
|
||||
" will be used as the default (e.g"
|
||||
" mozilla-central for firefox)."))
|
||||
" mozilla-central for firefox)."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument("-g", "--good",
|
||||
parser.add_argument(
|
||||
"-g",
|
||||
"--good",
|
||||
metavar="DATE|BUILDID|RELEASE|CHANGESET",
|
||||
help=("last known good build. Same possible values"
|
||||
" as the --bad option."))
|
||||
help=("last known good build. Same possible values" " as the --bad option."),
|
||||
)
|
||||
|
||||
parser.add_argument("--list-releases",
|
||||
action=ListReleasesAction,
|
||||
help="list all known releases and exit")
|
||||
parser.add_argument(
|
||||
"--list-releases", action=ListReleasesAction, help="list all known releases and exit",
|
||||
)
|
||||
|
||||
parser.add_argument("-B", "--build-type",
|
||||
parser.add_argument(
|
||||
"-B",
|
||||
"--build-type",
|
||||
default=defaults["build-type"],
|
||||
help=("Build type to use, e.g. opt, debug. "
|
||||
help=(
|
||||
"Build type to use, e.g. opt, debug. "
|
||||
"See --list-build-types for available values. "
|
||||
"Defaults to shippable for desktop Fx, opt for "
|
||||
"everything else."))
|
||||
"everything else."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument("--list-build-types", action=ListBuildTypesAction,
|
||||
help="List available build types combinations.")
|
||||
parser.add_argument(
|
||||
"--list-build-types",
|
||||
action=ListBuildTypesAction,
|
||||
help="List available build types combinations.",
|
||||
)
|
||||
|
||||
parser.add_argument("--find-fix", action="store_true",
|
||||
help="Search for a bug fix instead of a regression.")
|
||||
parser.add_argument(
|
||||
"--find-fix", action="store_true", help="Search for a bug fix instead of a regression.",
|
||||
)
|
||||
|
||||
parser.add_argument("-e", "--addon",
|
||||
parser.add_argument(
|
||||
"-e",
|
||||
"--addon",
|
||||
dest="addons",
|
||||
action='append',
|
||||
action="append",
|
||||
default=[],
|
||||
metavar="PATH1",
|
||||
help="addon to install; repeat for multiple addons.")
|
||||
help="addon to install; repeat for multiple addons.",
|
||||
)
|
||||
|
||||
parser.add_argument("-p", "--profile",
|
||||
parser.add_argument(
|
||||
"-p",
|
||||
"--profile",
|
||||
default=defaults["profile"],
|
||||
metavar="PATH",
|
||||
help="profile to use with nightlies.")
|
||||
help="profile to use with nightlies.",
|
||||
)
|
||||
|
||||
parser.add_argument('--adb-profile-dir',
|
||||
parser.add_argument(
|
||||
"--adb-profile-dir",
|
||||
dest="adb_profile_dir",
|
||||
default=defaults["adb-profile-dir"],
|
||||
help=("Path to use on android devices for storing"
|
||||
help=(
|
||||
"Path to use on android devices for storing"
|
||||
" the profile. Generally you should not need"
|
||||
" to specify this, and an appropriate path"
|
||||
" will be used. Specifying this to a value,"
|
||||
" e.g. '/sdcard/tests' will forcibly try to create"
|
||||
" the profile inside that folder."))
|
||||
" the profile inside that folder."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument('--profile-persistence',
|
||||
choices=('clone', 'clone-first', 'reuse'),
|
||||
parser.add_argument(
|
||||
"--profile-persistence",
|
||||
choices=("clone", "clone-first", "reuse"),
|
||||
default=defaults["profile-persistence"],
|
||||
help=("Persistence of the used profile. Before"
|
||||
help=(
|
||||
"Persistence of the used profile. Before"
|
||||
" each tested build, a profile is used. If"
|
||||
" the value of this option is 'clone', each"
|
||||
" test uses a fresh clone. If the value is"
|
||||
|
@ -166,96 +201,151 @@ def create_parser(defaults):
|
|||
" bisection. If the value is 'reuse', the given"
|
||||
" profile is directly used. Note that the"
|
||||
" profile might be modified by mozregression."
|
||||
" Defaults to %(default)s."))
|
||||
" Defaults to %(default)s."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument("-a", "--arg",
|
||||
parser.add_argument(
|
||||
"-a",
|
||||
"--arg",
|
||||
dest="cmdargs",
|
||||
action='append',
|
||||
action="append",
|
||||
default=[],
|
||||
metavar="ARG1",
|
||||
help=("a command-line argument to pass to the"
|
||||
help=(
|
||||
"a command-line argument to pass to the"
|
||||
" application; repeat for multiple arguments."
|
||||
" Use --arg='-option' to pass in options"
|
||||
" starting with `-`."))
|
||||
" starting with `-`."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument('--pref', nargs='*', dest='prefs',
|
||||
help=(" A preference to set. Must be a key-value pair"
|
||||
parser.add_argument(
|
||||
"--pref",
|
||||
nargs="*",
|
||||
dest="prefs",
|
||||
help=(
|
||||
" A preference to set. Must be a key-value pair"
|
||||
" separated by a ':'. Note that if your"
|
||||
" preference is of type float, you should"
|
||||
" pass it as a string, e.g.:"
|
||||
" --pref \"layers.low-precision-opacity:'0.0'\""
|
||||
))
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument('--preferences', nargs="*", dest='prefs_files',
|
||||
help=("read preferences from a JSON or INI file. For"
|
||||
parser.add_argument(
|
||||
"--preferences",
|
||||
nargs="*",
|
||||
dest="prefs_files",
|
||||
help=(
|
||||
"read preferences from a JSON or INI file. For"
|
||||
" INI, use 'file.ini:section' to specify a"
|
||||
" particular section."))
|
||||
" particular section."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument("-n", "--app",
|
||||
parser.add_argument(
|
||||
"-n",
|
||||
"--app",
|
||||
choices=FC_REGISTRY.names(),
|
||||
default=defaults["app"],
|
||||
help="application name. Default: %(default)s.")
|
||||
help="application name. Default: %(default)s.",
|
||||
)
|
||||
|
||||
parser.add_argument("--repo",
|
||||
parser.add_argument(
|
||||
"--repo",
|
||||
metavar="[mozilla-inbound|autoland|mozilla-beta...]",
|
||||
default=defaults["repo"],
|
||||
help="repository name used for the bisection.")
|
||||
help="repository name used for the bisection.",
|
||||
)
|
||||
|
||||
parser.add_argument("--bits",
|
||||
parser.add_argument(
|
||||
"--bits",
|
||||
choices=("32", "64"),
|
||||
default=defaults["bits"],
|
||||
help=("force 32 or 64 bit version (only applies to"
|
||||
" x86_64 boxes). Default: %s bits."
|
||||
% defaults["bits"] or mozinfo.bits))
|
||||
help=(
|
||||
"force 32 or 64 bit version (only applies to"
|
||||
" x86_64 boxes). Default: %s bits." % defaults["bits"]
|
||||
or mozinfo.bits
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument("-c", "--command",
|
||||
help=("Test command to evaluate builds automatically."
|
||||
parser.add_argument(
|
||||
"-c",
|
||||
"--command",
|
||||
help=(
|
||||
"Test command to evaluate builds automatically."
|
||||
" A return code of 0 will evaluate the build as"
|
||||
" good, and any other value as bad."
|
||||
" Variables like {binary} can be used, which"
|
||||
" will be replaced with their value as retrieved"
|
||||
" by the actual build."
|
||||
))
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument("--persist",
|
||||
parser.add_argument(
|
||||
"--persist",
|
||||
default=defaults["persist"],
|
||||
help=("the directory in which downloaded files are"
|
||||
" to persist. Defaults to %(default)r."))
|
||||
help=(
|
||||
"the directory in which downloaded files are" " to persist. Defaults to %(default)r."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument('--persist-size-limit', type=float,
|
||||
default=defaults['persist-size-limit'],
|
||||
help=("Size limit for the persist directory in"
|
||||
parser.add_argument(
|
||||
"--persist-size-limit",
|
||||
type=float,
|
||||
default=defaults["persist-size-limit"],
|
||||
help=(
|
||||
"Size limit for the persist directory in"
|
||||
" gigabytes (GiB). When the limit is reached,"
|
||||
" old builds are removed. 0 means no limit. Note"
|
||||
" that at least 5 build files are kept,"
|
||||
" regardless of this value."
|
||||
" Defaults to %(default)s."))
|
||||
" Defaults to %(default)s."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument('--http-timeout', type=float,
|
||||
default=float(defaults['http-timeout']),
|
||||
help=("Timeout in seconds to abort requests when there"
|
||||
parser.add_argument(
|
||||
"--http-timeout",
|
||||
type=float,
|
||||
default=float(defaults["http-timeout"]),
|
||||
help=(
|
||||
"Timeout in seconds to abort requests when there"
|
||||
" is no activity from the server. Default to"
|
||||
" %(default)s seconds - increase this if you"
|
||||
" are under a really slow network."))
|
||||
" are under a really slow network."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument('--no-background-dl', action='store_false',
|
||||
parser.add_argument(
|
||||
"--no-background-dl",
|
||||
action="store_false",
|
||||
dest="background_dl",
|
||||
default=(defaults['no-background-dl'].lower()
|
||||
not in ('1', 'yes', 'true')),
|
||||
help=("Do not download next builds in the background"
|
||||
" while evaluating the current build."))
|
||||
default=(defaults["no-background-dl"].lower() not in ("1", "yes", "true")),
|
||||
help=(
|
||||
"Do not download next builds in the background" " while evaluating the current build."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument('--background-dl-policy', choices=('cancel', 'keep'),
|
||||
default=defaults['background-dl-policy'],
|
||||
help=('Policy to use for background downloads.'
|
||||
parser.add_argument(
|
||||
"--background-dl-policy",
|
||||
choices=("cancel", "keep"),
|
||||
default=defaults["background-dl-policy"],
|
||||
help=(
|
||||
"Policy to use for background downloads."
|
||||
' Possible values are "cancel" to cancel all'
|
||||
' pending background downloads or "keep" to keep'
|
||||
' downloading them when persist mode is enabled.'
|
||||
' The default is %(default)s.'))
|
||||
" downloading them when persist mode is enabled."
|
||||
" The default is %(default)s."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument('--approx-policy', choices=('auto', 'none'),
|
||||
default=defaults['approx-policy'],
|
||||
help=("Policy to reuse approximate persistent builds"
|
||||
parser.add_argument(
|
||||
"--approx-policy",
|
||||
choices=("auto", "none"),
|
||||
default=defaults["approx-policy"],
|
||||
help=(
|
||||
"Policy to reuse approximate persistent builds"
|
||||
" instead of downloading the accurate ones."
|
||||
" When auto, mozregression will try its best to"
|
||||
" reuse the files, meaning that for 7 days of"
|
||||
|
@ -263,36 +353,51 @@ def create_parser(defaults):
|
|||
" which date approximates the build to download"
|
||||
" by one day (before or after). Use none to"
|
||||
" disable this behavior."
|
||||
" Defaults to %(default)s."))
|
||||
" Defaults to %(default)s."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument('--launch',
|
||||
parser.add_argument(
|
||||
"--launch",
|
||||
metavar="DATE|BUILDID|RELEASE|CHANGESET",
|
||||
help=("Launch only one specific build. Same possible"
|
||||
" values as the --bad option."))
|
||||
help=("Launch only one specific build. Same possible" " values as the --bad option."),
|
||||
)
|
||||
|
||||
parser.add_argument('-P', '--process-output', choices=('none', 'stdout'),
|
||||
default=defaults['process-output'],
|
||||
help=("Manage process output logging. Set to stdout by"
|
||||
" default when the build type is not 'opt'."))
|
||||
parser.add_argument(
|
||||
"-P",
|
||||
"--process-output",
|
||||
choices=("none", "stdout"),
|
||||
default=defaults["process-output"],
|
||||
help=(
|
||||
"Manage process output logging. Set to stdout by"
|
||||
" default when the build type is not 'opt'."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument('-M', '--mode', choices=('classic', 'no-first-check'),
|
||||
default=defaults['mode'],
|
||||
help=("bisection mode. 'classic' will check for the"
|
||||
parser.add_argument(
|
||||
"-M",
|
||||
"--mode",
|
||||
choices=("classic", "no-first-check"),
|
||||
default=defaults["mode"],
|
||||
help=(
|
||||
"bisection mode. 'classic' will check for the"
|
||||
" first good and bad builds to really be good"
|
||||
" and bad, and 'no-first-check' won't. Defaults"
|
||||
" to %(default)s."))
|
||||
" to %(default)s."
|
||||
),
|
||||
)
|
||||
|
||||
parser.add_argument('--archive-base-url',
|
||||
default=defaults['archive-base-url'],
|
||||
help=("Base url used to find the archived builds."
|
||||
" Defaults to %(default)s"))
|
||||
parser.add_argument(
|
||||
"--archive-base-url",
|
||||
default=defaults["archive-base-url"],
|
||||
help=("Base url used to find the archived builds." " Defaults to %(default)s"),
|
||||
)
|
||||
|
||||
parser.add_argument('--write-config',
|
||||
action=WriteConfigAction,
|
||||
help="Helps to write the configuration file.")
|
||||
parser.add_argument(
|
||||
"--write-config", action=WriteConfigAction, help="Helps to write the configuration file.",
|
||||
)
|
||||
|
||||
parser.add_argument('--debug', '-d', action='store_true',
|
||||
help='Show the debug output.')
|
||||
parser.add_argument("--debug", "-d", action="store_true", help="Show the debug output.")
|
||||
|
||||
return parser
|
||||
|
||||
|
@ -320,13 +425,13 @@ def preferences(prefs_files, prefs_args, logger):
|
|||
for prefs_file in prefs_files:
|
||||
prefs.add_file(prefs_file)
|
||||
|
||||
separator = ':'
|
||||
separator = ":"
|
||||
cli_prefs = []
|
||||
if prefs_args:
|
||||
for pref in prefs_args:
|
||||
if separator not in pref:
|
||||
if logger:
|
||||
if '=' in pref:
|
||||
if "=" in pref:
|
||||
logger.warning('Pref %s has an "=", did you mean to use ":"?' % pref)
|
||||
logger.info('Dropping pref %s for missing separator ":"' % pref)
|
||||
continue
|
||||
|
@ -343,14 +448,14 @@ def get_default_date_range(fetch_config):
|
|||
Compute the default date range (first, last) to bisect.
|
||||
"""
|
||||
last_date = datetime.date.today()
|
||||
if fetch_config.app_name == 'jsshell':
|
||||
if fetch_config.app_name == "jsshell":
|
||||
if fetch_config.os == "win" and fetch_config.bits == 64:
|
||||
first_date = datetime.date(2014, 5, 27)
|
||||
elif fetch_config.os == "linux" and "asan" in fetch_config.build_type:
|
||||
first_date = datetime.date(2013, 9, 1)
|
||||
else:
|
||||
first_date = datetime.date(2012, 4, 18)
|
||||
elif fetch_config.os == 'win' and fetch_config.bits == 64:
|
||||
elif fetch_config.os == "win" and fetch_config.bits == 64:
|
||||
# first firefox build date for win64 is 2010-05-28
|
||||
first_date = datetime.date(2010, 5, 28)
|
||||
else:
|
||||
|
@ -377,17 +482,19 @@ class Configuration(object):
|
|||
:attr fetch_config: the fetch_config instance, required to find
|
||||
information about a build
|
||||
"""
|
||||
|
||||
def __init__(self, options):
|
||||
self.options = options
|
||||
self.logger = init_logger(debug=options.debug)
|
||||
# allow to filter process output based on the user option
|
||||
if options.process_output is None:
|
||||
# process_output not user defined
|
||||
log_process_output = options.build_type != ''
|
||||
log_process_output = options.build_type != ""
|
||||
else:
|
||||
log_process_output = options.process_output == 'stdout'
|
||||
get_default_logger("process").component_filter = \
|
||||
log_process_output = options.process_output == "stdout"
|
||||
get_default_logger("process").component_filter = (
|
||||
lambda data: data if log_process_output else None
|
||||
)
|
||||
|
||||
# filter some mozversion log lines
|
||||
re_ignore_mozversion_line = re.compile(
|
||||
|
@ -395,7 +502,7 @@ class Configuration(object):
|
|||
r"|application_id|application_display_name): .+"
|
||||
)
|
||||
get_default_logger("mozversion").component_filter = lambda data: (
|
||||
None if re_ignore_mozversion_line.match(data['message']) else data
|
||||
None if re_ignore_mozversion_line.match(data["message"]) else data
|
||||
)
|
||||
|
||||
self.action = None
|
||||
|
@ -410,28 +517,27 @@ class Configuration(object):
|
|||
except DateFormatError:
|
||||
try:
|
||||
repo = self.options.repo
|
||||
if (get_name(repo) == 'mozilla-release' or
|
||||
(not repo and re.match(r'^\d+\.\d\.\d$', value))):
|
||||
if get_name(repo) == "mozilla-release" or (
|
||||
not repo and re.match(r"^\d+\.\d\.\d$", value)
|
||||
):
|
||||
new_value = tag_of_release(value)
|
||||
if not repo:
|
||||
self.logger.info("Assuming repo mozilla-release")
|
||||
self.fetch_config.set_repo('mozilla-release')
|
||||
self.logger.info("Using tag %s for release %s"
|
||||
% (new_value, value))
|
||||
self.fetch_config.set_repo("mozilla-release")
|
||||
self.logger.info("Using tag %s for release %s" % (new_value, value))
|
||||
value = new_value
|
||||
elif (get_name(repo) == 'mozilla-beta' or
|
||||
(not repo and re.match(r'^\d+\.0b\d+$', value))):
|
||||
elif get_name(repo) == "mozilla-beta" or (
|
||||
not repo and re.match(r"^\d+\.0b\d+$", value)
|
||||
):
|
||||
new_value = tag_of_beta(value)
|
||||
if not repo:
|
||||
self.logger.info("Assuming repo mozilla-beta")
|
||||
self.fetch_config.set_repo('mozilla-beta')
|
||||
self.logger.info("Using tag %s for release %s"
|
||||
% (new_value, value))
|
||||
self.fetch_config.set_repo("mozilla-beta")
|
||||
self.logger.info("Using tag %s for release %s" % (new_value, value))
|
||||
value = new_value
|
||||
else:
|
||||
new_value = parse_date(date_of_release(value))
|
||||
self.logger.info("Using date %s for release %s"
|
||||
% (new_value, value))
|
||||
self.logger.info("Using date %s for release %s" % (new_value, value))
|
||||
value = new_value
|
||||
except UnavailableRelease:
|
||||
self.logger.info("%s is not a release, assuming it's a hash..." % value)
|
||||
|
@ -446,30 +552,28 @@ class Configuration(object):
|
|||
|
||||
user_defined_bits = options.bits is not None
|
||||
options.bits = parse_bits(options.bits or mozinfo.bits)
|
||||
fetch_config = create_config(options.app, mozinfo.os, options.bits,
|
||||
mozinfo.processor)
|
||||
fetch_config = create_config(options.app, mozinfo.os, options.bits, mozinfo.processor)
|
||||
if options.build_type:
|
||||
try:
|
||||
fetch_config.set_build_type(options.build_type)
|
||||
except MozRegressionError as msg:
|
||||
self.logger.warning(
|
||||
"%s (Defaulting to %r)" % (msg, fetch_config.build_type)
|
||||
)
|
||||
self.logger.warning("%s (Defaulting to %r)" % (msg, fetch_config.build_type))
|
||||
self.fetch_config = fetch_config
|
||||
|
||||
fetch_config.set_repo(options.repo)
|
||||
fetch_config.set_base_url(options.archive_base_url)
|
||||
|
||||
if not user_defined_bits and \
|
||||
options.bits == 64 and \
|
||||
mozinfo.os == 'win' and \
|
||||
32 in fetch_config.available_bits():
|
||||
if (
|
||||
not user_defined_bits
|
||||
and options.bits == 64
|
||||
and mozinfo.os == "win"
|
||||
and 32 in fetch_config.available_bits()
|
||||
):
|
||||
# inform users on windows that we are using 64 bit builds.
|
||||
self.logger.info("bits option not specified, using 64-bit builds.")
|
||||
|
||||
if options.bits == 32 and mozinfo.os == 'mac':
|
||||
self.logger.info("only 64-bit builds available for mac, using "
|
||||
"64-bit builds")
|
||||
if options.bits == 32 and mozinfo.os == "mac":
|
||||
self.logger.info("only 64-bit builds available for mac, using " "64-bit builds")
|
||||
|
||||
if fetch_config.tk_needs_auth():
|
||||
creds = tc_authenticate(self.logger)
|
||||
|
@ -479,50 +583,52 @@ class Configuration(object):
|
|||
if options.launch:
|
||||
options.launch = self._convert_to_bisect_arg(options.launch)
|
||||
self.action = "launch_integration"
|
||||
if is_date_or_datetime(options.launch) and \
|
||||
fetch_config.should_use_archive():
|
||||
if is_date_or_datetime(options.launch) and fetch_config.should_use_archive():
|
||||
self.action = "launch_nightlies"
|
||||
else:
|
||||
# define good/bad default values if required
|
||||
default_good_date, default_bad_date = \
|
||||
get_default_date_range(fetch_config)
|
||||
default_good_date, default_bad_date = get_default_date_range(fetch_config)
|
||||
if options.find_fix:
|
||||
default_bad_date, default_good_date = \
|
||||
default_good_date, default_bad_date
|
||||
default_bad_date, default_good_date = (
|
||||
default_good_date,
|
||||
default_bad_date,
|
||||
)
|
||||
if not options.bad:
|
||||
options.bad = default_bad_date
|
||||
self.logger.info("No 'bad' option specified, using %s"
|
||||
% options.bad)
|
||||
self.logger.info("No 'bad' option specified, using %s" % options.bad)
|
||||
else:
|
||||
options.bad = self._convert_to_bisect_arg(options.bad)
|
||||
if not options.good:
|
||||
options.good = default_good_date
|
||||
self.logger.info("No 'good' option specified, using %s"
|
||||
% options.good)
|
||||
self.logger.info("No 'good' option specified, using %s" % options.good)
|
||||
else:
|
||||
options.good = self._convert_to_bisect_arg(options.good)
|
||||
|
||||
self.action = "bisect_integration"
|
||||
if is_date_or_datetime(options.good) and \
|
||||
is_date_or_datetime(options.bad):
|
||||
if not options.find_fix and \
|
||||
to_datetime(options.good) > to_datetime(options.bad):
|
||||
if is_date_or_datetime(options.good) and is_date_or_datetime(options.bad):
|
||||
if not options.find_fix and to_datetime(options.good) > to_datetime(options.bad):
|
||||
raise MozRegressionError(
|
||||
("Good date %s is later than bad date %s."
|
||||
(
|
||||
"Good date %s is later than bad date %s."
|
||||
" Maybe you wanted to use the --find-fix"
|
||||
" flag?") % (options.good, options.bad))
|
||||
elif options.find_fix and \
|
||||
to_datetime(options.good) < to_datetime(options.bad):
|
||||
" flag?"
|
||||
)
|
||||
% (options.good, options.bad)
|
||||
)
|
||||
elif options.find_fix and to_datetime(options.good) < to_datetime(options.bad):
|
||||
raise MozRegressionError(
|
||||
("Bad date %s is later than good date %s."
|
||||
(
|
||||
"Bad date %s is later than good date %s."
|
||||
" You should not use the --find-fix flag"
|
||||
" in this case...") % (options.bad, options.good))
|
||||
" in this case..."
|
||||
)
|
||||
% (options.bad, options.good)
|
||||
)
|
||||
if fetch_config.should_use_archive():
|
||||
self.action = "bisect_nightlies"
|
||||
options.preferences = preferences(options.prefs_files, options.prefs, self.logger)
|
||||
# convert GiB to bytes.
|
||||
options.persist_size_limit = \
|
||||
int(abs(float(options.persist_size_limit)) * 1073741824)
|
||||
options.persist_size_limit = int(abs(float(options.persist_size_limit)) * 1073741824)
|
||||
|
||||
|
||||
def cli(argv=None, conf_file=DEFAULT_CONF_FNAME, namespace=None):
|
||||
|
@ -541,12 +647,16 @@ def cli(argv=None, conf_file=DEFAULT_CONF_FNAME, namespace=None):
|
|||
# we don't set the cmdargs default to be that from the
|
||||
# configuration file, because then any new arguments
|
||||
# will be appended: https://bugs.python.org/issue16399
|
||||
options.cmdargs = defaults['cmdargs']
|
||||
options.cmdargs = defaults["cmdargs"]
|
||||
if conf_file and not os.path.isfile(conf_file):
|
||||
print('*' * 10)
|
||||
print(colorize("You should use a config file. Please use the " +
|
||||
'{sBRIGHT}--write-config{sRESET_ALL}' +
|
||||
" command line flag to help you create one."))
|
||||
print('*' * 10)
|
||||
print("*" * 10)
|
||||
print(
|
||||
colorize(
|
||||
"You should use a config file. Please use the "
|
||||
+ "{sBRIGHT}--write-config{sRESET_ALL}"
|
||||
+ " command line flag to help you create one."
|
||||
)
|
||||
)
|
||||
print("*" * 10)
|
||||
print()
|
||||
return Configuration(options)
|
||||
|
|
|
@ -6,32 +6,28 @@
|
|||
Reading and writing of the configuration file.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import mozinfo
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
from configobj import ConfigObj, ParseError
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from mozregression.log import colorize
|
||||
from mozregression.errors import MozRegressionError
|
||||
import mozinfo
|
||||
from configobj import ConfigObj, ParseError
|
||||
from six.moves import input
|
||||
|
||||
from mozregression.errors import MozRegressionError
|
||||
from mozregression.log import colorize
|
||||
|
||||
CONFIG_FILE_HELP_URL = (
|
||||
"http://mozilla.github.io/mozregression/documentation/configuration.html"
|
||||
)
|
||||
CONFIG_FILE_HELP_URL = "http://mozilla.github.io/mozregression/documentation/configuration.html"
|
||||
DEFAULT_CONF_FNAME = os.path.expanduser(
|
||||
os.path.join("~", ".mozilla", "mozregression", "mozregression.cfg")
|
||||
)
|
||||
TC_CREDENTIALS_FNAME = os.path.expanduser(
|
||||
os.path.join("~", ".mozilla", "mozregression",
|
||||
"taskcluster-credentials.json")
|
||||
os.path.join("~", ".mozilla", "mozregression", "taskcluster-credentials.json")
|
||||
)
|
||||
OLD_TC_ROOT_URL = "https://taskcluster.net"
|
||||
TC_ROOT_URL = "https://firefox-ci-tc.services.mozilla.com"
|
||||
TC_ROOT_URL_MIGRATION_FLAG_DATE = datetime.strptime('2019-11-09', '%Y-%M-%d')
|
||||
TC_ROOT_URL_MIGRATION_FLAG_DATE = datetime.strptime("2019-11-09", "%Y-%M-%d")
|
||||
ARCHIVE_BASE_URL = "https://archive.mozilla.org/pub"
|
||||
# when a bisection range needs to be expanded, the following value is used to
|
||||
# specify how many builds we try (if 20, we will try 20 before the lower limit,
|
||||
|
@ -41,25 +37,25 @@ DEFAULT_EXPAND = 20
|
|||
# default values when not defined in config file.
|
||||
# Note that this is also the list of options that can be used in config file
|
||||
DEFAULTS = {
|
||||
'adb-profile-dir': None,
|
||||
'app': 'firefox',
|
||||
'approx-policy': 'auto',
|
||||
'archive-base-url': ARCHIVE_BASE_URL,
|
||||
'background-dl-policy': 'cancel',
|
||||
'bits': None,
|
||||
'build-type': '',
|
||||
'cmdargs': [],
|
||||
'http-timeout': 30.0,
|
||||
'mode': 'classic',
|
||||
'no-background-dl': '',
|
||||
'persist': None,
|
||||
'persist-size-limit': 0,
|
||||
'process-output': None,
|
||||
'profile': None,
|
||||
'profile-persistence': 'clone',
|
||||
'repo': None,
|
||||
'taskcluster-accesstoken': None,
|
||||
'taskcluster-clientid': None,
|
||||
"adb-profile-dir": None,
|
||||
"app": "firefox",
|
||||
"approx-policy": "auto",
|
||||
"archive-base-url": ARCHIVE_BASE_URL,
|
||||
"background-dl-policy": "cancel",
|
||||
"bits": None,
|
||||
"build-type": "",
|
||||
"cmdargs": [],
|
||||
"http-timeout": 30.0,
|
||||
"mode": "classic",
|
||||
"no-background-dl": "",
|
||||
"persist": None,
|
||||
"persist-size-limit": 0,
|
||||
"process-output": None,
|
||||
"profile": None,
|
||||
"profile-persistence": "clone",
|
||||
"repo": None,
|
||||
"taskcluster-accesstoken": None,
|
||||
"taskcluster-clientid": None,
|
||||
}
|
||||
|
||||
|
||||
|
@ -71,23 +67,25 @@ def get_defaults(conf_path):
|
|||
try:
|
||||
config = ConfigObj(conf_path)
|
||||
except ParseError as exc:
|
||||
raise MozRegressionError(
|
||||
"Error while reading the config file %s:\n %s" % (conf_path, exc)
|
||||
)
|
||||
raise MozRegressionError("Error while reading the config file %s:\n %s" % (conf_path, exc))
|
||||
defaults.update(config)
|
||||
|
||||
return defaults
|
||||
|
||||
|
||||
def _get_persist_dir(default):
|
||||
print("You should configure a persist directory, where to put downloaded"
|
||||
" build files to reuse them in future bisections.")
|
||||
print("I recommend using %s. Leave blank to use that default. If you"
|
||||
print(
|
||||
"You should configure a persist directory, where to put downloaded"
|
||||
" build files to reuse them in future bisections."
|
||||
)
|
||||
print(
|
||||
"I recommend using %s. Leave blank to use that default. If you"
|
||||
" really don't want a persist dir type NONE, else you can"
|
||||
" just define a path that you would like to use." % default)
|
||||
" just define a path that you would like to use." % default
|
||||
)
|
||||
value = input("persist: ")
|
||||
if value == "NONE":
|
||||
return ''
|
||||
return ""
|
||||
elif value:
|
||||
persist_dir = os.path.realpath(value)
|
||||
else:
|
||||
|
@ -100,11 +98,13 @@ def _get_persist_dir(default):
|
|||
|
||||
|
||||
def _get_persist_size_limit(default):
|
||||
print("You should choose a size limit for the persist dir. I recommend you"
|
||||
print(
|
||||
"You should choose a size limit for the persist dir. I recommend you"
|
||||
" to use %s GiB, so leave it blank to use that default. Else you"
|
||||
" can type NONE to not limit the persist dir, or any number you like"
|
||||
" (a GiB value, so type 0.5 to allow ~500 MiB)." % default)
|
||||
value = input('persist-size-limit: ')
|
||||
" (a GiB value, so type 0.5 to allow ~500 MiB)." % default
|
||||
)
|
||||
value = input("persist-size-limit: ")
|
||||
if value == "NONE":
|
||||
return 0.0
|
||||
elif value:
|
||||
|
@ -113,12 +113,14 @@ def _get_persist_size_limit(default):
|
|||
|
||||
|
||||
def _get_bits(default):
|
||||
print("You are using a 64-bit system, so mozregression will by default"
|
||||
print(
|
||||
"You are using a 64-bit system, so mozregression will by default"
|
||||
" use the 64-bit build files. If you want to change that to"
|
||||
" 32-bit by default, type 32 here.")
|
||||
" 32-bit by default, type 32 here."
|
||||
)
|
||||
while 1:
|
||||
value = input('bits: ')
|
||||
if value in ('', '32', '64'):
|
||||
value = input("bits: ")
|
||||
if value in ("", "32", "64"):
|
||||
break
|
||||
if not value:
|
||||
return default
|
||||
|
@ -156,22 +158,23 @@ def write_conf(conf_path):
|
|||
else:
|
||||
value = default
|
||||
else:
|
||||
print('%s already defined.' % optname)
|
||||
print("%s already defined." % optname)
|
||||
value = config[optname]
|
||||
name = colorize("{fGREEN}%s{sRESET_ALL}" % optname)
|
||||
print("%s: %s" % (name, value))
|
||||
|
||||
_set_option('persist', _get_persist_dir,
|
||||
os.path.join(conf_dir, "persist"))
|
||||
_set_option("persist", _get_persist_dir, os.path.join(conf_dir, "persist"))
|
||||
|
||||
_set_option('persist-size-limit', _get_persist_size_limit, 20.0)
|
||||
_set_option("persist-size-limit", _get_persist_size_limit, 20.0)
|
||||
|
||||
if mozinfo.os != 'mac' and mozinfo.bits == 64:
|
||||
_set_option('bits', _get_bits, 64)
|
||||
if mozinfo.os != "mac" and mozinfo.bits == 64:
|
||||
_set_option("bits", _get_bits, 64)
|
||||
|
||||
config.write()
|
||||
|
||||
print()
|
||||
print(colorize('Config file {sBRIGHT}%s{sRESET_ALL} written.' % conf_path))
|
||||
print("Note you can edit it manually, and there are other options you can"
|
||||
" configure. See %s." % CONFIG_FILE_HELP_URL)
|
||||
print(colorize("Config file {sBRIGHT}%s{sRESET_ALL} written." % conf_path))
|
||||
print(
|
||||
"Note you can edit it manually, and there are other options you can"
|
||||
" configure. See %s." % CONFIG_FILE_HELP_URL
|
||||
)
|
||||
|
|
|
@ -3,9 +3,10 @@ Date utilities functions.
|
|||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import re
|
||||
import datetime
|
||||
|
||||
import calendar
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from mozregression.errors import DateFormatError
|
||||
|
||||
|
@ -20,13 +21,11 @@ def parse_date(date_string):
|
|||
return datetime.datetime.strptime(date_string, "%Y%m%d%H%M%S")
|
||||
except ValueError:
|
||||
raise DateFormatError(date_string, "Not a valid build id: `%s`")
|
||||
regex = re.compile(r'(\d{4})\-(\d{1,2})\-(\d{1,2})')
|
||||
regex = re.compile(r"(\d{4})\-(\d{1,2})\-(\d{1,2})")
|
||||
matched = regex.match(date_string)
|
||||
if not matched:
|
||||
raise DateFormatError(date_string)
|
||||
return datetime.date(int(matched.group(1)),
|
||||
int(matched.group(2)),
|
||||
int(matched.group(3)))
|
||||
return datetime.date(int(matched.group(1)), int(matched.group(2)), int(matched.group(3)))
|
||||
|
||||
|
||||
def to_datetime(date):
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
import tempfile
|
||||
import threading
|
||||
import requests
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import os
|
||||
import sys
|
||||
import mozfile
|
||||
|
||||
import tempfile
|
||||
import threading
|
||||
from contextlib import closing
|
||||
|
||||
import mozfile
|
||||
import requests
|
||||
import six
|
||||
from mozlog import get_proxy_logger
|
||||
|
||||
from mozregression.persist_limit import PersistLimit
|
||||
import six
|
||||
|
||||
LOG = get_proxy_logger('Download')
|
||||
LOG = get_proxy_logger("Download")
|
||||
|
||||
|
||||
class DownloadInterrupt(Exception):
|
||||
|
@ -45,11 +45,18 @@ class Download(object):
|
|||
:param progress: A callable to report the progress (default to None).
|
||||
see :meth:`set_progress`.
|
||||
"""
|
||||
def __init__(self, url, dest, finished_callback=None,
|
||||
chunk_size=16 * 1024, session=requests, progress=None):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
url,
|
||||
dest,
|
||||
finished_callback=None,
|
||||
chunk_size=16 * 1024,
|
||||
session=requests,
|
||||
progress=None,
|
||||
):
|
||||
self.thread = threading.Thread(
|
||||
target=self._download,
|
||||
args=(url, dest, finished_callback, chunk_size, session)
|
||||
target=self._download, args=(url, dest, finished_callback, chunk_size, session),
|
||||
)
|
||||
self._lock = threading.Lock()
|
||||
self.__url = url
|
||||
|
@ -156,16 +163,14 @@ class Download(object):
|
|||
bytes_so_far = 0
|
||||
try:
|
||||
with closing(session.get(url, stream=True)) as response:
|
||||
total_size = int(response.headers['Content-length'].strip())
|
||||
total_size = int(response.headers["Content-length"].strip())
|
||||
self._update_progress(bytes_so_far, total_size)
|
||||
# we use NamedTemporaryFile as raw open() call was causing
|
||||
# issues on windows - see:
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=1185756
|
||||
with tempfile.NamedTemporaryFile(
|
||||
delete=False,
|
||||
mode='wb',
|
||||
suffix='.tmp',
|
||||
dir=os.path.dirname(dest)) as temp:
|
||||
delete=False, mode="wb", suffix=".tmp", dir=os.path.dirname(dest)
|
||||
) as temp:
|
||||
for chunk in response.iter_content(chunk_size):
|
||||
if self.is_canceled():
|
||||
break
|
||||
|
@ -225,6 +230,7 @@ class DownloadManager(object):
|
|||
limiting the size of the download dir. Defaults
|
||||
to None, meaning no limit.
|
||||
"""
|
||||
|
||||
def __init__(self, destdir, session=requests, persist_limit=None):
|
||||
self.destdir = destdir
|
||||
self.session = session
|
||||
|
@ -283,10 +289,13 @@ class DownloadManager(object):
|
|||
# else create the download (will be automatically removed of
|
||||
# the list on completion) start it, and returns that.
|
||||
with self._lock:
|
||||
download = Download(url, dest,
|
||||
download = Download(
|
||||
url,
|
||||
dest,
|
||||
session=self.session,
|
||||
finished_callback=self._download_finished,
|
||||
progress=progress)
|
||||
progress=progress,
|
||||
)
|
||||
self._downloads[dest] = download
|
||||
download.start()
|
||||
self._download_started(download)
|
||||
|
@ -313,13 +322,13 @@ class BuildDownloadManager(DownloadManager):
|
|||
"""
|
||||
A DownloadManager specialized to download builds.
|
||||
"""
|
||||
def __init__(self, destdir, session=requests,
|
||||
background_dl_policy='cancel',
|
||||
persist_limit=None):
|
||||
DownloadManager.__init__(self, destdir, session=session,
|
||||
persist_limit=persist_limit)
|
||||
|
||||
def __init__(
|
||||
self, destdir, session=requests, background_dl_policy="cancel", persist_limit=None,
|
||||
):
|
||||
DownloadManager.__init__(self, destdir, session=session, persist_limit=persist_limit)
|
||||
self._downloads_bg = set()
|
||||
assert background_dl_policy in ('cancel', 'keep')
|
||||
assert background_dl_policy in ("cancel", "keep")
|
||||
self.background_dl_policy = background_dl_policy
|
||||
|
||||
def _extract_download_info(self, build_info):
|
||||
|
@ -360,7 +369,7 @@ class BuildDownloadManager(DownloadManager):
|
|||
build_info.build_file = dest
|
||||
# first, stop all downloads in background (except the one for this
|
||||
# build if any)
|
||||
if self.background_dl_policy == 'cancel':
|
||||
if self.background_dl_policy == "cancel":
|
||||
self.cancel(cancel_if=lambda dl: dest != dl.get_dest())
|
||||
|
||||
dl = self.download(build_url, fname, progress=download_progress)
|
||||
|
@ -369,7 +378,7 @@ class BuildDownloadManager(DownloadManager):
|
|||
try:
|
||||
dl.wait()
|
||||
finally:
|
||||
print('') # a new line after download_progress calls
|
||||
print("") # a new line after download_progress calls
|
||||
|
||||
else:
|
||||
msg = "Using local file: %s" % dest
|
||||
|
|
|
@ -15,16 +15,16 @@ class WinTooOldBuildError(MozRegressionError):
|
|||
"""
|
||||
Raised when a windows build is too old.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
MozRegressionError.__init__(self,
|
||||
"Can't run Windows builds before"
|
||||
" 2010-03-18")
|
||||
MozRegressionError.__init__(self, "Can't run Windows builds before" " 2010-03-18")
|
||||
|
||||
|
||||
class DateFormatError(MozRegressionError):
|
||||
"""
|
||||
Raised when a date can not be parsed from a string.
|
||||
"""
|
||||
|
||||
def __init__(self, date_string, format="Incorrect date format: `%s`"):
|
||||
MozRegressionError.__init__(self, format % date_string)
|
||||
|
||||
|
@ -46,10 +46,11 @@ class UnavailableRelease(MozRegressionError):
|
|||
"""
|
||||
Raised when firefox release is not available.
|
||||
"""
|
||||
|
||||
def __init__(self, release):
|
||||
MozRegressionError.__init__(self,
|
||||
"Unable to find a matching date for"
|
||||
" release %s" % release)
|
||||
MozRegressionError.__init__(
|
||||
self, "Unable to find a matching date for" " release %s" % release
|
||||
)
|
||||
|
||||
|
||||
class LauncherError(MozRegressionError):
|
||||
|
|
|
@ -7,27 +7,28 @@ The public API is composed of two classes, :class:`NightlyInfoFetcher` and
|
|||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import re
|
||||
import taskcluster
|
||||
from datetime import datetime
|
||||
from taskcluster.exceptions import TaskclusterFailure
|
||||
from mozlog import get_proxy_logger
|
||||
from threading import Thread, Lock
|
||||
from requests import HTTPError
|
||||
from threading import Lock, Thread
|
||||
|
||||
from mozregression.config import (OLD_TC_ROOT_URL, TC_ROOT_URL,
|
||||
TC_ROOT_URL_MIGRATION_FLAG_DATE)
|
||||
from mozregression.network import url_links, retry_get
|
||||
from mozregression.errors import BuildInfoNotFound, MozRegressionError
|
||||
from mozregression.build_info import NightlyBuildInfo, IntegrationBuildInfo
|
||||
from mozregression.json_pushes import JsonPushes, Push
|
||||
|
||||
LOG = get_proxy_logger(__name__)
|
||||
# Fix intermittent bug due to strptime first call not being thread safe
|
||||
# see https://bugzilla.mozilla.org/show_bug.cgi?id=1200270
|
||||
# and http://bugs.python.org/issue7980
|
||||
import _strptime # noqa
|
||||
import taskcluster
|
||||
from mozlog import get_proxy_logger
|
||||
from requests import HTTPError
|
||||
from taskcluster.exceptions import TaskclusterFailure
|
||||
|
||||
from mozregression.build_info import IntegrationBuildInfo, NightlyBuildInfo
|
||||
from mozregression.config import OLD_TC_ROOT_URL, TC_ROOT_URL, TC_ROOT_URL_MIGRATION_FLAG_DATE
|
||||
from mozregression.errors import BuildInfoNotFound, MozRegressionError
|
||||
from mozregression.json_pushes import JsonPushes, Push
|
||||
from mozregression.network import retry_get, url_links
|
||||
|
||||
LOG = get_proxy_logger(__name__)
|
||||
|
||||
|
||||
class InfoFetcher(object):
|
||||
|
@ -37,10 +38,8 @@ class InfoFetcher(object):
|
|||
self.build_info_regex = re.compile(fetch_config.build_info_regex())
|
||||
|
||||
def _update_build_info_from_txt(self, build_info):
|
||||
if 'build_txt_url' in build_info:
|
||||
build_info.update(
|
||||
self._fetch_txt_info(build_info['build_txt_url'])
|
||||
)
|
||||
if "build_txt_url" in build_info:
|
||||
build_info.update(self._fetch_txt_info(build_info["build_txt_url"]))
|
||||
|
||||
def _fetch_txt_info(self, url):
|
||||
"""
|
||||
|
@ -52,18 +51,18 @@ class InfoFetcher(object):
|
|||
data = {}
|
||||
response = retry_get(url)
|
||||
for line in response.text.splitlines():
|
||||
if '/rev/' in line:
|
||||
repository, changeset = line.split('/rev/')
|
||||
data['repository'] = repository
|
||||
data['changeset'] = changeset
|
||||
if "/rev/" in line:
|
||||
repository, changeset = line.split("/rev/")
|
||||
data["repository"] = repository
|
||||
data["changeset"] = changeset
|
||||
break
|
||||
if not data:
|
||||
# the txt file could be in an old format:
|
||||
# DATE CHANGESET
|
||||
# we can try to extract that to get the changeset at least.
|
||||
matched = re.match(r'^\d+ (\w+)$', response.text.strip())
|
||||
matched = re.match(r"^\d+ (\w+)$", response.text.strip())
|
||||
if matched:
|
||||
data['changeset'] = matched.group(1)
|
||||
data["changeset"] = matched.group(1)
|
||||
return data
|
||||
|
||||
def find_build_info(self, changeset_or_date, fetch_txt_info=True):
|
||||
|
@ -115,59 +114,59 @@ class IntegrationInfoFetcher(InfoFetcher):
|
|||
task_id = None
|
||||
status = None
|
||||
for tc_root_url in possible_tc_root_urls:
|
||||
LOG.debug('using taskcluster root url %s' % tc_root_url)
|
||||
LOG.debug("using taskcluster root url %s" % tc_root_url)
|
||||
options = self.fetch_config.tk_options(tc_root_url)
|
||||
tc_index = taskcluster.Index(options)
|
||||
tc_queue = taskcluster.Queue(options)
|
||||
tk_routes = self.fetch_config.tk_routes(push)
|
||||
stored_failure = None
|
||||
for tk_route in tk_routes:
|
||||
LOG.debug('using taskcluster route %r' % tk_route)
|
||||
LOG.debug("using taskcluster route %r" % tk_route)
|
||||
try:
|
||||
task_id = tc_index.findTask(tk_route)['taskId']
|
||||
task_id = tc_index.findTask(tk_route)["taskId"]
|
||||
except TaskclusterFailure as ex:
|
||||
LOG.debug('nothing found via route %r' % tk_route)
|
||||
LOG.debug("nothing found via route %r" % tk_route)
|
||||
stored_failure = ex
|
||||
continue
|
||||
if task_id:
|
||||
status = tc_queue.status(task_id)['status']
|
||||
status = tc_queue.status(task_id)["status"]
|
||||
break
|
||||
if status:
|
||||
break
|
||||
if not task_id:
|
||||
raise stored_failure
|
||||
except TaskclusterFailure:
|
||||
raise BuildInfoNotFound("Unable to find build info using the"
|
||||
" taskcluster route %r" %
|
||||
self.fetch_config.tk_route(push))
|
||||
raise BuildInfoNotFound(
|
||||
"Unable to find build info using the"
|
||||
" taskcluster route %r" % self.fetch_config.tk_route(push)
|
||||
)
|
||||
|
||||
# find a completed run for that task
|
||||
run_id, build_date = None, None
|
||||
for run in reversed(status['runs']):
|
||||
if run['state'] == 'completed':
|
||||
run_id = run['runId']
|
||||
build_date = datetime.strptime(run["resolved"],
|
||||
'%Y-%m-%dT%H:%M:%S.%fZ')
|
||||
for run in reversed(status["runs"]):
|
||||
if run["state"] == "completed":
|
||||
run_id = run["runId"]
|
||||
build_date = datetime.strptime(run["resolved"], "%Y-%m-%dT%H:%M:%S.%fZ")
|
||||
break
|
||||
|
||||
if run_id is None:
|
||||
raise BuildInfoNotFound("Unable to find completed runs for task %s"
|
||||
% task_id)
|
||||
artifacts = tc_queue.listArtifacts(task_id, run_id)['artifacts']
|
||||
raise BuildInfoNotFound("Unable to find completed runs for task %s" % task_id)
|
||||
artifacts = tc_queue.listArtifacts(task_id, run_id)["artifacts"]
|
||||
|
||||
# look over the artifacts of that run
|
||||
build_url = None
|
||||
for a in artifacts:
|
||||
name = os.path.basename(a['name'])
|
||||
name = os.path.basename(a["name"])
|
||||
if self.build_regex.search(name):
|
||||
meth = tc_queue.buildUrl
|
||||
if self.fetch_config.tk_needs_auth():
|
||||
meth = tc_queue.buildSignedUrl
|
||||
build_url = meth('getArtifact', task_id, run_id, a['name'])
|
||||
build_url = meth("getArtifact", task_id, run_id, a["name"])
|
||||
break
|
||||
if build_url is None:
|
||||
raise BuildInfoNotFound("unable to find a build url for the"
|
||||
" changeset %r" % changeset)
|
||||
raise BuildInfoNotFound(
|
||||
"unable to find a build url for the" " changeset %r" % changeset
|
||||
)
|
||||
return IntegrationBuildInfo(
|
||||
self.fetch_config,
|
||||
build_url=build_url,
|
||||
|
@ -194,21 +193,21 @@ class NightlyInfoFetcher(InfoFetcher):
|
|||
build info file are found for the url.
|
||||
"""
|
||||
data = {}
|
||||
if not url.endswith('/'):
|
||||
url += '/'
|
||||
if not url.endswith("/"):
|
||||
url += "/"
|
||||
for link in url_links(url):
|
||||
if 'build_url' not in data and self.build_regex.match(link):
|
||||
data['build_url'] = url + link
|
||||
elif 'build_txt_url' not in data \
|
||||
and self.build_info_regex.match(link):
|
||||
data['build_txt_url'] = url + link
|
||||
if "build_url" not in data and self.build_regex.match(link):
|
||||
data["build_url"] = url + link
|
||||
elif "build_txt_url" not in data and self.build_info_regex.match(link):
|
||||
data["build_txt_url"] = url + link
|
||||
if data:
|
||||
# Check that we found all required data. The URL in build_url is
|
||||
# required. build_txt_url is optional.
|
||||
if 'build_url' not in data:
|
||||
if "build_url" not in data:
|
||||
raise BuildInfoNotFound(
|
||||
"Failed to find a build file in directory {} that "
|
||||
"matches regex '{}'".format(url, self.build_regex.pattern))
|
||||
"matches regex '{}'".format(url, self.build_regex.pattern)
|
||||
)
|
||||
|
||||
with self._fetch_lock:
|
||||
lst.append((index, data))
|
||||
|
@ -262,9 +261,10 @@ class NightlyInfoFetcher(InfoFetcher):
|
|||
valid_builds = []
|
||||
while build_urls:
|
||||
some = build_urls[:max_workers]
|
||||
threads = [Thread(target=self._fetch_build_info_from_url,
|
||||
args=(url, i, valid_builds))
|
||||
for i, url in enumerate(some)]
|
||||
threads = [
|
||||
Thread(target=self._fetch_build_info_from_url, args=(url, i, valid_builds))
|
||||
for i, url in enumerate(some)
|
||||
]
|
||||
for thread in threads:
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
@ -279,10 +279,10 @@ class NightlyInfoFetcher(InfoFetcher):
|
|||
|
||||
build_info = NightlyBuildInfo(
|
||||
self.fetch_config,
|
||||
build_url=infos['build_url'],
|
||||
build_url=infos["build_url"],
|
||||
build_date=date,
|
||||
changeset=infos.get('changeset'),
|
||||
repo_url=infos.get('repository')
|
||||
changeset=infos.get("changeset"),
|
||||
repo_url=infos.get("repository"),
|
||||
)
|
||||
break
|
||||
build_urls = build_urls[max_workers:]
|
||||
|
|
|
@ -20,31 +20,28 @@ an instance of :class:`ClassRegistry`. Example: ::
|
|||
print REGISTRY.names()
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
import re
|
||||
import datetime
|
||||
|
||||
from mozregression.class_registry import ClassRegistry
|
||||
from mozregression import errors, branches
|
||||
from mozregression.dates import to_utc_timestamp
|
||||
from mozregression.config import ARCHIVE_BASE_URL
|
||||
import datetime
|
||||
import re
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import six
|
||||
|
||||
from mozregression import branches, errors
|
||||
from mozregression.class_registry import ClassRegistry
|
||||
from mozregression.config import ARCHIVE_BASE_URL
|
||||
from mozregression.dates import to_utc_timestamp
|
||||
|
||||
# switch from fennec api-11 to api-15 on taskcluster
|
||||
# appeared on this date for m-c.
|
||||
TIMESTAMP_FENNEC_API_15 = to_utc_timestamp(
|
||||
datetime.datetime(2016, 1, 29, 0, 30, 13)
|
||||
)
|
||||
TIMESTAMP_FENNEC_API_15 = to_utc_timestamp(datetime.datetime(2016, 1, 29, 0, 30, 13))
|
||||
|
||||
# switch from fennec api-15 to api-16 on taskcluster
|
||||
# appeared on this date for m-c.
|
||||
TIMESTAMP_FENNEC_API_16 = to_utc_timestamp(
|
||||
datetime.datetime(2017, 8, 29, 18, 28, 36)
|
||||
)
|
||||
TIMESTAMP_FENNEC_API_16 = to_utc_timestamp(datetime.datetime(2017, 8, 29, 18, 28, 36))
|
||||
|
||||
|
||||
def get_build_regex(name, os, bits, processor, psuffix='', with_ext=True):
|
||||
def get_build_regex(name, os, bits, processor, psuffix="", with_ext=True):
|
||||
"""
|
||||
Returns a string regexp that can match a build filename.
|
||||
|
||||
|
@ -75,15 +72,14 @@ def get_build_regex(name, os, bits, processor, psuffix='', with_ext=True):
|
|||
suffix, ext = r".*mac.*", r"\.dmg"
|
||||
else:
|
||||
raise errors.MozRegressionError(
|
||||
"mozregression supports linux, mac and windows but your"
|
||||
" os is reported as '%s'." % os
|
||||
"mozregression supports linux, mac and windows but your" " os is reported as '%s'." % os
|
||||
)
|
||||
|
||||
# New taskcluster builds now just name the binary archive 'target', so
|
||||
# that is added as one possibility in the regex.
|
||||
regex = '(target|%s%s%s)' % (name, suffix, psuffix)
|
||||
regex = "(target|%s%s%s)" % (name, suffix, psuffix)
|
||||
if with_ext:
|
||||
return '%s%s' % (regex, ext)
|
||||
return "%s%s" % (regex, ext)
|
||||
else:
|
||||
return regex
|
||||
|
||||
|
@ -92,7 +88,8 @@ class CommonConfig(object):
|
|||
"""
|
||||
Define the configuration for both nightly and integration fetching.
|
||||
"""
|
||||
BUILD_TYPES = ('opt',) # only opt allowed by default
|
||||
|
||||
BUILD_TYPES = ("opt",) # only opt allowed by default
|
||||
BUILD_TYPE_FALLBACKS = {}
|
||||
app_name = None
|
||||
|
||||
|
@ -101,7 +98,7 @@ class CommonConfig(object):
|
|||
self.bits = bits
|
||||
self.processor = processor
|
||||
self.repo = None
|
||||
self.set_build_type('opt')
|
||||
self.set_build_type("opt")
|
||||
self._used_build_index = 0
|
||||
|
||||
@property
|
||||
|
@ -119,23 +116,25 @@ class CommonConfig(object):
|
|||
"""
|
||||
self._used_build_index = (
|
||||
# Need to be careful not to overflow the list
|
||||
(self._used_build_index + 1) % len(self.build_types)
|
||||
(self._used_build_index + 1)
|
||||
% len(self.build_types)
|
||||
)
|
||||
|
||||
def build_regex(self):
|
||||
"""
|
||||
Returns a string regex that can match a build file on the servers.
|
||||
"""
|
||||
return get_build_regex(self.app_name, self.os, self.bits,
|
||||
self.processor) + '$'
|
||||
return get_build_regex(self.app_name, self.os, self.bits, self.processor) + "$"
|
||||
|
||||
def build_info_regex(self):
|
||||
"""
|
||||
Returns a string regex that can match a build info file (txt)
|
||||
on the servers.
|
||||
"""
|
||||
return get_build_regex(self.app_name, self.os, self.bits,
|
||||
self.processor, with_ext=False) + r'\.txt$'
|
||||
return (
|
||||
get_build_regex(self.app_name, self.os, self.bits, self.processor, with_ext=False)
|
||||
+ r"\.txt$"
|
||||
)
|
||||
|
||||
def available_bits(self):
|
||||
"""
|
||||
|
@ -149,13 +148,10 @@ class CommonConfig(object):
|
|||
for available in self.BUILD_TYPES:
|
||||
match = re.match(r"(.+)\[(.+)\]", available)
|
||||
if match:
|
||||
suffix = ('-aarch64' if self.processor == 'aarch64' and
|
||||
self.bits == 64 else '')
|
||||
suffix = "-aarch64" if self.processor == "aarch64" and self.bits == 64 else ""
|
||||
available = match.group(1)
|
||||
platforms = match.group(2)
|
||||
if '{}{}{}'.format(
|
||||
self.os, self.bits, suffix
|
||||
) not in platforms.split(','):
|
||||
if "{}{}{}".format(self.os, self.bits, suffix) not in platforms.split(","):
|
||||
available = None
|
||||
if available:
|
||||
res.append(available)
|
||||
|
@ -169,9 +165,7 @@ class CommonConfig(object):
|
|||
"""
|
||||
if build_type in self.available_build_types():
|
||||
fallbacks = self.BUILD_TYPE_FALLBACKS.get(build_type)
|
||||
self.build_types = (
|
||||
(build_type,) + fallbacks if fallbacks else (build_type,)
|
||||
)
|
||||
self.build_types = (build_type,) + fallbacks if fallbacks else (build_type,)
|
||||
return
|
||||
raise errors.MozRegressionError(
|
||||
"Unable to find a suitable build type %r." % str(build_type)
|
||||
|
@ -194,11 +188,11 @@ class CommonConfig(object):
|
|||
|
||||
Note that this method relies on the repo and build type defined.
|
||||
"""
|
||||
return not (branches.get_category(self.repo) in
|
||||
('integration', 'try', 'releases') or
|
||||
# we can find the asan builds (firefox and jsshell) in
|
||||
# archives.m.o
|
||||
self.build_type not in ('opt', 'asan', 'shippable'))
|
||||
# we can find the asan builds (firefox and jsshell) in archives.m.o
|
||||
return not (
|
||||
branches.get_category(self.repo) in ("integration", "try", "releases")
|
||||
or self.build_type not in ("opt", "asan", "shippable")
|
||||
)
|
||||
|
||||
|
||||
class NightlyConfigMixin(six.with_metaclass(ABCMeta)):
|
||||
|
@ -216,21 +210,24 @@ class NightlyConfigMixin(six.with_metaclass(ABCMeta)):
|
|||
Note that subclasses must implement :meth:`_get_nightly_repo` to
|
||||
provide a default value.
|
||||
"""
|
||||
|
||||
archive_base_url = ARCHIVE_BASE_URL
|
||||
nightly_base_repo_name = "firefox"
|
||||
nightly_repo = None
|
||||
|
||||
def set_base_url(self, url):
|
||||
self.archive_base_url = url.rstrip('/')
|
||||
self.archive_base_url = url.rstrip("/")
|
||||
|
||||
def get_nighly_base_url(self, date):
|
||||
"""
|
||||
Returns the base part of the nightly build url for a given date.
|
||||
"""
|
||||
return "%s/%s/nightly/%04d/%02d/" % (self.archive_base_url,
|
||||
return "%s/%s/nightly/%04d/%02d/" % (
|
||||
self.archive_base_url,
|
||||
self.nightly_base_repo_name,
|
||||
date.year,
|
||||
date.month)
|
||||
date.month,
|
||||
)
|
||||
|
||||
def get_nightly_repo(self, date):
|
||||
"""
|
||||
|
@ -256,11 +253,16 @@ class NightlyConfigMixin(six.with_metaclass(ABCMeta)):
|
|||
|
||||
def _get_nightly_repo_regex(self, date, repo):
|
||||
if isinstance(date, datetime.datetime):
|
||||
return (r'^%04d-%02d-%02d-%02d-%02d-%02d-%s/$'
|
||||
% (date.year, date.month, date.day, date.hour,
|
||||
date.minute, date.second, repo))
|
||||
return (r'^%04d-%02d-%02d-[\d-]+%s/$'
|
||||
% (date.year, date.month, date.day, repo))
|
||||
return r"^%04d-%02d-%02d-%02d-%02d-%02d-%s/$" % (
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
date.hour,
|
||||
date.minute,
|
||||
date.second,
|
||||
repo,
|
||||
)
|
||||
return r"^%04d-%02d-%02d-[\d-]+%s/$" % (date.year, date.month, date.day, repo)
|
||||
|
||||
|
||||
class FirefoxNightlyConfigMixin(NightlyConfigMixin):
|
||||
|
@ -272,7 +274,7 @@ class FirefoxNightlyConfigMixin(NightlyConfigMixin):
|
|||
|
||||
|
||||
class ThunderbirdNightlyConfigMixin(NightlyConfigMixin):
|
||||
nightly_base_repo_name = 'thunderbird'
|
||||
nightly_base_repo_name = "thunderbird"
|
||||
|
||||
def _get_nightly_repo(self, date):
|
||||
# sneaking this in here
|
||||
|
@ -294,11 +296,11 @@ class FennecNightlyConfigMixin(NightlyConfigMixin):
|
|||
nightly_base_repo_name = "mobile"
|
||||
|
||||
def _get_nightly_repo(self, date):
|
||||
return 'mozilla-central'
|
||||
return "mozilla-central"
|
||||
|
||||
def get_nightly_repo_regex(self, date):
|
||||
repo = self.get_nightly_repo(date)
|
||||
if repo in ('mozilla-central',):
|
||||
if repo in ("mozilla-central",):
|
||||
if date < datetime.date(2014, 12, 6):
|
||||
repo += "-android"
|
||||
elif date < datetime.date(2014, 12, 13):
|
||||
|
@ -316,7 +318,8 @@ class IntegrationConfigMixin(six.with_metaclass(ABCMeta)):
|
|||
"""
|
||||
Define the integration-related required configuration.
|
||||
"""
|
||||
default_integration_branch = 'mozilla-central'
|
||||
|
||||
default_integration_branch = "mozilla-central"
|
||||
_tk_credentials = None
|
||||
|
||||
@property
|
||||
|
@ -342,7 +345,7 @@ class IntegrationConfigMixin(six.with_metaclass(ABCMeta)):
|
|||
builds. Returns an empty string by default, or 'debug' if build type
|
||||
is debug.
|
||||
"""
|
||||
return self.build_type if self.build_type != 'opt' else ''
|
||||
return self.build_type if self.build_type != "opt" else ""
|
||||
|
||||
def tk_needs_auth(self):
|
||||
"""
|
||||
|
@ -362,77 +365,76 @@ class IntegrationConfigMixin(six.with_metaclass(ABCMeta)):
|
|||
Returns the takcluster options, including the credentials required to
|
||||
download private artifacts.
|
||||
"""
|
||||
tk_options = {'rootUrl': root_url}
|
||||
tk_options = {"rootUrl": root_url}
|
||||
if self.tk_needs_auth():
|
||||
tk_options.update({'credentials': self._tk_credentials})
|
||||
tk_options.update({"credentials": self._tk_credentials})
|
||||
return tk_options
|
||||
|
||||
|
||||
def _common_tk_part(integration_conf):
|
||||
# private method to avoid copy/paste for building taskcluster route part.
|
||||
if integration_conf.os == 'linux':
|
||||
part = 'linux'
|
||||
if integration_conf.os == "linux":
|
||||
part = "linux"
|
||||
if integration_conf.bits == 64:
|
||||
part += str(integration_conf.bits)
|
||||
elif integration_conf.os == 'mac':
|
||||
part = 'macosx64'
|
||||
elif integration_conf.os == "mac":
|
||||
part = "macosx64"
|
||||
else:
|
||||
# windows
|
||||
part = '{}{}'.format(integration_conf.os, integration_conf.bits)
|
||||
if integration_conf.processor == 'aarch64' and integration_conf.bits == 64:
|
||||
part += '-aarch64'
|
||||
part = "{}{}".format(integration_conf.os, integration_conf.bits)
|
||||
if integration_conf.processor == "aarch64" and integration_conf.bits == 64:
|
||||
part += "-aarch64"
|
||||
return part
|
||||
|
||||
|
||||
class FirefoxIntegrationConfigMixin(IntegrationConfigMixin):
|
||||
def tk_routes(self, push):
|
||||
for build_type in self.build_types:
|
||||
yield 'gecko.v2.{}{}.revision.{}.firefox.{}-{}'.format(
|
||||
yield "gecko.v2.{}{}.revision.{}.firefox.{}-{}".format(
|
||||
self.integration_branch,
|
||||
'.shippable' if build_type == 'shippable' else '',
|
||||
".shippable" if build_type == "shippable" else "",
|
||||
push.changeset,
|
||||
_common_tk_part(self),
|
||||
'opt' if build_type == 'shippable' else build_type
|
||||
"opt" if build_type == "shippable" else build_type,
|
||||
)
|
||||
self._inc_used_build()
|
||||
return
|
||||
|
||||
|
||||
class FennecIntegrationConfigMixin(IntegrationConfigMixin):
|
||||
tk_name = 'android-api-11'
|
||||
tk_name = "android-api-11"
|
||||
|
||||
def tk_routes(self, push):
|
||||
tk_name = self.tk_name
|
||||
if tk_name == 'android-api-11':
|
||||
if tk_name == "android-api-11":
|
||||
if push.timestamp >= TIMESTAMP_FENNEC_API_16:
|
||||
tk_name = 'android-api-16'
|
||||
tk_name = "android-api-16"
|
||||
elif push.timestamp >= TIMESTAMP_FENNEC_API_15:
|
||||
tk_name = 'android-api-15'
|
||||
tk_name = "android-api-15"
|
||||
for build_type in self.build_types:
|
||||
yield 'gecko.v2.{}.revision.{}.mobile.{}-{}'.format(
|
||||
self.integration_branch, push.changeset, tk_name,
|
||||
build_type
|
||||
yield "gecko.v2.{}.revision.{}.mobile.{}-{}".format(
|
||||
self.integration_branch, push.changeset, tk_name, build_type
|
||||
)
|
||||
self._inc_used_build()
|
||||
return
|
||||
|
||||
|
||||
class ThunderbirdIntegrationConfigMixin(IntegrationConfigMixin):
|
||||
default_integration_branch = 'comm-central'
|
||||
default_integration_branch = "comm-central"
|
||||
|
||||
def tk_routes(self, push):
|
||||
for build_type in self.build_types:
|
||||
yield 'comm.v2.{}.revision.{}.thunderbird.{}-{}'.format(
|
||||
self.integration_branch, push.changeset, _common_tk_part(self),
|
||||
build_type
|
||||
yield "comm.v2.{}.revision.{}.thunderbird.{}-{}".format(
|
||||
self.integration_branch, push.changeset, _common_tk_part(self), build_type,
|
||||
)
|
||||
self._inc_used_build()
|
||||
return
|
||||
|
||||
|
||||
# ------------ full config implementations ------------
|
||||
|
||||
|
||||
REGISTRY = ClassRegistry('app_name')
|
||||
REGISTRY = ClassRegistry("app_name")
|
||||
|
||||
|
||||
def create_config(name, os, bits, processor):
|
||||
|
@ -449,62 +451,68 @@ def create_config(name, os, bits, processor):
|
|||
return REGISTRY.get(name)(os, bits, processor)
|
||||
|
||||
|
||||
@REGISTRY.register('firefox')
|
||||
class FirefoxConfig(CommonConfig,
|
||||
FirefoxNightlyConfigMixin,
|
||||
FirefoxIntegrationConfigMixin):
|
||||
BUILD_TYPES = ('shippable', 'opt', 'pgo[linux32,linux64,win32,win64]',
|
||||
'debug', 'asan[linux64]', 'asan-debug[linux64]')
|
||||
@REGISTRY.register("firefox")
|
||||
class FirefoxConfig(CommonConfig, FirefoxNightlyConfigMixin, FirefoxIntegrationConfigMixin):
|
||||
BUILD_TYPES = (
|
||||
"shippable",
|
||||
"opt",
|
||||
"pgo[linux32,linux64,win32,win64]",
|
||||
"debug",
|
||||
"asan[linux64]",
|
||||
"asan-debug[linux64]",
|
||||
)
|
||||
BUILD_TYPE_FALLBACKS = {
|
||||
'shippable': ('opt', 'pgo'),
|
||||
'opt': ('shippable', 'pgo'),
|
||||
"shippable": ("opt", "pgo"),
|
||||
"opt": ("shippable", "pgo"),
|
||||
}
|
||||
|
||||
def __init__(self, os, bits, processor):
|
||||
super(FirefoxConfig, self).__init__(os, bits, processor)
|
||||
self.set_build_type('shippable')
|
||||
self.set_build_type("shippable")
|
||||
|
||||
def build_regex(self):
|
||||
return get_build_regex(
|
||||
self.app_name, self.os, self.bits, self.processor,
|
||||
psuffix='-asan-reporter' if 'asan' in self.build_type else ''
|
||||
) + '$'
|
||||
return (
|
||||
get_build_regex(
|
||||
self.app_name,
|
||||
self.os,
|
||||
self.bits,
|
||||
self.processor,
|
||||
psuffix="-asan-reporter" if "asan" in self.build_type else "",
|
||||
)
|
||||
+ "$"
|
||||
)
|
||||
|
||||
|
||||
@REGISTRY.register('thunderbird')
|
||||
class ThunderbirdConfig(CommonConfig,
|
||||
ThunderbirdNightlyConfigMixin,
|
||||
ThunderbirdIntegrationConfigMixin):
|
||||
@REGISTRY.register("thunderbird")
|
||||
class ThunderbirdConfig(
|
||||
CommonConfig, ThunderbirdNightlyConfigMixin, ThunderbirdIntegrationConfigMixin
|
||||
):
|
||||
pass
|
||||
|
||||
|
||||
@REGISTRY.register('fennec')
|
||||
class FennecConfig(CommonConfig,
|
||||
FennecNightlyConfigMixin,
|
||||
FennecIntegrationConfigMixin):
|
||||
BUILD_TYPES = ('opt', 'debug')
|
||||
@REGISTRY.register("fennec")
|
||||
class FennecConfig(CommonConfig, FennecNightlyConfigMixin, FennecIntegrationConfigMixin):
|
||||
BUILD_TYPES = ("opt", "debug")
|
||||
|
||||
def build_regex(self):
|
||||
return r'(target|fennec-.*)\.apk'
|
||||
return r"(target|fennec-.*)\.apk"
|
||||
|
||||
def build_info_regex(self):
|
||||
return r'(target|fennec-.*)\.txt'
|
||||
return r"(target|fennec-.*)\.txt"
|
||||
|
||||
def available_bits(self):
|
||||
return ()
|
||||
|
||||
|
||||
@REGISTRY.register('gve')
|
||||
class GeckoViewExampleConfig(CommonConfig,
|
||||
FennecNightlyConfigMixin,
|
||||
FennecIntegrationConfigMixin):
|
||||
BUILD_TYPES = ('opt', 'debug')
|
||||
@REGISTRY.register("gve")
|
||||
class GeckoViewExampleConfig(CommonConfig, FennecNightlyConfigMixin, FennecIntegrationConfigMixin):
|
||||
BUILD_TYPES = ("opt", "debug")
|
||||
|
||||
def build_regex(self):
|
||||
return r'geckoview_example\.apk'
|
||||
return r"geckoview_example\.apk"
|
||||
|
||||
def build_info_regex(self):
|
||||
return r'(target|fennec-.*)\.txt'
|
||||
return r"(target|fennec-.*)\.txt"
|
||||
|
||||
def available_bits(self):
|
||||
return ()
|
||||
|
@ -514,13 +522,13 @@ class GeckoViewExampleConfig(CommonConfig,
|
|||
return False
|
||||
|
||||
|
||||
@REGISTRY.register('fennec-2.3', attr_value='fennec')
|
||||
@REGISTRY.register("fennec-2.3", attr_value="fennec")
|
||||
class Fennec23Config(FennecConfig):
|
||||
tk_name = 'android-api-9'
|
||||
tk_name = "android-api-9"
|
||||
|
||||
def get_nightly_repo_regex(self, date):
|
||||
repo = self.get_nightly_repo(date)
|
||||
if repo == 'mozilla-central':
|
||||
if repo == "mozilla-central":
|
||||
if date < datetime.date(2014, 12, 6):
|
||||
repo = "mozilla-central-android"
|
||||
else:
|
||||
|
@ -528,28 +536,30 @@ class Fennec23Config(FennecConfig):
|
|||
return self._get_nightly_repo_regex(date, repo)
|
||||
|
||||
|
||||
@REGISTRY.register('jsshell', disable_in_gui=True)
|
||||
@REGISTRY.register("jsshell", disable_in_gui=True)
|
||||
class JsShellConfig(FirefoxConfig):
|
||||
def build_info_regex(self):
|
||||
# the info file is the one for firefox
|
||||
return get_build_regex('firefox', self.os, self.bits, self.processor,
|
||||
with_ext=False) + r'\.txt$'
|
||||
return (
|
||||
get_build_regex("firefox", self.os, self.bits, self.processor, with_ext=False)
|
||||
+ r"\.txt$"
|
||||
)
|
||||
|
||||
def build_regex(self):
|
||||
if self.os == 'linux':
|
||||
if self.os == "linux":
|
||||
if self.bits == 64:
|
||||
part = 'linux-x86_64'
|
||||
part = "linux-x86_64"
|
||||
else:
|
||||
part = 'linux-i686'
|
||||
elif self.os == 'win':
|
||||
part = "linux-i686"
|
||||
elif self.os == "win":
|
||||
if self.bits == 64:
|
||||
if self.processor == "aarch64":
|
||||
part = 'win64-aarch64'
|
||||
part = "win64-aarch64"
|
||||
else:
|
||||
part = 'win64(-x86_64)?'
|
||||
part = "win64(-x86_64)?"
|
||||
else:
|
||||
part = 'win32'
|
||||
part = "win32"
|
||||
else:
|
||||
part = 'mac'
|
||||
psuffix = '-asan' if 'asan' in self.build_type else ''
|
||||
return r'jsshell-%s%s\.zip$' % (part, psuffix)
|
||||
part = "mac"
|
||||
psuffix = "-asan" if "asan" in self.build_type else ""
|
||||
return r"jsshell-%s%s\.zip$" % (part, psuffix)
|
||||
|
|
|
@ -3,10 +3,10 @@ Representation of the bisection history.
|
|||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
BisectionStep = namedtuple('BisectionStep', 'build_range, index, verdict')
|
||||
BisectionStep = namedtuple("BisectionStep", "build_range, index, verdict")
|
||||
|
||||
|
||||
class BisectionHistory(list):
|
||||
|
@ -20,5 +20,6 @@ class BisectionHistory(list):
|
|||
since it only store steps for one handler - e.g only for
|
||||
one branch.
|
||||
"""
|
||||
|
||||
def add(self, build_range, index, verdict):
|
||||
self.append(BisectionStep(build_range, index, verdict))
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import datetime
|
||||
|
||||
import six
|
||||
from mozlog import get_proxy_logger
|
||||
|
||||
from mozregression.errors import EmptyPushlogError
|
||||
from mozregression.network import retry_get
|
||||
from mozregression import branches
|
||||
from mozregression.dates import is_date_or_datetime
|
||||
import six
|
||||
from mozregression.errors import EmptyPushlogError
|
||||
from mozregression.network import retry_get
|
||||
|
||||
LOG = get_proxy_logger("JsonPushes")
|
||||
|
||||
|
@ -16,7 +17,8 @@ class Push(object):
|
|||
"""
|
||||
Simple wrapper around a json push object from json-pushes API.
|
||||
"""
|
||||
__slots__ = ('_data', '_push_id') # to save memory usage
|
||||
|
||||
__slots__ = ("_data", "_push_id") # to save memory usage
|
||||
|
||||
def __init__(self, push_id, data):
|
||||
self._data = data
|
||||
|
@ -28,18 +30,18 @@ class Push(object):
|
|||
|
||||
@property
|
||||
def changesets(self):
|
||||
return self._data['changesets']
|
||||
return self._data["changesets"]
|
||||
|
||||
@property
|
||||
def changeset(self):
|
||||
"""
|
||||
Returns the last changeset in the push (the most interesting for us)
|
||||
"""
|
||||
return self._data['changesets'][-1]
|
||||
return self._data["changesets"][-1]
|
||||
|
||||
@property
|
||||
def timestamp(self):
|
||||
return self._data['date']
|
||||
return self._data["date"]
|
||||
|
||||
@property
|
||||
def utc_date(self):
|
||||
|
@ -53,7 +55,8 @@ class JsonPushes(object):
|
|||
"""
|
||||
Find pushlog Push objects from a mozilla hg json-pushes api.
|
||||
"""
|
||||
def __init__(self, branch='mozilla-central'):
|
||||
|
||||
def __init__(self, branch="mozilla-central"):
|
||||
self.branch = branch
|
||||
self.repo_url = branches.get_url(branch)
|
||||
|
||||
|
@ -63,15 +66,19 @@ class JsonPushes(object):
|
|||
|
||||
Basically issue a raw request to the server.
|
||||
"""
|
||||
base_url = '%s/json-pushes?' % self.repo_url
|
||||
url = base_url + '&'.join(sorted("%s=%s" % kv for kv in six.iteritems(kwargs)))
|
||||
base_url = "%s/json-pushes?" % self.repo_url
|
||||
url = base_url + "&".join(sorted("%s=%s" % kv for kv in six.iteritems(kwargs)))
|
||||
LOG.debug("Using url: %s" % url)
|
||||
|
||||
response = retry_get(url)
|
||||
data = response.json()
|
||||
|
||||
if (response.status_code == 404 and data is not None and
|
||||
"error" in data and "unknown revision" in data["error"]):
|
||||
if (
|
||||
response.status_code == 404
|
||||
and data is not None
|
||||
and "error" in data
|
||||
and "unknown revision" in data["error"]
|
||||
):
|
||||
raise EmptyPushlogError(
|
||||
"The url %r returned a 404 error because the push is not"
|
||||
" in this repo (e.g., not merged yet)." % url
|
||||
|
@ -80,8 +87,7 @@ class JsonPushes(object):
|
|||
|
||||
if not data:
|
||||
raise EmptyPushlogError(
|
||||
"The url %r contains no pushlog. Maybe use another range ?"
|
||||
% url
|
||||
"The url %r contains no pushlog. Maybe use another range ?" % url
|
||||
)
|
||||
|
||||
pushlog = []
|
||||
|
@ -89,8 +95,7 @@ class JsonPushes(object):
|
|||
pushlog.append(Push(key, data[key]))
|
||||
return pushlog
|
||||
|
||||
def pushes_within_changes(self, fromchange, tochange, verbose=True,
|
||||
**kwargs):
|
||||
def pushes_within_changes(self, fromchange, tochange, verbose=True, **kwargs):
|
||||
"""
|
||||
Returns a list of Push objects, including fromchange and tochange.
|
||||
|
||||
|
@ -105,17 +110,16 @@ class JsonPushes(object):
|
|||
# the first changeset is not taken into account in the result.
|
||||
# let's add it directly with this request
|
||||
chsets = self.pushes(changeset=fromchange)
|
||||
kwargs['fromchange'] = fromchange
|
||||
kwargs["fromchange"] = fromchange
|
||||
else:
|
||||
chsets = []
|
||||
kwargs['startdate'] = fromchange.strftime('%Y-%m-%d')
|
||||
kwargs["startdate"] = fromchange.strftime("%Y-%m-%d")
|
||||
|
||||
if not to_is_date:
|
||||
kwargs['tochange'] = tochange
|
||||
kwargs["tochange"] = tochange
|
||||
else:
|
||||
# add one day to take the last day in account
|
||||
kwargs['enddate'] = (
|
||||
tochange + datetime.timedelta(days=1)).strftime('%Y-%m-%d')
|
||||
kwargs["enddate"] = (tochange + datetime.timedelta(days=1)).strftime("%Y-%m-%d")
|
||||
|
||||
# now fetch all remaining changesets
|
||||
chsets.extend(self.pushes(**kwargs))
|
||||
|
@ -123,12 +127,18 @@ class JsonPushes(object):
|
|||
log = LOG.info if verbose else LOG.debug
|
||||
if from_is_date:
|
||||
first = chsets[0]
|
||||
log("Using {} (pushed on {}) for date {}".format(
|
||||
first.changeset, first.utc_date, fromchange))
|
||||
log(
|
||||
"Using {} (pushed on {}) for date {}".format(
|
||||
first.changeset, first.utc_date, fromchange
|
||||
)
|
||||
)
|
||||
if to_is_date:
|
||||
last = chsets[-1]
|
||||
log("Using {} (pushed on {}) for date {}".format(
|
||||
last.changeset, last.utc_date, tochange))
|
||||
log(
|
||||
"Using {} (pushed on {}) for date {}".format(
|
||||
last.changeset, last.utc_date, tochange
|
||||
)
|
||||
)
|
||||
|
||||
return chsets
|
||||
|
||||
|
@ -140,12 +150,9 @@ class JsonPushes(object):
|
|||
"""
|
||||
if is_date_or_datetime(changeset):
|
||||
try:
|
||||
return self.pushes_within_changes(changeset,
|
||||
changeset,
|
||||
verbose=False)[-1]
|
||||
return self.pushes_within_changes(changeset, changeset, verbose=False)[-1]
|
||||
except EmptyPushlogError:
|
||||
raise EmptyPushlogError(
|
||||
"No pushes available for the date %s on %s."
|
||||
% (changeset, self.branch)
|
||||
"No pushes available for the date %s on %s." % (changeset, self.branch)
|
||||
)
|
||||
return self.pushes(changeset=changeset, **kwargs)[0]
|
||||
|
|
|
@ -6,29 +6,30 @@
|
|||
Define the launcher classes, responsible of running the tested applications.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from mozlog.structured import get_default_logger, get_proxy_logger
|
||||
from mozprofile import ThunderbirdProfile, Profile
|
||||
from mozrunner import Runner
|
||||
from mozfile import remove
|
||||
from mozdevice import ADBAndroid, ADBHost, ADBError
|
||||
import mozversion
|
||||
import mozinstall
|
||||
import zipfile
|
||||
import mozinfo
|
||||
import six
|
||||
import sys
|
||||
import stat
|
||||
from subprocess import call
|
||||
import sys
|
||||
import time
|
||||
import zipfile
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from subprocess import call
|
||||
from threading import Thread
|
||||
|
||||
import mozinfo
|
||||
import mozinstall
|
||||
import mozversion
|
||||
import six
|
||||
from mozdevice import ADBAndroid, ADBError, ADBHost
|
||||
from mozfile import remove
|
||||
from mozlog.structured import get_default_logger, get_proxy_logger
|
||||
from mozprofile import Profile, ThunderbirdProfile
|
||||
from mozrunner import Runner
|
||||
|
||||
from mozregression.class_registry import ClassRegistry
|
||||
from mozregression.errors import LauncherNotRunnable, LauncherError
|
||||
from mozregression.errors import LauncherError, LauncherNotRunnable
|
||||
from mozregression.tempdir import safe_mkdtemp
|
||||
|
||||
LOG = get_proxy_logger("Test Runner")
|
||||
|
@ -39,6 +40,7 @@ class Launcher(six.with_metaclass(ABCMeta)):
|
|||
Handle the logic of downloading a build file, installing and
|
||||
running an application.
|
||||
"""
|
||||
|
||||
profile_class = Profile
|
||||
|
||||
@classmethod
|
||||
|
@ -134,18 +136,13 @@ class Launcher(six.with_metaclass(ABCMeta)):
|
|||
if isinstance(profile, Profile):
|
||||
return profile
|
||||
else:
|
||||
return self.create_profile(profile=profile, addons=addons,
|
||||
preferences=preferences)
|
||||
return self.create_profile(profile=profile, addons=addons, preferences=preferences)
|
||||
|
||||
@classmethod
|
||||
def create_profile(cls, profile=None, addons=(), preferences=None,
|
||||
clone=True):
|
||||
def create_profile(cls, profile=None, addons=(), preferences=None, clone=True):
|
||||
if profile:
|
||||
if not os.path.exists(profile):
|
||||
LOG.warning(
|
||||
"Creating directory '%s' to put the profile in there"
|
||||
% profile
|
||||
)
|
||||
LOG.warning("Creating directory '%s' to put the profile in there" % profile)
|
||||
os.makedirs(profile)
|
||||
# since the user gave an empty dir for the profile,
|
||||
# let's keep it on the disk in any case.
|
||||
|
@ -155,14 +152,11 @@ class Launcher(six.with_metaclass(ABCMeta)):
|
|||
# be undone. Let's clone the profile to not have side effect
|
||||
# on existing profile.
|
||||
# see https://bugzilla.mozilla.org/show_bug.cgi?id=999009
|
||||
profile = cls.profile_class.clone(profile, addons=addons,
|
||||
preferences=preferences)
|
||||
profile = cls.profile_class.clone(profile, addons=addons, preferences=preferences)
|
||||
else:
|
||||
profile = cls.profile_class(profile, addons=addons,
|
||||
preferences=preferences)
|
||||
profile = cls.profile_class(profile, addons=addons, preferences=preferences)
|
||||
elif len(addons):
|
||||
profile = cls.profile_class(addons=addons,
|
||||
preferences=preferences)
|
||||
profile = cls.profile_class(addons=addons, preferences=preferences)
|
||||
else:
|
||||
profile = cls.profile_class(preferences=preferences)
|
||||
return profile
|
||||
|
@ -181,52 +175,41 @@ def safe_get_version(**kwargs):
|
|||
class MozRunnerLauncher(Launcher):
|
||||
tempdir = None
|
||||
runner = None
|
||||
app_name = 'undefined'
|
||||
app_name = "undefined"
|
||||
binary = None
|
||||
|
||||
def _install(self, dest):
|
||||
self.tempdir = safe_mkdtemp()
|
||||
try:
|
||||
self.binary = mozinstall.get_binary(
|
||||
mozinstall.install(src=dest, dest=self.tempdir),
|
||||
self.app_name
|
||||
mozinstall.install(src=dest, dest=self.tempdir), self.app_name
|
||||
)
|
||||
except Exception:
|
||||
remove(self.tempdir)
|
||||
raise
|
||||
|
||||
def _disableUpdateByPolicy(self):
|
||||
updatePolicy = {
|
||||
'policies': {
|
||||
'DisableAppUpdate': True
|
||||
}
|
||||
}
|
||||
updatePolicy = {"policies": {"DisableAppUpdate": True}}
|
||||
installdir = os.path.dirname(self.binary)
|
||||
if mozinfo.os == 'mac':
|
||||
if mozinfo.os == "mac":
|
||||
# macOS has the following filestructure:
|
||||
# binary at:
|
||||
# PackageName.app/Contents/MacOS/firefox
|
||||
# we need policies.json in:
|
||||
# PackageName.app/Contents/Resources/distribution
|
||||
installdir = os.path.normpath(
|
||||
os.path.join(installdir, '..', 'Resources')
|
||||
)
|
||||
os.mkdir(os.path.join(installdir, 'distribution'))
|
||||
policyFile = os.path.join(
|
||||
installdir, 'distribution', 'policies.json'
|
||||
)
|
||||
with open(policyFile, 'w') as fp:
|
||||
installdir = os.path.normpath(os.path.join(installdir, "..", "Resources"))
|
||||
os.mkdir(os.path.join(installdir, "distribution"))
|
||||
policyFile = os.path.join(installdir, "distribution", "policies.json")
|
||||
with open(policyFile, "w") as fp:
|
||||
json.dump(updatePolicy, fp, indent=2)
|
||||
|
||||
def _start(self, profile=None, addons=(), cmdargs=(), preferences=None,
|
||||
adb_profile_dir=None):
|
||||
profile = self._create_profile(profile=profile, addons=addons,
|
||||
preferences=preferences)
|
||||
def _start(
|
||||
self, profile=None, addons=(), cmdargs=(), preferences=None, adb_profile_dir=None,
|
||||
):
|
||||
profile = self._create_profile(profile=profile, addons=addons, preferences=preferences)
|
||||
|
||||
LOG.info("Launching %s" % self.binary)
|
||||
self.runner = Runner(binary=self.binary,
|
||||
cmdargs=cmdargs,
|
||||
profile=profile)
|
||||
self.runner = Runner(binary=self.binary, cmdargs=cmdargs, profile=profile)
|
||||
|
||||
def _on_exit():
|
||||
# if we are stopping the process do not log anything.
|
||||
|
@ -243,23 +226,22 @@ class MozRunnerLauncher(Launcher):
|
|||
except Exception:
|
||||
print()
|
||||
LOG.error(
|
||||
"Error while waiting process, consider filing a bug.",
|
||||
exc_info=True
|
||||
"Error while waiting process, consider filing a bug.", exc_info=True,
|
||||
)
|
||||
return
|
||||
if exitcode != 0:
|
||||
# first print a blank line, to be sure we don't
|
||||
# write on an already printed line without EOL.
|
||||
print()
|
||||
LOG.warning('Process exited with code %s' % exitcode)
|
||||
LOG.warning("Process exited with code %s" % exitcode)
|
||||
|
||||
# we don't need stdin, and GUI will not work in Windowed mode if set
|
||||
# see: https://stackoverflow.com/a/40108817
|
||||
devnull = open(os.devnull, 'wb')
|
||||
devnull = open(os.devnull, "wb")
|
||||
self.runner.process_args = {
|
||||
'processOutputLine': [get_default_logger("process").info],
|
||||
'stdin': devnull,
|
||||
'onFinish': _on_exit,
|
||||
"processOutputLine": [get_default_logger("process").info],
|
||||
"stdin": devnull,
|
||||
"onFinish": _on_exit,
|
||||
}
|
||||
self.runner.start()
|
||||
|
||||
|
@ -267,7 +249,7 @@ class MozRunnerLauncher(Launcher):
|
|||
return self.runner.wait()
|
||||
|
||||
def _stop(self):
|
||||
if mozinfo.os == "win" and self.app_name == 'firefox':
|
||||
if mozinfo.os == "win" and self.app_name == "firefox":
|
||||
# for some reason, stopping the runner may hang on windows. For
|
||||
# example restart the browser in safe mode, it will hang for a
|
||||
# couple of minutes. As a workaround, we call that in a thread and
|
||||
|
@ -294,17 +276,14 @@ class MozRunnerLauncher(Launcher):
|
|||
return safe_get_version(binary=self.binary)
|
||||
|
||||
|
||||
REGISTRY = ClassRegistry('app_name')
|
||||
REGISTRY = ClassRegistry("app_name")
|
||||
|
||||
|
||||
def create_launcher(buildinfo):
|
||||
"""
|
||||
Create and returns an instance launcher for the given buildinfo.
|
||||
"""
|
||||
return REGISTRY.get(buildinfo.app_name)(
|
||||
buildinfo.build_file,
|
||||
task_id=buildinfo.task_id
|
||||
)
|
||||
return REGISTRY.get(buildinfo.app_name)(buildinfo.build_file, task_id=buildinfo.task_id)
|
||||
|
||||
|
||||
class FirefoxRegressionProfile(Profile):
|
||||
|
@ -317,39 +296,38 @@ class FirefoxRegressionProfile(Profile):
|
|||
preferences = {
|
||||
# Don't automatically update the application (only works on older
|
||||
# versions of Firefox)
|
||||
'app.update.enabled': False,
|
||||
"app.update.enabled": False,
|
||||
# On newer versions of Firefox (where disabling automatic updates
|
||||
# is impossible, at least don't update automatically)
|
||||
'app.update.auto': False,
|
||||
"app.update.auto": False,
|
||||
# Don't automatically download the update (this pref is specific to
|
||||
# some versions of Fennec)
|
||||
'app.update.autodownload': 'disabled',
|
||||
"app.update.autodownload": "disabled",
|
||||
# Don't restore the last open set of tabs
|
||||
# if the browser has crashed
|
||||
'browser.sessionstore.resume_from_crash': False,
|
||||
"browser.sessionstore.resume_from_crash": False,
|
||||
# Don't check for the default web browser during startup
|
||||
'browser.shell.checkDefaultBrowser': False,
|
||||
"browser.shell.checkDefaultBrowser": False,
|
||||
# Don't warn on exit when multiple tabs are open
|
||||
'browser.tabs.warnOnClose': False,
|
||||
"browser.tabs.warnOnClose": False,
|
||||
# Don't warn when exiting the browser
|
||||
'browser.warnOnQuit': False,
|
||||
"browser.warnOnQuit": False,
|
||||
# Don't send Firefox health reports to the production
|
||||
# server
|
||||
'datareporting.healthreport.uploadEnabled': False,
|
||||
'datareporting.healthreport.documentServerURI':
|
||||
'http://%(server)s/healthreport/',
|
||||
"datareporting.healthreport.uploadEnabled": False,
|
||||
"datareporting.healthreport.documentServerURI": "http://%(server)s/healthreport/",
|
||||
# Don't show tab with privacy notice on every launch
|
||||
'datareporting.policy.dataSubmissionPolicyBypassNotification': True,
|
||||
"datareporting.policy.dataSubmissionPolicyBypassNotification": True,
|
||||
# Don't report telemetry information
|
||||
'toolkit.telemetry.enabled': False,
|
||||
"toolkit.telemetry.enabled": False,
|
||||
# Allow sideloading extensions
|
||||
'extensions.autoDisableScopes': 0,
|
||||
"extensions.autoDisableScopes": 0,
|
||||
# Disable what's new page
|
||||
'browser.startup.homepage_override.mstone': 'ignore',
|
||||
"browser.startup.homepage_override.mstone": "ignore",
|
||||
}
|
||||
|
||||
|
||||
@REGISTRY.register('firefox')
|
||||
@REGISTRY.register("firefox")
|
||||
class FirefoxLauncher(MozRunnerLauncher):
|
||||
profile_class = FirefoxRegressionProfile
|
||||
|
||||
|
@ -357,11 +335,12 @@ class FirefoxLauncher(MozRunnerLauncher):
|
|||
super(FirefoxLauncher, self)._install(dest)
|
||||
self._disableUpdateByPolicy()
|
||||
|
||||
def _start(self, profile=None, addons=(), cmdargs=(), preferences=None,
|
||||
adb_profile_dir=None):
|
||||
super(FirefoxLauncher, self)._start(profile, addons,
|
||||
['--allow-downgrade'] + cmdargs,
|
||||
preferences, adb_profile_dir)
|
||||
def _start(
|
||||
self, profile=None, addons=(), cmdargs=(), preferences=None, adb_profile_dir=None,
|
||||
):
|
||||
super(FirefoxLauncher, self)._start(
|
||||
profile, addons, ["--allow-downgrade"] + cmdargs, preferences, adb_profile_dir,
|
||||
)
|
||||
|
||||
|
||||
class ThunderbirdRegressionProfile(ThunderbirdProfile):
|
||||
|
@ -371,12 +350,12 @@ class ThunderbirdRegressionProfile(ThunderbirdProfile):
|
|||
|
||||
preferences = {
|
||||
# Don't automatically update the application
|
||||
'app.update.enabled': False,
|
||||
'app.update.auto': False,
|
||||
"app.update.enabled": False,
|
||||
"app.update.auto": False,
|
||||
}
|
||||
|
||||
|
||||
@REGISTRY.register('thunderbird')
|
||||
@REGISTRY.register("thunderbird")
|
||||
class ThunderbirdLauncher(MozRunnerLauncher):
|
||||
profile_class = ThunderbirdRegressionProfile
|
||||
|
||||
|
@ -403,39 +382,36 @@ class AndroidLauncher(Launcher):
|
|||
except ADBError as adb_error:
|
||||
raise LauncherNotRunnable(str(adb_error))
|
||||
if not devices:
|
||||
raise LauncherNotRunnable("No android device connected."
|
||||
" Connect a device and try again.")
|
||||
raise LauncherNotRunnable(
|
||||
"No android device connected." " Connect a device and try again."
|
||||
)
|
||||
|
||||
def _install(self, dest):
|
||||
# get info now, as dest may be removed
|
||||
self.app_info = safe_get_version(binary=dest)
|
||||
self.package_name = self.app_info.get("package_name",
|
||||
self._get_package_name())
|
||||
self.package_name = self.app_info.get("package_name", self._get_package_name())
|
||||
self.adb = ADBAndroid(require_root=False)
|
||||
try:
|
||||
self.adb.uninstall_app(self.package_name)
|
||||
except ADBError as msg:
|
||||
LOG.warning(
|
||||
"Failed to uninstall %s (%s)\nThis is normal if it is the"
|
||||
" first time the application is installed."
|
||||
% (self.package_name, msg)
|
||||
" first time the application is installed." % (self.package_name, msg)
|
||||
)
|
||||
self.adb.install_app(dest)
|
||||
|
||||
def _start(self, profile=None, addons=(), cmdargs=(), preferences=None,
|
||||
adb_profile_dir=None):
|
||||
def _start(
|
||||
self, profile=None, addons=(), cmdargs=(), preferences=None, adb_profile_dir=None,
|
||||
):
|
||||
# for now we don't handle addons on the profile for fennec
|
||||
profile = self._create_profile(profile=profile,
|
||||
preferences=preferences)
|
||||
profile = self._create_profile(profile=profile, preferences=preferences)
|
||||
# send the profile on the device
|
||||
if not adb_profile_dir:
|
||||
adb_profile_dir = self.adb.test_root
|
||||
self.remote_profile = "/".join([adb_profile_dir,
|
||||
os.path.basename(profile.profile)])
|
||||
self.remote_profile = "/".join([adb_profile_dir, os.path.basename(profile.profile)])
|
||||
if self.adb.exists(self.remote_profile):
|
||||
self.adb.rm(self.remote_profile, recursive=True)
|
||||
LOG.debug("Pushing profile to device (%s -> %s)" % (
|
||||
profile.profile, self.remote_profile))
|
||||
LOG.debug("Pushing profile to device (%s -> %s)" % (profile.profile, self.remote_profile))
|
||||
self.adb.push(profile.profile, self.remote_profile)
|
||||
self._launch()
|
||||
|
||||
|
@ -452,31 +428,32 @@ class AndroidLauncher(Launcher):
|
|||
return self.app_info
|
||||
|
||||
|
||||
@REGISTRY.register('fennec')
|
||||
@REGISTRY.register("fennec")
|
||||
class FennecLauncher(AndroidLauncher):
|
||||
def _get_package_name(self):
|
||||
return "org.mozilla.fennec"
|
||||
|
||||
def _launch(self):
|
||||
LOG.debug("Launching fennec")
|
||||
self.adb.launch_fennec(self.package_name,
|
||||
extra_args=["-profile", self.remote_profile])
|
||||
self.adb.launch_fennec(self.package_name, extra_args=["-profile", self.remote_profile])
|
||||
|
||||
|
||||
@REGISTRY.register('gve')
|
||||
@REGISTRY.register("gve")
|
||||
class GeckoViewExampleLauncher(AndroidLauncher):
|
||||
def _get_package_name(self):
|
||||
return "org.mozilla.geckoview_example"
|
||||
|
||||
def _launch(self):
|
||||
LOG.debug("Launching geckoview_example")
|
||||
self.adb.launch_activity(self.package_name,
|
||||
self.adb.launch_activity(
|
||||
self.package_name,
|
||||
activity_name="GeckoViewActivity",
|
||||
extra_args=["-profile", self.remote_profile],
|
||||
e10s=True)
|
||||
e10s=True,
|
||||
)
|
||||
|
||||
|
||||
@REGISTRY.register('jsshell')
|
||||
@REGISTRY.register("jsshell")
|
||||
class JsShellLauncher(Launcher):
|
||||
temp_dir = None
|
||||
|
||||
|
@ -485,10 +462,7 @@ class JsShellLauncher(Launcher):
|
|||
try:
|
||||
with zipfile.ZipFile(dest, "r") as z:
|
||||
z.extractall(self.tempdir)
|
||||
self.binary = os.path.join(
|
||||
self.tempdir,
|
||||
'js' if mozinfo.os != 'win' else 'js.exe'
|
||||
)
|
||||
self.binary = os.path.join(self.tempdir, "js" if mozinfo.os != "win" else "js.exe")
|
||||
# set the file executable
|
||||
os.chmod(self.binary, os.stat(self.binary).st_mode | stat.S_IEXEC)
|
||||
except Exception:
|
||||
|
@ -499,7 +473,7 @@ class JsShellLauncher(Launcher):
|
|||
LOG.info("Launching %s" % self.binary)
|
||||
res = call([self.binary], cwd=self.tempdir)
|
||||
if res != 0:
|
||||
LOG.warning('jsshell exited with code %d.' % res)
|
||||
LOG.warning("jsshell exited with code %d." % res)
|
||||
|
||||
def _wait(self):
|
||||
pass
|
||||
|
|
|
@ -3,14 +3,15 @@ Logging and outputting configuration and utilities.
|
|||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import time
|
||||
import mozinfo
|
||||
|
||||
from colorama import Fore, Style, Back
|
||||
from mozlog.structuredlog import set_default_logger, StructuredLogger
|
||||
from mozlog.handlers import StreamHandler, LogLevelFilter
|
||||
import mozinfo
|
||||
import six
|
||||
from colorama import Back, Fore, Style
|
||||
from mozlog.handlers import LogLevelFilter, StreamHandler
|
||||
from mozlog.structuredlog import StructuredLogger, set_default_logger
|
||||
|
||||
ALLOW_COLOR = sys.stdout.isatty()
|
||||
|
||||
|
@ -18,7 +19,7 @@ ALLOW_COLOR = sys.stdout.isatty()
|
|||
def _format_seconds(total):
|
||||
"""Format number of seconds to MM:SS.DD form."""
|
||||
minutes, seconds = divmod(total, 60)
|
||||
return '%2d:%05.2f' % (minutes, seconds)
|
||||
return "%2d:%05.2f" % (minutes, seconds)
|
||||
|
||||
|
||||
def init_logger(debug=True, allow_color=ALLOW_COLOR, output=None):
|
||||
|
@ -29,31 +30,30 @@ def init_logger(debug=True, allow_color=ALLOW_COLOR, output=None):
|
|||
output = output or sys.stdout
|
||||
start = time.time() * 1000
|
||||
level_color = {
|
||||
'WARNING': Fore.MAGENTA + Style.BRIGHT,
|
||||
'CRITICAL': Fore.RED + Style.BRIGHT,
|
||||
'ERROR': Fore.RED + Style.BRIGHT,
|
||||
'DEBUG': Fore.CYAN + Style.BRIGHT,
|
||||
'INFO': Style.BRIGHT,
|
||||
"WARNING": Fore.MAGENTA + Style.BRIGHT,
|
||||
"CRITICAL": Fore.RED + Style.BRIGHT,
|
||||
"ERROR": Fore.RED + Style.BRIGHT,
|
||||
"DEBUG": Fore.CYAN + Style.BRIGHT,
|
||||
"INFO": Style.BRIGHT,
|
||||
}
|
||||
time_color = Fore.BLUE
|
||||
if mozinfo.os == "win":
|
||||
time_color += Style.BRIGHT # this is unreadable on windows without it
|
||||
|
||||
def format_log(data):
|
||||
level = data['level']
|
||||
elapsed = _format_seconds((data['time'] - start) / 1000)
|
||||
level = data["level"]
|
||||
elapsed = _format_seconds((data["time"] - start) / 1000)
|
||||
if allow_color:
|
||||
elapsed = time_color + elapsed + Style.RESET_ALL
|
||||
if level in level_color:
|
||||
level = level_color[level] + level + Style.RESET_ALL
|
||||
msg = data['message']
|
||||
if 'stack' in data:
|
||||
msg += "\n%s" % data['stack']
|
||||
msg = data["message"]
|
||||
if "stack" in data:
|
||||
msg += "\n%s" % data["stack"]
|
||||
return "%s %s: %s\n" % (elapsed, level, msg)
|
||||
|
||||
logger = StructuredLogger("mozregression")
|
||||
handler = LogLevelFilter(StreamHandler(output, format_log),
|
||||
'debug' if debug else 'info')
|
||||
handler = LogLevelFilter(StreamHandler(output, format_log), "debug" if debug else "info")
|
||||
logger.add_handler(handler)
|
||||
|
||||
set_default_logger(logger)
|
||||
|
@ -63,10 +63,10 @@ def init_logger(debug=True, allow_color=ALLOW_COLOR, output=None):
|
|||
COLORS = {}
|
||||
NO_COLORS = {}
|
||||
|
||||
for prefix, st in (('b', Back), ('s', Style), ('f', Fore)):
|
||||
for prefix, st in (("b", Back), ("s", Style), ("f", Fore)):
|
||||
for name, value in six.iteritems(st.__dict__):
|
||||
COLORS[prefix + name] = value
|
||||
NO_COLORS[prefix + name] = ''
|
||||
NO_COLORS[prefix + name] = ""
|
||||
|
||||
|
||||
def colorize(text, allow_color=ALLOW_COLOR):
|
||||
|
|
|
@ -10,12 +10,13 @@ break mach!
|
|||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from argparse import Namespace
|
||||
|
||||
from mozregression import __version__
|
||||
from mozregression.cli import create_parser
|
||||
from mozregression.config import DEFAULT_CONF_FNAME, get_defaults
|
||||
from mozregression.main import main, pypi_latest_version
|
||||
from mozregression import __version__
|
||||
|
||||
|
||||
def new_release_on_pypi():
|
||||
|
|
|
@ -7,34 +7,33 @@ Entry point for the mozregression command line.
|
|||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
import os
|
||||
import sys
|
||||
import requests
|
||||
import atexit
|
||||
import pipes
|
||||
import mozfile
|
||||
import colorama
|
||||
|
||||
import atexit
|
||||
import os
|
||||
import pipes
|
||||
import sys
|
||||
|
||||
import colorama
|
||||
import mozfile
|
||||
import requests
|
||||
from mozlog import get_proxy_logger
|
||||
from requests.exceptions import RequestException, HTTPError
|
||||
from requests.exceptions import HTTPError, RequestException
|
||||
|
||||
from mozregression import __version__
|
||||
from mozregression.config import TC_CREDENTIALS_FNAME, DEFAULT_EXPAND
|
||||
from mozregression.approx_persist import ApproxPersistChooser
|
||||
from mozregression.bisector import Bisection, Bisector, IntegrationHandler, NightlyHandler
|
||||
from mozregression.bugzilla import bug_url, find_bugids_in_push
|
||||
from mozregression.cli import cli
|
||||
from mozregression.errors import MozRegressionError, GoodBadExpectationError
|
||||
from mozregression.bisector import (Bisector, NightlyHandler, IntegrationHandler,
|
||||
Bisection)
|
||||
from mozregression.config import DEFAULT_EXPAND, TC_CREDENTIALS_FNAME
|
||||
from mozregression.download_manager import BuildDownloadManager
|
||||
from mozregression.errors import GoodBadExpectationError, MozRegressionError
|
||||
from mozregression.fetch_build_info import IntegrationInfoFetcher, NightlyInfoFetcher
|
||||
from mozregression.json_pushes import JsonPushes
|
||||
from mozregression.launchers import REGISTRY as APP_REGISTRY
|
||||
from mozregression.network import set_http_session
|
||||
from mozregression.tempdir import safe_mkdtemp
|
||||
from mozregression.test_runner import ManualTestRunner, CommandTestRunner
|
||||
from mozregression.download_manager import BuildDownloadManager
|
||||
from mozregression.persist_limit import PersistLimit
|
||||
from mozregression.fetch_build_info import (NightlyInfoFetcher,
|
||||
IntegrationInfoFetcher)
|
||||
from mozregression.json_pushes import JsonPushes
|
||||
from mozregression.bugzilla import find_bugids_in_push, bug_url
|
||||
from mozregression.approx_persist import ApproxPersistChooser
|
||||
from mozregression.tempdir import safe_mkdtemp
|
||||
from mozregression.test_runner import CommandTestRunner, ManualTestRunner
|
||||
|
||||
LOG = get_proxy_logger("main")
|
||||
|
||||
|
@ -55,16 +54,16 @@ class Application(object):
|
|||
launcher_class.check_is_runnable()
|
||||
# init global profile if required
|
||||
self._global_profile = None
|
||||
if options.profile_persistence in ('clone-first', 'reuse'):
|
||||
if options.profile_persistence in ("clone-first", "reuse"):
|
||||
self._global_profile = launcher_class.create_profile(
|
||||
profile=options.profile,
|
||||
addons=options.addons,
|
||||
preferences=options.preferences,
|
||||
clone=options.profile_persistence == 'clone-first'
|
||||
clone=options.profile_persistence == "clone-first",
|
||||
)
|
||||
options.cmdargs = options.cmdargs + ['--allow-downgrade']
|
||||
options.cmdargs = options.cmdargs + ["--allow-downgrade"]
|
||||
elif options.profile:
|
||||
options.cmdargs = options.cmdargs + ['--allow-downgrade']
|
||||
options.cmdargs = options.cmdargs + ["--allow-downgrade"]
|
||||
|
||||
def clear(self):
|
||||
if self._build_download_manager:
|
||||
|
@ -79,21 +78,22 @@ class Application(object):
|
|||
# https://bugzilla.mozilla.org/show_bug.cgi?id=1231745
|
||||
self._build_download_manager.wait(raise_if_error=False)
|
||||
mozfile.remove(self._download_dir)
|
||||
if self._global_profile \
|
||||
and self.options.profile_persistence == 'clone-first':
|
||||
if self._global_profile and self.options.profile_persistence == "clone-first":
|
||||
self._global_profile.cleanup()
|
||||
|
||||
@property
|
||||
def test_runner(self):
|
||||
if self._test_runner is None:
|
||||
if self.options.command is None:
|
||||
self._test_runner = ManualTestRunner(launcher_kwargs=dict(
|
||||
self._test_runner = ManualTestRunner(
|
||||
launcher_kwargs=dict(
|
||||
addons=self.options.addons,
|
||||
profile=self._global_profile or self.options.profile,
|
||||
cmdargs=self.options.cmdargs,
|
||||
preferences=self.options.preferences,
|
||||
adb_profile_dir=self.options.adb_profile_dir,
|
||||
))
|
||||
)
|
||||
)
|
||||
else:
|
||||
self._test_runner = CommandTestRunner(self.options.command)
|
||||
return self._test_runner
|
||||
|
@ -102,11 +102,13 @@ class Application(object):
|
|||
def bisector(self):
|
||||
if self._bisector is None:
|
||||
self._bisector = Bisector(
|
||||
self.fetch_config, self.test_runner,
|
||||
self.fetch_config,
|
||||
self.test_runner,
|
||||
self.build_download_manager,
|
||||
dl_in_background=self.options.background_dl,
|
||||
approx_chooser=(None if self.options.approx_policy != 'auto'
|
||||
else ApproxPersistChooser(7)),
|
||||
approx_chooser=(
|
||||
None if self.options.approx_policy != "auto" else ApproxPersistChooser(7)
|
||||
),
|
||||
)
|
||||
return self._bisector
|
||||
|
||||
|
@ -120,7 +122,7 @@ class Application(object):
|
|||
self._build_download_manager = BuildDownloadManager(
|
||||
self._download_dir,
|
||||
background_dl_policy=background_dl_policy,
|
||||
persist_limit=PersistLimit(self.options.persist_size_limit)
|
||||
persist_limit=PersistLimit(self.options.persist_size_limit),
|
||||
)
|
||||
return self._build_download_manager
|
||||
|
||||
|
@ -128,25 +130,26 @@ class Application(object):
|
|||
good_date, bad_date = self.options.good, self.options.bad
|
||||
handler = NightlyHandler(
|
||||
find_fix=self.options.find_fix,
|
||||
ensure_good_and_bad=self.options.mode != 'no-first-check',
|
||||
ensure_good_and_bad=self.options.mode != "no-first-check",
|
||||
)
|
||||
result = self._do_bisect(handler, good_date, bad_date)
|
||||
if result == Bisection.FINISHED:
|
||||
LOG.info("Got as far as we can go bisecting nightlies...")
|
||||
handler.print_range()
|
||||
LOG.info("Switching bisection method to taskcluster")
|
||||
self.fetch_config.set_repo(
|
||||
self.fetch_config.get_nightly_repo(handler.bad_date))
|
||||
return self._bisect_integration(handler.good_revision,
|
||||
handler.bad_revision,
|
||||
expand=DEFAULT_EXPAND)
|
||||
self.fetch_config.set_repo(self.fetch_config.get_nightly_repo(handler.bad_date))
|
||||
return self._bisect_integration(
|
||||
handler.good_revision, handler.bad_revision, expand=DEFAULT_EXPAND
|
||||
)
|
||||
elif result == Bisection.USER_EXIT:
|
||||
self._print_resume_info(handler)
|
||||
else:
|
||||
# NO_DATA
|
||||
LOG.info("Unable to get valid builds within the given"
|
||||
LOG.info(
|
||||
"Unable to get valid builds within the given"
|
||||
" range. You should try to launch mozregression"
|
||||
" again with a larger date range.")
|
||||
" again with a larger date range."
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
|
@ -154,15 +157,17 @@ class Application(object):
|
|||
return self._bisect_integration(
|
||||
self.options.good,
|
||||
self.options.bad,
|
||||
ensure_good_and_bad=self.options.mode != 'no-first-check',
|
||||
ensure_good_and_bad=self.options.mode != "no-first-check",
|
||||
)
|
||||
|
||||
def _bisect_integration(self, good_rev, bad_rev, ensure_good_and_bad=False,
|
||||
expand=0):
|
||||
LOG.info("Getting %s builds between %s and %s"
|
||||
% (self.fetch_config.integration_branch, good_rev, bad_rev))
|
||||
handler = IntegrationHandler(find_fix=self.options.find_fix,
|
||||
ensure_good_and_bad=ensure_good_and_bad)
|
||||
def _bisect_integration(self, good_rev, bad_rev, ensure_good_and_bad=False, expand=0):
|
||||
LOG.info(
|
||||
"Getting %s builds between %s and %s"
|
||||
% (self.fetch_config.integration_branch, good_rev, bad_rev)
|
||||
)
|
||||
handler = IntegrationHandler(
|
||||
find_fix=self.options.find_fix, ensure_good_and_bad=ensure_good_and_bad
|
||||
)
|
||||
result = self._do_bisect(handler, good_rev, bad_rev, expand=expand)
|
||||
if result == Bisection.FINISHED:
|
||||
LOG.info("No more integration revisions, bisection finished.")
|
||||
|
@ -179,8 +184,7 @@ class Application(object):
|
|||
if result:
|
||||
branch, good_rev, bad_rev = result
|
||||
self.fetch_config.set_repo(branch)
|
||||
return self._bisect_integration(good_rev, bad_rev,
|
||||
expand=DEFAULT_EXPAND)
|
||||
return self._bisect_integration(good_rev, bad_rev, expand=DEFAULT_EXPAND)
|
||||
else:
|
||||
# This code is broken, it prints out the message even when
|
||||
# there are multiple bug numbers or commits in the range.
|
||||
|
@ -191,38 +195,42 @@ class Application(object):
|
|||
# just missing the builds for some intermediate builds)
|
||||
# (2) there is only one bug number in that push
|
||||
jp = JsonPushes(handler.build_range[1].repo_name)
|
||||
num_pushes = len(jp.pushes_within_changes(
|
||||
handler.build_range[0].changeset,
|
||||
handler.build_range[1].changeset))
|
||||
num_pushes = len(
|
||||
jp.pushes_within_changes(
|
||||
handler.build_range[0].changeset, handler.build_range[1].changeset,
|
||||
)
|
||||
)
|
||||
if num_pushes == 2:
|
||||
bugids = find_bugids_in_push(
|
||||
handler.build_range[1].repo_name,
|
||||
handler.build_range[1].changeset
|
||||
handler.build_range[1].repo_name, handler.build_range[1].changeset,
|
||||
)
|
||||
if len(bugids) == 1:
|
||||
word = 'fix' if handler.find_fix else 'regression'
|
||||
LOG.info("Looks like the following bug has the "
|
||||
word = "fix" if handler.find_fix else "regression"
|
||||
LOG.info(
|
||||
"Looks like the following bug has the "
|
||||
" changes which introduced the"
|
||||
" {}:\n{}".format(word,
|
||||
bug_url(bugids[0])))
|
||||
" {}:\n{}".format(word, bug_url(bugids[0]))
|
||||
)
|
||||
elif result == Bisection.USER_EXIT:
|
||||
self._print_resume_info(handler)
|
||||
else:
|
||||
# NO_DATA. With integration branches, this can not happen if changesets
|
||||
# are incorrect - so builds are probably too old
|
||||
LOG.info(
|
||||
'There are no build artifacts for these changesets (they are probably too old).')
|
||||
"There are no build artifacts for these changesets (they are probably too old)."
|
||||
)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def _do_bisect(self, handler, good, bad, **kwargs):
|
||||
try:
|
||||
return self.bisector.bisect(handler, good, bad, **kwargs)
|
||||
except (KeyboardInterrupt, MozRegressionError,
|
||||
RequestException) as exc:
|
||||
if handler.good_revision is not None and \
|
||||
handler.bad_revision is not None and \
|
||||
not isinstance(exc, GoodBadExpectationError):
|
||||
except (KeyboardInterrupt, MozRegressionError, RequestException) as exc:
|
||||
if (
|
||||
handler.good_revision is not None
|
||||
and handler.bad_revision is not None
|
||||
and not isinstance(exc, GoodBadExpectationError)
|
||||
):
|
||||
atexit.register(self._on_exit_print_resume_info, handler)
|
||||
raise
|
||||
|
||||
|
@ -230,8 +238,7 @@ class Application(object):
|
|||
# copy sys.argv, remove every --good/--bad/--repo related argument,
|
||||
# then add our own
|
||||
argv = sys.argv[:]
|
||||
args = ('--good', '--bad', '-g', '-b', '--good-rev', '--bad-rev',
|
||||
'--repo')
|
||||
args = ("--good", "--bad", "-g", "-b", "--good-rev", "--bad-rev", "--repo")
|
||||
indexes_to_remove = []
|
||||
for i, arg in enumerate(argv):
|
||||
if i in indexes_to_remove:
|
||||
|
@ -241,24 +248,24 @@ class Application(object):
|
|||
# handle '--good 2015-01-01'
|
||||
indexes_to_remove.extend((i, i + 1))
|
||||
break
|
||||
elif arg.startswith(karg + '='):
|
||||
elif arg.startswith(karg + "="):
|
||||
# handle '--good=2015-01-01'
|
||||
indexes_to_remove.append(i)
|
||||
break
|
||||
for i in reversed(indexes_to_remove):
|
||||
del argv[i]
|
||||
|
||||
argv.append('--repo=%s' % handler.build_range[0].repo_name)
|
||||
argv.append("--repo=%s" % handler.build_range[0].repo_name)
|
||||
|
||||
if hasattr(handler, 'good_date'):
|
||||
argv.append('--good=%s' % handler.good_date)
|
||||
argv.append('--bad=%s' % handler.bad_date)
|
||||
if hasattr(handler, "good_date"):
|
||||
argv.append("--good=%s" % handler.good_date)
|
||||
argv.append("--bad=%s" % handler.bad_date)
|
||||
else:
|
||||
argv.append('--good=%s' % handler.good_revision)
|
||||
argv.append('--bad=%s' % handler.bad_revision)
|
||||
argv.append("--good=%s" % handler.good_revision)
|
||||
argv.append("--bad=%s" % handler.bad_revision)
|
||||
|
||||
LOG.info('To resume, run:')
|
||||
LOG.info(' '.join([pipes.quote(arg) for arg in argv]))
|
||||
LOG.info("To resume, run:")
|
||||
LOG.info(" ".join([pipes.quote(arg) for arg in argv]))
|
||||
|
||||
def _on_exit_print_resume_info(self, handler):
|
||||
handler.print_range()
|
||||
|
@ -279,7 +286,7 @@ class Application(object):
|
|||
|
||||
def pypi_latest_version():
|
||||
url = "https://pypi.python.org/pypi/mozregression/json"
|
||||
return requests.get(url, timeout=10).json()['info']['version']
|
||||
return requests.get(url, timeout=10).json()["info"]["version"]
|
||||
|
||||
|
||||
def check_mozregression_version():
|
||||
|
@ -290,12 +297,15 @@ def check_mozregression_version():
|
|||
return
|
||||
|
||||
if __version__ != mozregression_version:
|
||||
LOG.warning("You are using mozregression version %s, "
|
||||
"however version %s is available."
|
||||
% (__version__, mozregression_version))
|
||||
LOG.warning(
|
||||
"You are using mozregression version %s, "
|
||||
"however version %s is available." % (__version__, mozregression_version)
|
||||
)
|
||||
|
||||
LOG.warning("You should consider upgrading via the 'pip install"
|
||||
" --upgrade mozregression' command.")
|
||||
LOG.warning(
|
||||
"You should consider upgrading via the 'pip install"
|
||||
" --upgrade mozregression' command."
|
||||
)
|
||||
|
||||
|
||||
def main(argv=None, namespace=None, check_new_version=True):
|
||||
|
@ -303,7 +313,7 @@ def main(argv=None, namespace=None, check_new_version=True):
|
|||
main entry point of mozregression command line.
|
||||
"""
|
||||
# terminal color support on windows
|
||||
if os.name == 'nt':
|
||||
if os.name == "nt":
|
||||
colorama.init()
|
||||
|
||||
if sys.version_info <= (2, 7, 9):
|
||||
|
@ -311,6 +321,7 @@ def main(argv=None, namespace=None, check_new_version=True):
|
|||
# of warnings that we do not want. See
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=1199020
|
||||
import logging
|
||||
|
||||
logging.captureWarnings(True)
|
||||
|
||||
config, app = None, None
|
||||
|
|
|
@ -7,12 +7,13 @@ network functions utilities for mozregression.
|
|||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
import redo
|
||||
import requests
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
import six
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
|
||||
def retry_get(url, **karwgs):
|
||||
|
@ -23,10 +24,14 @@ def retry_get(url, **karwgs):
|
|||
it will retry the requests call three times in case of HTTPError or
|
||||
ConnectionError.
|
||||
"""
|
||||
return redo.retry(get_http_session().get, attempts=3, sleeptime=1,
|
||||
retry_exceptions=(requests.exceptions.HTTPError,
|
||||
requests.exceptions.ConnectionError),
|
||||
args=(url,), kwargs=karwgs)
|
||||
return redo.retry(
|
||||
get_http_session().get,
|
||||
attempts=3,
|
||||
sleeptime=1,
|
||||
retry_exceptions=(requests.exceptions.HTTPError, requests.exceptions.ConnectionError,),
|
||||
args=(url,),
|
||||
kwargs=karwgs,
|
||||
)
|
||||
|
||||
|
||||
SESSION = None
|
||||
|
@ -53,6 +58,7 @@ def set_http_session(session=None, get_defaults=None):
|
|||
for k, v in six.iteritems(get_defaults):
|
||||
kwargs.setdefault(k, v)
|
||||
return _get(*args, **kwargs)
|
||||
|
||||
session.get = _default_get
|
||||
SESSION = session
|
||||
|
||||
|
@ -85,19 +91,20 @@ def url_links(url, regex=None, auth=None):
|
|||
regex = re.compile(regex)
|
||||
match = regex.match
|
||||
else:
|
||||
|
||||
def match(_):
|
||||
return True
|
||||
|
||||
# do not return a generator but an array, so we can store it for later use
|
||||
result = []
|
||||
for link in soup.findAll('a'):
|
||||
href = link.get('href')
|
||||
for link in soup.findAll("a"):
|
||||
href = link.get("href")
|
||||
# return "relative" part of the url only
|
||||
if href.startswith('/'):
|
||||
if href.endswith('/'):
|
||||
href = href.strip('/').rsplit('/', 1)[-1] + '/'
|
||||
if href.startswith("/"):
|
||||
if href.endswith("/"):
|
||||
href = href.strip("/").rsplit("/", 1)[-1] + "/"
|
||||
else:
|
||||
href = href.rsplit('/', 1)[-1]
|
||||
href = href.rsplit("/", 1)[-1]
|
||||
if match(href):
|
||||
result.append(href)
|
||||
return result
|
||||
|
|
|
@ -3,14 +3,15 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import stat
|
||||
import mozfile
|
||||
from glob import glob
|
||||
from collections import namedtuple
|
||||
from glob import glob
|
||||
|
||||
import mozfile
|
||||
|
||||
File = namedtuple('File', ('path', 'stat'))
|
||||
File = namedtuple("File", ("path", "stat"))
|
||||
|
||||
|
||||
class PersistLimit(object):
|
||||
|
@ -25,6 +26,7 @@ class PersistLimit(object):
|
|||
:param file_limit: even if the size limit is reached, this force
|
||||
to keep at least *file_limit* files.
|
||||
"""
|
||||
|
||||
def __init__(self, size_limit, file_limit=5):
|
||||
self.size_limit = size_limit
|
||||
self.file_limit = file_limit
|
||||
|
@ -60,8 +62,7 @@ class PersistLimit(object):
|
|||
return
|
||||
# sort by creation time, oldest first
|
||||
files = sorted(self.files, key=lambda f: f.stat.st_atime)
|
||||
while len(files) > self.file_limit and \
|
||||
self._files_size >= self.size_limit:
|
||||
while len(files) > self.file_limit and self._files_size >= self.size_limit:
|
||||
f = files.pop(0)
|
||||
mozfile.remove(f.path)
|
||||
self._files_size -= f.stat.st_size
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
from __future__ import absolute_import
|
||||
import re
|
||||
|
||||
import re
|
||||
from datetime import date
|
||||
|
||||
import six
|
||||
from six.moves import filter, map
|
||||
|
||||
from mozregression.errors import UnavailableRelease
|
||||
from mozregression.network import retry_get
|
||||
import six
|
||||
from six.moves import filter
|
||||
from six.moves import map
|
||||
|
||||
|
||||
def releases():
|
||||
|
@ -71,7 +72,7 @@ def releases():
|
|||
53: "2017-01-23",
|
||||
54: "2017-03-06",
|
||||
55: "2017-06-12",
|
||||
56: "2017-08-02"
|
||||
56: "2017-08-02",
|
||||
}
|
||||
|
||||
def filter_tags(tag_node):
|
||||
|
@ -89,10 +90,7 @@ def releases():
|
|||
response = retry_get(tags_url)
|
||||
|
||||
if response.status_code == 200:
|
||||
fetched_releases = list(map(
|
||||
map_tags,
|
||||
list(filter(filter_tags, response.json()["tags"]))
|
||||
))
|
||||
fetched_releases = list(map(map_tags, list(filter(filter_tags, response.json()["tags"]))))
|
||||
|
||||
for release in fetched_releases:
|
||||
releases.update(release)
|
||||
|
@ -114,10 +112,10 @@ def tag_of_release(release):
|
|||
"""
|
||||
Provide the mercurial tag of a release, suitable for use in place of a hash
|
||||
"""
|
||||
if re.match(r'^\d+$', release):
|
||||
release += '.0'
|
||||
if re.match(r'^\d+\.\d(\.\d)?$', release):
|
||||
return 'FIREFOX_%s_RELEASE' % release.replace('.', '_')
|
||||
if re.match(r"^\d+$", release):
|
||||
release += ".0"
|
||||
if re.match(r"^\d+\.\d(\.\d)?$", release):
|
||||
return "FIREFOX_%s_RELEASE" % release.replace(".", "_")
|
||||
else:
|
||||
raise UnavailableRelease(release)
|
||||
|
||||
|
@ -127,10 +125,10 @@ def tag_of_beta(release):
|
|||
Provide the mercurial tag of a beta release, suitable for use in place of a
|
||||
hash
|
||||
"""
|
||||
if re.match(r'^\d+\.0b\d+$', release):
|
||||
return 'FIREFOX_%s_RELEASE' % release.replace('.', '_')
|
||||
elif re.match(r'^\d+(\.0)?$', release):
|
||||
return 'FIREFOX_RELEASE_%s_BASE' % release.replace('.0', '')
|
||||
if re.match(r"^\d+\.0b\d+$", release):
|
||||
return "FIREFOX_%s_RELEASE" % release.replace(".", "_")
|
||||
elif re.match(r"^\d+(\.0)?$", release):
|
||||
return "FIREFOX_RELEASE_%s_BASE" % release.replace(".0", "")
|
||||
else:
|
||||
raise UnavailableRelease(release)
|
||||
|
||||
|
@ -142,6 +140,6 @@ def formatted_valid_release_dates():
|
|||
"""
|
||||
message = "Valid releases: \n"
|
||||
for key, value in six.iteritems(releases()):
|
||||
message += '% 3s: %s\n' % (key, value)
|
||||
message += "% 3s: %s\n" % (key, value)
|
||||
|
||||
return message
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import json
|
||||
|
||||
from taskcluster import utils as tc_utils
|
||||
|
||||
from mozregression.config import (get_defaults, DEFAULT_CONF_FNAME,
|
||||
TC_CREDENTIALS_FNAME)
|
||||
from mozregression.config import DEFAULT_CONF_FNAME, TC_CREDENTIALS_FNAME, get_defaults
|
||||
|
||||
|
||||
def tc_authenticate(logger):
|
||||
|
@ -16,8 +17,8 @@ def tc_authenticate(logger):
|
|||
"""
|
||||
# first, try to load credentials from mozregression config file
|
||||
defaults = get_defaults(DEFAULT_CONF_FNAME)
|
||||
client_id = defaults.get('taskcluster-clientid')
|
||||
access_token = defaults.get('taskcluster-accesstoken')
|
||||
client_id = defaults.get("taskcluster-clientid")
|
||||
access_token = defaults.get("taskcluster-accesstoken")
|
||||
if client_id and access_token:
|
||||
return dict(clientId=client_id, accessToken=access_token)
|
||||
|
||||
|
@ -25,7 +26,7 @@ def tc_authenticate(logger):
|
|||
# else, try to load a valid certificate locally
|
||||
with open(TC_CREDENTIALS_FNAME) as f:
|
||||
creds = json.load(f)
|
||||
if not tc_utils.isExpired(creds['certificate']):
|
||||
if not tc_utils.isExpired(creds["certificate"]):
|
||||
return creds
|
||||
except Exception:
|
||||
pass
|
||||
|
@ -41,6 +42,6 @@ def tc_authenticate(logger):
|
|||
creds = tc_utils.authenticate("mozregression private build access")
|
||||
|
||||
# save the credentials and the certificate for later use
|
||||
with open(TC_CREDENTIALS_FNAME, 'w') as f:
|
||||
with open(TC_CREDENTIALS_FNAME, "w") as f:
|
||||
json.dump(creds, f)
|
||||
return creds
|
||||
|
|
|
@ -2,20 +2,23 @@
|
|||
# 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 __future__ import absolute_import
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
import six
|
||||
|
||||
|
||||
def safe_mkdtemp():
|
||||
'''
|
||||
"""
|
||||
Creates a temporary directory using mkdtemp, but makes sure that the
|
||||
returned directory is the full path on windows (see:
|
||||
https://bugzilla.mozilla.org/show_bug.cgi?id=1385928)
|
||||
'''
|
||||
"""
|
||||
tempdir = tempfile.mkdtemp()
|
||||
if os.name == 'nt':
|
||||
if os.name == "nt":
|
||||
from ctypes import create_unicode_buffer, windll
|
||||
|
||||
BUFFER_SIZE = 500
|
||||
buffer = create_unicode_buffer(BUFFER_SIZE)
|
||||
get_long_path_name = windll.kernel32.GetLongPathNameW
|
||||
|
|
|
@ -7,20 +7,20 @@ This module implements a :class:`TestRunner` interface for testing builds
|
|||
and a default implementation :class:`ManualTestRunner`.
|
||||
"""
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
from mozlog import get_proxy_logger
|
||||
import subprocess
|
||||
import shlex
|
||||
import os
|
||||
import datetime
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
from mozregression.launchers import create_launcher as mozlauncher
|
||||
from mozregression.errors import TestCommandError, LauncherError
|
||||
import datetime
|
||||
import os
|
||||
import shlex
|
||||
import subprocess
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
import six
|
||||
from six.moves import range
|
||||
from six.moves import input
|
||||
from mozlog import get_proxy_logger
|
||||
from six.moves import input, range
|
||||
|
||||
from mozregression.errors import LauncherError, TestCommandError
|
||||
from mozregression.launchers import create_launcher as mozlauncher
|
||||
|
||||
LOG = get_proxy_logger("Test Runner")
|
||||
|
||||
|
@ -29,16 +29,13 @@ def create_launcher(build_info):
|
|||
"""
|
||||
Create and returns a :class:`mozregression.launchers.Launcher`.
|
||||
"""
|
||||
if build_info.build_type == 'nightly':
|
||||
if build_info.build_type == "nightly":
|
||||
if isinstance(build_info.build_date, datetime.datetime):
|
||||
desc = ("for buildid %s"
|
||||
% build_info.build_date.strftime("%Y%m%d%H%M%S"))
|
||||
desc = "for buildid %s" % build_info.build_date.strftime("%Y%m%d%H%M%S")
|
||||
else:
|
||||
desc = "for %s" % build_info.build_date
|
||||
else:
|
||||
desc = ("built on %s, revision %s"
|
||||
% (build_info.build_date,
|
||||
build_info.short_changeset))
|
||||
desc = "built on %s, revision %s" % (build_info.build_date, build_info.short_changeset,)
|
||||
LOG.info("Running %s build %s" % (build_info.repo_name, desc))
|
||||
|
||||
return mozlauncher(build_info)
|
||||
|
@ -93,6 +90,7 @@ class ManualTestRunner(TestRunner):
|
|||
A TestRunner subclass that run builds and ask for evaluation by
|
||||
prompting in the terminal.
|
||||
"""
|
||||
|
||||
def __init__(self, launcher_kwargs=None):
|
||||
TestRunner.__init__(self)
|
||||
self.launcher_kwargs = launcher_kwargs or {}
|
||||
|
@ -101,22 +99,22 @@ class ManualTestRunner(TestRunner):
|
|||
"""
|
||||
Ask and returns the verdict.
|
||||
"""
|
||||
options = ['good', 'bad', 'skip', 'retry', 'exit']
|
||||
options = ["good", "bad", "skip", "retry", "exit"]
|
||||
if allow_back:
|
||||
options.insert(-1, 'back')
|
||||
options.insert(-1, "back")
|
||||
# allow user to just type one letter
|
||||
allowed_inputs = options + [o[0] for o in options]
|
||||
# format options to nice printing
|
||||
formatted_options = (', '.join(["'%s'" % o for o in options[:-1]]) +
|
||||
" or '%s'" % options[-1])
|
||||
formatted_options = ", ".join(["'%s'" % o for o in options[:-1]]) + " or '%s'" % options[-1]
|
||||
verdict = ""
|
||||
while verdict not in allowed_inputs:
|
||||
verdict = input("Was this %s build good, bad, or broken?"
|
||||
" (type %s and press Enter): " % (build_info.build_type,
|
||||
formatted_options))
|
||||
verdict = input(
|
||||
"Was this %s build good, bad, or broken?"
|
||||
" (type %s and press Enter): " % (build_info.build_type, formatted_options)
|
||||
)
|
||||
|
||||
if verdict == 'back':
|
||||
return 'back'
|
||||
if verdict == "back":
|
||||
return "back"
|
||||
# shorten verdict to one character for processing...
|
||||
return verdict[0]
|
||||
|
||||
|
@ -149,13 +147,17 @@ class ManualTestRunner(TestRunner):
|
|||
min = -mid + 1
|
||||
max = build_range_len - mid - 2
|
||||
valid_range = list(range(min, max + 1))
|
||||
print("Build was skipped. You can manually choose a new build to"
|
||||
" test, to be able to get out of a broken build range.")
|
||||
print("Please type the index of the build you would like to try - the"
|
||||
" index is 0-based on the middle of the remaining build range.")
|
||||
print(
|
||||
"Build was skipped. You can manually choose a new build to"
|
||||
" test, to be able to get out of a broken build range."
|
||||
)
|
||||
print(
|
||||
"Please type the index of the build you would like to try - the"
|
||||
" index is 0-based on the middle of the remaining build range."
|
||||
)
|
||||
print("You can choose a build index between [%d, %d]:" % (min, max))
|
||||
while True:
|
||||
value = input('> ')
|
||||
value = input("> ")
|
||||
try:
|
||||
index = int(value)
|
||||
if index in valid_range:
|
||||
|
@ -164,9 +166,8 @@ class ManualTestRunner(TestRunner):
|
|||
pass
|
||||
|
||||
|
||||
def _raise_command_error(exc, msg=''):
|
||||
raise TestCommandError("Unable to run the test command%s: `%s`"
|
||||
% (msg, exc))
|
||||
def _raise_command_error(exc, msg=""):
|
||||
raise TestCommandError("Unable to run the test command%s: `%s`" % (msg, exc))
|
||||
|
||||
|
||||
class CommandTestRunner(TestRunner):
|
||||
|
@ -185,6 +186,7 @@ class CommandTestRunner(TestRunner):
|
|||
with curly brackets. Example:
|
||||
`mozmill -app firefox -b {binary} -t path/to/test.js`
|
||||
"""
|
||||
|
||||
def __init__(self, command):
|
||||
TestRunner.__init__(self)
|
||||
self.command = command
|
||||
|
@ -193,29 +195,28 @@ class CommandTestRunner(TestRunner):
|
|||
with create_launcher(build_info) as launcher:
|
||||
build_info.update_from_app_info(launcher.get_app_info())
|
||||
variables = {k: v for k, v in six.iteritems(build_info.to_dict())}
|
||||
if hasattr(launcher, 'binary'):
|
||||
variables['binary'] = launcher.binary
|
||||
if hasattr(launcher, "binary"):
|
||||
variables["binary"] = launcher.binary
|
||||
|
||||
env = dict(os.environ)
|
||||
for k, v in six.iteritems(variables):
|
||||
env['MOZREGRESSION_' + k.upper()] = str(v)
|
||||
env["MOZREGRESSION_" + k.upper()] = str(v)
|
||||
try:
|
||||
command = self.command.format(**variables)
|
||||
except KeyError as exc:
|
||||
_raise_command_error(exc, ' (formatting error)')
|
||||
LOG.info('Running test command: `%s`' % command)
|
||||
_raise_command_error(exc, " (formatting error)")
|
||||
LOG.info("Running test command: `%s`" % command)
|
||||
cmdlist = shlex.split(command)
|
||||
try:
|
||||
retcode = subprocess.call(cmdlist, env=env)
|
||||
except IndexError:
|
||||
_raise_command_error("Empty command")
|
||||
except OSError as exc:
|
||||
_raise_command_error(exc,
|
||||
" (%s not found or not executable)"
|
||||
% cmdlist[0])
|
||||
LOG.info('Test command result: %d (build is %s)'
|
||||
% (retcode, 'good' if retcode == 0 else 'bad'))
|
||||
return 'g' if retcode == 0 else 'b'
|
||||
_raise_command_error(exc, " (%s not found or not executable)" % cmdlist[0])
|
||||
LOG.info(
|
||||
"Test command result: %d (build is %s)" % (retcode, "good" if retcode == 0 else "bad")
|
||||
)
|
||||
return "g" if retcode == 0 else "b"
|
||||
|
||||
def run_once(self, build_info):
|
||||
return 0 if self.evaluate(build_info) == 'g' else 1
|
||||
return 0 if self.evaluate(build_info) == "g" else 1
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
[tool.black]
|
||||
line-length = 100
|
||||
exclude = "gui/mozregui/ui"
|
||||
|
||||
[tool.isort]
|
||||
line_length = 100
|
||||
skip_glob = "**/gui/mozregui/ui/*"
|
||||
default_section = "THIRDPARTY"
|
||||
known_first_party = "mozregression,mozregui"
|
||||
# For compatibility with black:
|
||||
multi_line_output = 3
|
||||
include_trailing_comma = "True"
|
||||
force_grid_wrap = 0
|
||||
use_parentheses = "True"
|
|
@ -0,0 +1,6 @@
|
|||
# all the things (what you usually want when doing development)
|
||||
-r dev.txt
|
||||
-r linters.txt
|
||||
-r build.txt
|
||||
-r gui.txt
|
||||
-e .
|
|
@ -5,6 +5,7 @@ configobj==5.0.6
|
|||
mozdevice==3.1.0
|
||||
mozfile==2.1.0
|
||||
mozinfo==1.2.1
|
||||
mozinstall==2.0.0
|
||||
mozlog==6.0
|
||||
mozprofile==2.5.0
|
||||
mozrunner==7.8.0
|
||||
|
@ -15,16 +16,3 @@ taskcluster==6.0.0
|
|||
|
||||
# for some reason we need to specify a specific version of six
|
||||
six==1.12.0
|
||||
|
||||
# dev dependencies
|
||||
coverage==4.2
|
||||
mock==3.0.5 # last version compatible with python2.7
|
||||
pytest==4.6.9 # last version compatible with python2.7
|
||||
pytest-mock == 2.0.0
|
||||
# peg flake8 + deps so code doesn't suddenly violate validation expectations unexpectedly
|
||||
flake8==3.2.1
|
||||
mccabe==0.5.3
|
||||
pyflakes==1.3.0
|
||||
|
||||
# install mozregression
|
||||
-e .
|
|
@ -0,0 +1,4 @@
|
|||
# *just* the dependencies required for the console version of
|
||||
# mozregression (compatible with older versions of python)
|
||||
-r build.txt
|
||||
-r dev.txt
|
|
@ -0,0 +1,4 @@
|
|||
coverage==4.2
|
||||
mock==3.0.5 # last version compatible with python2.7
|
||||
pytest==4.6.9 # last version compatible with python2.7
|
||||
pytest-mock == 2.0.0
|
|
@ -1,5 +1,3 @@
|
|||
-r requirements-dev.txt
|
||||
|
||||
# Qt bindings for python
|
||||
PySide2==5.14.1
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
# peg flake8 + deps so code doesn't suddenly violate validation expectations unexpectedly
|
||||
flake8==3.7.9
|
||||
flake8-black==0.1.1
|
||||
isort==4.3.21
|
||||
mccabe==0.6.1
|
||||
pyflakes==2.1.1
|
12
setup.cfg
12
setup.cfg
|
@ -1,6 +1,10 @@
|
|||
[flake8]
|
||||
exclude = .git,__pycache__
|
||||
# E129: visually indented line with same indent as next logical line
|
||||
# E501: line too long
|
||||
extend_ignore = E129,E501
|
||||
exclude = .git,__pycache__,vendor,gui/mozregui/ui
|
||||
max-line-length = 100
|
||||
# E121,E123,E126,E226,E24,E704,W503: Ignored in default pycodestyle config:
|
||||
# https://github.com/PyCQA/pycodestyle/blob/2.2.0/pycodestyle.py#L72
|
||||
# Our additions...
|
||||
# E129: visually indented line with same indent as next logical line
|
||||
# E203: pep8 is wrong, overridden by black (https://github.com/psf/black/issues/315)
|
||||
ignore = E121,E123,E126,E129,E203,E226,E24,E704,W503
|
||||
|
||||
|
|
70
setup.py
70
setup.py
|
@ -1,25 +1,29 @@
|
|||
import sys
|
||||
|
||||
from setuptools import setup
|
||||
from mozregression import __version__
|
||||
from setuptools.command.test import test as TestCommand
|
||||
|
||||
from mozregression import __version__
|
||||
|
||||
|
||||
class PyTest(TestCommand):
|
||||
"""
|
||||
Run py.test with the "python setup.py test command"
|
||||
"""
|
||||
user_options = [('pytest-args=', 'a', "Arguments to pass to py.test")]
|
||||
|
||||
user_options = [("pytest-args=", "a", "Arguments to pass to py.test")]
|
||||
|
||||
def initialize_options(self):
|
||||
TestCommand.initialize_options(self)
|
||||
self.pytest_args = ''
|
||||
self.pytest_args = ""
|
||||
|
||||
def finalize_options(self):
|
||||
TestCommand.finalize_options(self)
|
||||
self.pytest_args += (' ' + self.distribution.test_suite)
|
||||
self.pytest_args += " " + self.distribution.test_suite
|
||||
|
||||
def run_tests(self):
|
||||
import pytest
|
||||
|
||||
errno = pytest.main(self.pytest_args)
|
||||
sys.exit(errno)
|
||||
|
||||
|
@ -30,22 +34,22 @@ if sys.version_info < (2, 7) or (sys.version_info >= (3, 0) and sys.version_info
|
|||
# we pin these dependencies in the requirements files -- all of these
|
||||
# should be python 3 compatible
|
||||
DEPENDENCIES = [
|
||||
'beautifulsoup4>=4.7.1',
|
||||
'colorama>=0.4.1',
|
||||
'configobj>=5.0.6',
|
||||
'mozdevice>=3.0.1',
|
||||
'mozfile>=2.0.0',
|
||||
'mozinfo>=1.1.0',
|
||||
'mozinstall>=2.0.0',
|
||||
'mozlog>=4.0',
|
||||
'mozprocess>=1.0.0',
|
||||
'mozprofile>=2.2.0',
|
||||
'mozrunner>=7.4.0',
|
||||
'mozversion>=2.1.0',
|
||||
'redo>=2.0.2',
|
||||
'requests[security]>=2.21.0',
|
||||
'six>=1.12.0',
|
||||
'taskcluster>=6.0.0',
|
||||
"beautifulsoup4>=4.7.1",
|
||||
"colorama>=0.4.1",
|
||||
"configobj>=5.0.6",
|
||||
"mozdevice>=3.0.1",
|
||||
"mozfile>=2.0.0",
|
||||
"mozinfo>=1.1.0",
|
||||
"mozinstall>=2.0.0",
|
||||
"mozlog>=4.0",
|
||||
"mozprocess>=1.0.0",
|
||||
"mozprofile>=2.2.0",
|
||||
"mozrunner>=7.4.0",
|
||||
"mozversion>=2.1.0",
|
||||
"redo>=2.0.2",
|
||||
"requests[security]>=2.21.0",
|
||||
"six>=1.12.0",
|
||||
"taskcluster>=6.0.0",
|
||||
]
|
||||
|
||||
desc = """Regression range finder for Mozilla nightly builds"""
|
||||
|
@ -53,24 +57,26 @@ long_desc = """Regression range finder for Mozilla nightly builds.
|
|||
For more information see the mozregression website:
|
||||
http://mozilla.github.io/mozregression/"""
|
||||
|
||||
setup(name="mozregression",
|
||||
setup(
|
||||
name="mozregression",
|
||||
version=__version__,
|
||||
description=desc,
|
||||
long_description=long_desc,
|
||||
author='Mozilla Automation and Tools Team',
|
||||
author_email='tools@lists.mozilla.org',
|
||||
url='http://github.com/mozilla/mozregression',
|
||||
license='MPL 1.1/GPL 2.0/LGPL 2.1',
|
||||
packages=['mozregression'],
|
||||
author="Mozilla Automation and Tools Team",
|
||||
author_email="tools@lists.mozilla.org",
|
||||
url="http://github.com/mozilla/mozregression",
|
||||
license="MPL 1.1/GPL 2.0/LGPL 2.1",
|
||||
packages=["mozregression"],
|
||||
entry_points="""
|
||||
[console_scripts]
|
||||
mozregression = mozregression.main:main
|
||||
""",
|
||||
platforms=['Any'],
|
||||
platforms=["Any"],
|
||||
install_requires=DEPENDENCIES,
|
||||
classifiers=[
|
||||
'Development Status :: 5 - Production/Stable',
|
||||
'Environment :: Console',
|
||||
'Intended Audience :: Developers',
|
||||
'Operating System :: OS Independent'
|
||||
])
|
||||
"Development Status :: 5 - Production/Stable",
|
||||
"Environment :: Console",
|
||||
"Intended Audience :: Developers",
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
from __future__ import absolute_import
|
||||
|
||||
from mozlog.structured import set_default_logger
|
||||
from mozlog.structured.structuredlog import StructuredLogger
|
||||
|
||||
set_default_logger(StructuredLogger('mozregression.tests.unit'))
|
||||
set_default_logger(StructuredLogger("mozregression.tests.unit"))
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
import pytest
|
||||
|
||||
from mozregression import build_range
|
||||
from mozregression.fetch_build_info import InfoFetcher
|
||||
|
||||
|
||||
class RangeCreator(object):
|
||||
def __init__(self, mocker):
|
||||
self.mocker = mocker
|
||||
|
||||
def create(self, values):
|
||||
info_fetcher = self.mocker.Mock(spec=InfoFetcher)
|
||||
info_fetcher.find_build_info.side_effect = lambda i: i
|
||||
future_build_infos = [build_range.FutureBuildInfo(info_fetcher, v) for v in values]
|
||||
return build_range.BuildRange(info_fetcher, future_build_infos)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def range_creator(mocker):
|
||||
return RangeCreator(mocker)
|
|
@ -1,8 +1,10 @@
|
|||
from __future__ import absolute_import
|
||||
import pytest
|
||||
from .test_build_info import create_build_info
|
||||
|
||||
from mozregression import build_info, approx_persist, build_range
|
||||
import pytest
|
||||
|
||||
from mozregression import approx_persist, build_info, build_range
|
||||
|
||||
from .test_build_info import create_build_info
|
||||
|
||||
|
||||
def create_build_range(values):
|
||||
|
@ -10,47 +12,47 @@ def create_build_range(values):
|
|||
|
||||
data = []
|
||||
for v in values:
|
||||
data.append(build_range.FutureBuildInfo(
|
||||
info_fetcher, v
|
||||
))
|
||||
data.append(build_range.FutureBuildInfo(info_fetcher, v))
|
||||
return build_range.BuildRange(info_fetcher, data)
|
||||
|
||||
|
||||
def build_firefox_name(chset):
|
||||
return ('%s-shippable--mozilla-central--firefox-38.0a1.en-US.linux-x86_64.tar.bz2'
|
||||
% chset)
|
||||
return "%s-shippable--mozilla-central--firefox-38.0a1.en-US.linux-x86_64.tar.bz2" % chset
|
||||
|
||||
|
||||
def build_firefox_names(chsets):
|
||||
return [build_firefox_name(c) for c in chsets]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('bdata, mid, around, fnames, result', [
|
||||
@pytest.mark.parametrize(
|
||||
"bdata, mid, around, fnames, result",
|
||||
[
|
||||
# index is None when there is no files
|
||||
('0123456789', None, 7, [], None),
|
||||
("0123456789", None, 7, [], None),
|
||||
# one file around works
|
||||
('0123456789', None, 7, build_firefox_names('4'), 4),
|
||||
('0123456789', None, 7, build_firefox_names('6'), 6),
|
||||
("0123456789", None, 7, build_firefox_names("4"), 4),
|
||||
("0123456789", None, 7, build_firefox_names("6"), 6),
|
||||
# with 10 builds, two files around returns None
|
||||
('0123456789', None, 7, build_firefox_names('123789'), None),
|
||||
("0123456789", None, 7, build_firefox_names("123789"), None),
|
||||
# same with 13
|
||||
('0123456789abc', None, 7, build_firefox_names('8'), None),
|
||||
("0123456789abc", None, 7, build_firefox_names("8"), None),
|
||||
# but 14 will give a result
|
||||
('0123456789abcd', None, 7, build_firefox_names('8'), 8),
|
||||
("0123456789abcd", None, 7, build_firefox_names("8"), 8),
|
||||
# we never overflow
|
||||
('0123456789', 8, 4, [], None),
|
||||
('0123456789', 1, 4, [], None),
|
||||
("0123456789", 8, 4, [], None),
|
||||
("0123456789", 1, 4, [], None),
|
||||
# it is possible that someone chooses '1' after a skip.
|
||||
# in that case, we should not offer the build '0' (the first)
|
||||
('0123456789', 1, 7, build_firefox_names('0'), None),
|
||||
("0123456789", 1, 7, build_firefox_names("0"), None),
|
||||
# same thing with '8' (here, we do not provide the last)
|
||||
('0123456789', 8, 7, build_firefox_names('9'), None),
|
||||
("0123456789", 8, 7, build_firefox_names("9"), None),
|
||||
# though if it is neither the last or the first, we use it
|
||||
('0123456789', 1, 7, build_firefox_names('2'), 2),
|
||||
('0123456789', 2, 7, build_firefox_names('1'), 1),
|
||||
('0123456789', 8, 7, build_firefox_names('7'), 7),
|
||||
('0123456789', 7, 7, build_firefox_names('8'), 8),
|
||||
])
|
||||
("0123456789", 1, 7, build_firefox_names("2"), 2),
|
||||
("0123456789", 2, 7, build_firefox_names("1"), 1),
|
||||
("0123456789", 8, 7, build_firefox_names("7"), 7),
|
||||
("0123456789", 7, 7, build_firefox_names("8"), 8),
|
||||
],
|
||||
)
|
||||
def test_approx_index(bdata, mid, around, fnames, result):
|
||||
# this is always a firefox 64 linux build info
|
||||
binfo = create_build_info(build_info.IntegrationBuildInfo)
|
||||
|
|
|
@ -3,16 +3,23 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import absolute_import
|
||||
import unittest
|
||||
from mock import patch, Mock, call, MagicMock
|
||||
import datetime
|
||||
|
||||
from mozregression.bisector import (NightlyHandler, IntegrationHandler, Bisector,
|
||||
Bisection, BisectorHandler)
|
||||
from mozregression import build_range
|
||||
from mozregression.errors import LauncherError, MozRegressionError
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
from mock import MagicMock, Mock, call, patch
|
||||
from six.moves import range
|
||||
|
||||
from mozregression import build_range
|
||||
from mozregression.bisector import (
|
||||
Bisection,
|
||||
Bisector,
|
||||
BisectorHandler,
|
||||
IntegrationHandler,
|
||||
NightlyHandler,
|
||||
)
|
||||
from mozregression.errors import LauncherError, MozRegressionError
|
||||
|
||||
|
||||
class MockBisectorHandler(BisectorHandler):
|
||||
def _print_progress(self, new_data):
|
||||
|
@ -22,41 +29,38 @@ class MockBisectorHandler(BisectorHandler):
|
|||
class TestBisectorHandler(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.handler = MockBisectorHandler()
|
||||
self.handler.set_build_range([
|
||||
{'build_url': 'http://build_url_0', 'repository': 'my'}
|
||||
])
|
||||
self.handler.set_build_range([{"build_url": "http://build_url_0", "repository": "my"}])
|
||||
|
||||
def test_initialize(self):
|
||||
self.handler.set_build_range([
|
||||
Mock(changeset='1', repo_url='my'),
|
||||
Mock(),
|
||||
Mock(changeset='3', repo_url='my'),
|
||||
])
|
||||
self.handler.set_build_range(
|
||||
[Mock(changeset="1", repo_url="my"), Mock(), Mock(changeset="3", repo_url="my")]
|
||||
)
|
||||
self.handler.initialize()
|
||||
self.assertEqual(self.handler.found_repo, 'my')
|
||||
self.assertEqual(self.handler.good_revision, '1')
|
||||
self.assertEqual(self.handler.bad_revision, '3')
|
||||
self.assertEqual(self.handler.found_repo, "my")
|
||||
self.assertEqual(self.handler.good_revision, "1")
|
||||
self.assertEqual(self.handler.bad_revision, "3")
|
||||
|
||||
def test_get_pushlog_url(self):
|
||||
self.handler.found_repo = 'https://hg.mozilla.repo'
|
||||
self.handler.good_revision = '2'
|
||||
self.handler.bad_revision = '6'
|
||||
self.handler.found_repo = "https://hg.mozilla.repo"
|
||||
self.handler.good_revision = "2"
|
||||
self.handler.bad_revision = "6"
|
||||
self.assertEqual(
|
||||
self.handler.get_pushlog_url(),
|
||||
"https://hg.mozilla.repo/pushloghtml?fromchange=2&tochange=6")
|
||||
"https://hg.mozilla.repo/pushloghtml?fromchange=2&tochange=6",
|
||||
)
|
||||
|
||||
def test_get_pushlog_url_same_chsets(self):
|
||||
self.handler.found_repo = 'https://hg.mozilla.repo'
|
||||
self.handler.good_revision = self.handler.bad_revision = '2'
|
||||
self.handler.found_repo = "https://hg.mozilla.repo"
|
||||
self.handler.good_revision = self.handler.bad_revision = "2"
|
||||
self.assertEqual(
|
||||
self.handler.get_pushlog_url(),
|
||||
"https://hg.mozilla.repo/pushloghtml?changeset=2")
|
||||
self.handler.get_pushlog_url(), "https://hg.mozilla.repo/pushloghtml?changeset=2",
|
||||
)
|
||||
|
||||
@patch('mozregression.bisector.LOG')
|
||||
@patch("mozregression.bisector.LOG")
|
||||
def test_print_range(self, logger):
|
||||
self.handler.found_repo = 'https://hg.mozilla.repo'
|
||||
self.handler.good_revision = '2'
|
||||
self.handler.bad_revision = '6'
|
||||
self.handler.found_repo = "https://hg.mozilla.repo"
|
||||
self.handler.good_revision = "2"
|
||||
self.handler.bad_revision = "6"
|
||||
log = []
|
||||
logger.info = log.append
|
||||
|
||||
|
@ -65,31 +69,28 @@ class TestBisectorHandler(unittest.TestCase):
|
|||
self.assertEqual(log[1], "First bad revision: 6")
|
||||
self.assertIn(self.handler.get_pushlog_url(), log[2])
|
||||
|
||||
@patch('tests.unit.test_bisector.MockBisectorHandler._print_progress')
|
||||
@patch("tests.unit.test_bisector.MockBisectorHandler._print_progress")
|
||||
def test_build_good(self, _print_progress):
|
||||
self.handler.build_good(0, [{"changeset": '123'},
|
||||
{"changeset": '456'}])
|
||||
_print_progress.assert_called_with([{"changeset": '123'},
|
||||
{"changeset": '456'}])
|
||||
self.handler.build_good(0, [{"changeset": "123"}, {"changeset": "456"}])
|
||||
_print_progress.assert_called_with([{"changeset": "123"}, {"changeset": "456"}])
|
||||
|
||||
@patch('tests.unit.test_bisector.MockBisectorHandler._print_progress')
|
||||
@patch("tests.unit.test_bisector.MockBisectorHandler._print_progress")
|
||||
def test_build_bad(self, _print_progress):
|
||||
# with at least two, _print_progress will be called
|
||||
self.handler.build_bad(0, [{"changeset": '123'}, {"changeset": '456'}])
|
||||
_print_progress.assert_called_with([{"changeset": '123'},
|
||||
{"changeset": '456'}])
|
||||
self.handler.build_bad(0, [{"changeset": "123"}, {"changeset": "456"}])
|
||||
_print_progress.assert_called_with([{"changeset": "123"}, {"changeset": "456"}])
|
||||
|
||||
|
||||
class TestNightlyHandler(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.handler = NightlyHandler()
|
||||
|
||||
@patch('mozregression.bisector.BisectorHandler.initialize')
|
||||
@patch("mozregression.bisector.BisectorHandler.initialize")
|
||||
def test_initialize(self, initialize):
|
||||
def get_associated_data(index):
|
||||
return index
|
||||
self.handler.build_range = [Mock(build_date=0),
|
||||
Mock(build_date=1)]
|
||||
|
||||
self.handler.build_range = [Mock(build_date=0), Mock(build_date=1)]
|
||||
self.handler.initialize()
|
||||
# check that members are set
|
||||
self.assertEqual(self.handler.good_date, 0)
|
||||
|
@ -97,7 +98,7 @@ class TestNightlyHandler(unittest.TestCase):
|
|||
|
||||
initialize.assert_called_with(self.handler)
|
||||
|
||||
@patch('mozregression.bisector.LOG')
|
||||
@patch("mozregression.bisector.LOG")
|
||||
def test_print_progress(self, logger):
|
||||
log = []
|
||||
logger.info = log.append
|
||||
|
@ -106,25 +107,25 @@ class TestNightlyHandler(unittest.TestCase):
|
|||
|
||||
new_data = [
|
||||
Mock(build_date=datetime.date(2014, 11, 15)),
|
||||
Mock(build_date=datetime.date(2014, 11, 20))
|
||||
Mock(build_date=datetime.date(2014, 11, 20)),
|
||||
]
|
||||
|
||||
self.handler._print_progress(new_data)
|
||||
self.assertIn('from [2014-11-10, 2014-11-20] (10 days)', log[0])
|
||||
self.assertIn('to [2014-11-15, 2014-11-20] (5 days)', log[0])
|
||||
self.assertIn('2 steps left', log[0])
|
||||
self.assertIn("from [2014-11-10, 2014-11-20] (10 days)", log[0])
|
||||
self.assertIn("to [2014-11-15, 2014-11-20] (5 days)", log[0])
|
||||
self.assertIn("2 steps left", log[0])
|
||||
|
||||
@patch('mozregression.bisector.LOG')
|
||||
@patch("mozregression.bisector.LOG")
|
||||
def test_user_exit(self, logger):
|
||||
log = []
|
||||
logger.info = log.append
|
||||
self.handler.good_date = datetime.date(2014, 11, 10)
|
||||
self.handler.bad_date = datetime.date(2014, 11, 20)
|
||||
self.handler.user_exit(0)
|
||||
self.assertEqual('Newest known good nightly: 2014-11-10', log[0])
|
||||
self.assertEqual('Oldest known bad nightly: 2014-11-20', log[1])
|
||||
self.assertEqual("Newest known good nightly: 2014-11-10", log[0])
|
||||
self.assertEqual("Oldest known bad nightly: 2014-11-20", log[1])
|
||||
|
||||
@patch('mozregression.bisector.LOG')
|
||||
@patch("mozregression.bisector.LOG")
|
||||
def test_print_range_without_repo(self, logger):
|
||||
log = []
|
||||
logger.info = log.append
|
||||
|
@ -133,14 +134,14 @@ class TestNightlyHandler(unittest.TestCase):
|
|||
self.handler.bad_date = datetime.date(2014, 11, 20)
|
||||
self.handler.print_range()
|
||||
self.assertIn("no pushlog url available", log[0])
|
||||
self.assertEqual('Newest known good nightly: 2014-11-10', log[1])
|
||||
self.assertEqual('Oldest known bad nightly: 2014-11-20', log[2])
|
||||
self.assertEqual("Newest known good nightly: 2014-11-10", log[1])
|
||||
self.assertEqual("Oldest known bad nightly: 2014-11-20", log[2])
|
||||
|
||||
@patch('mozregression.bisector.LOG')
|
||||
@patch("mozregression.bisector.LOG")
|
||||
def test_print_range_rev_availables(self, logger):
|
||||
self.handler.found_repo = 'https://hg.mozilla.repo'
|
||||
self.handler.good_revision = '2'
|
||||
self.handler.bad_revision = '6'
|
||||
self.handler.found_repo = "https://hg.mozilla.repo"
|
||||
self.handler.good_revision = "2"
|
||||
self.handler.bad_revision = "6"
|
||||
self.handler.good_date = datetime.date(2015, 1, 1)
|
||||
self.handler.bad_date = datetime.date(2015, 1, 2)
|
||||
log = []
|
||||
|
@ -151,89 +152,81 @@ class TestNightlyHandler(unittest.TestCase):
|
|||
self.assertEqual(log[1], "First bad revision: 6 (2015-01-02)")
|
||||
self.assertIn(self.handler.get_pushlog_url(), log[2])
|
||||
|
||||
@patch('mozregression.bisector.LOG')
|
||||
@patch("mozregression.bisector.LOG")
|
||||
def test_print_range_no_rev_availables(self, logger):
|
||||
self.handler.found_repo = 'https://hg.mozilla.repo'
|
||||
self.handler.found_repo = "https://hg.mozilla.repo"
|
||||
self.handler.good_date = datetime.date(2014, 11, 10)
|
||||
self.handler.bad_date = datetime.date(2014, 11, 20)
|
||||
log = []
|
||||
logger.info = log.append
|
||||
|
||||
self.handler.print_range()
|
||||
self.assertEqual('Newest known good nightly: 2014-11-10', log[0])
|
||||
self.assertEqual('Oldest known bad nightly: 2014-11-20', log[1])
|
||||
self.assertIn("pushloghtml?startdate=2014-11-10&enddate=2014-11-20",
|
||||
log[2])
|
||||
self.assertEqual("Newest known good nightly: 2014-11-10", log[0])
|
||||
self.assertEqual("Oldest known bad nightly: 2014-11-20", log[1])
|
||||
self.assertIn("pushloghtml?startdate=2014-11-10&enddate=2014-11-20", log[2])
|
||||
|
||||
|
||||
class TestIntegrationHandler(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.handler = IntegrationHandler()
|
||||
|
||||
@patch('mozregression.bisector.LOG')
|
||||
@patch("mozregression.bisector.LOG")
|
||||
def test_print_progress(self, logger):
|
||||
log = []
|
||||
logger.info = log.append
|
||||
self.handler.set_build_range([
|
||||
Mock(short_changeset='12'),
|
||||
Mock(short_changeset='123'),
|
||||
Mock(short_changeset='1234'),
|
||||
Mock(short_changeset='12345'),
|
||||
])
|
||||
new_data = [
|
||||
Mock(short_changeset='1234'),
|
||||
Mock(short_changeset='12345')
|
||||
self.handler.set_build_range(
|
||||
[
|
||||
Mock(short_changeset="12"),
|
||||
Mock(short_changeset="123"),
|
||||
Mock(short_changeset="1234"),
|
||||
Mock(short_changeset="12345"),
|
||||
]
|
||||
)
|
||||
new_data = [Mock(short_changeset="1234"), Mock(short_changeset="12345")]
|
||||
|
||||
self.handler._print_progress(new_data)
|
||||
self.assertIn('from [12, 12345] (4 builds)', log[0])
|
||||
self.assertIn('to [1234, 12345] (2 builds)', log[0])
|
||||
self.assertIn('1 steps left', log[0])
|
||||
self.assertIn("from [12, 12345] (4 builds)", log[0])
|
||||
self.assertIn("to [1234, 12345] (2 builds)", log[0])
|
||||
self.assertIn("1 steps left", log[0])
|
||||
|
||||
@patch('mozregression.bisector.LOG')
|
||||
@patch("mozregression.bisector.LOG")
|
||||
def test_user_exit(self, logger):
|
||||
log = []
|
||||
logger.info = log.append
|
||||
self.handler.good_revision = '3'
|
||||
self.handler.bad_revision = '1'
|
||||
self.handler.good_revision = "3"
|
||||
self.handler.bad_revision = "1"
|
||||
self.handler.user_exit(0)
|
||||
self.assertEqual('Newest known good integration revision: 3', log[0])
|
||||
self.assertEqual('Oldest known bad integration revision: 1', log[1])
|
||||
self.assertEqual("Newest known good integration revision: 3", log[0])
|
||||
self.assertEqual("Oldest known bad integration revision: 1", log[1])
|
||||
|
||||
|
||||
class MyBuildData(build_range.BuildRange):
|
||||
def __init__(self, data=()):
|
||||
|
||||
class FutureBuildInfo(build_range.FutureBuildInfo):
|
||||
def __init__(self, *a, **kwa):
|
||||
build_range.FutureBuildInfo.__init__(self, *a, **kwa)
|
||||
self._build_info = Mock(data=self.data)
|
||||
|
||||
build_range.BuildRange.__init__(
|
||||
self,
|
||||
None,
|
||||
[FutureBuildInfo(None, v) for v in data]
|
||||
)
|
||||
build_range.BuildRange.__init__(self, None, [FutureBuildInfo(None, v) for v in data])
|
||||
|
||||
def __repr__(self):
|
||||
return repr([s.build_info.data for s in self._future_build_infos])
|
||||
|
||||
def __eq__(self, other):
|
||||
return [s.build_info.data for s in self._future_build_infos] == \
|
||||
[s.build_info.data for s in other._future_build_infos]
|
||||
return [s.build_info.data for s in self._future_build_infos] == [
|
||||
s.build_info.data for s in other._future_build_infos
|
||||
]
|
||||
|
||||
|
||||
class TestBisector(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.handler = MagicMock(find_fix=False, ensure_good_and_bad=False)
|
||||
self.test_runner = Mock()
|
||||
self.bisector = Bisector(Mock(), self.test_runner,
|
||||
Mock(),
|
||||
dl_in_background=False)
|
||||
self.bisector = Bisector(Mock(), self.test_runner, Mock(), dl_in_background=False)
|
||||
self.bisector.download_background = False
|
||||
|
||||
# shim for py2.7
|
||||
if not hasattr(self, 'assertRaisesRegex'):
|
||||
if not hasattr(self, "assertRaisesRegex"):
|
||||
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||
|
||||
def test__bisect_no_data(self):
|
||||
|
@ -262,148 +255,150 @@ class TestBisector(unittest.TestCase):
|
|||
if isinstance(verdict, Exception):
|
||||
raise verdict
|
||||
return verdict
|
||||
|
||||
self.test_runner.evaluate = Mock(side_effect=evaluate)
|
||||
result = self.bisector._bisect(self.handler, build_range)
|
||||
return {
|
||||
'result': result,
|
||||
"result": result,
|
||||
}
|
||||
|
||||
def test_ensure_good_bad_invalid(self):
|
||||
self.handler.ensure_good_and_bad = True
|
||||
with self.assertRaisesRegex(MozRegressionError,
|
||||
"expected to be good"):
|
||||
self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ['b'])
|
||||
with self.assertRaisesRegex(MozRegressionError, "expected to be good"):
|
||||
self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ["b"])
|
||||
|
||||
with self.assertRaisesRegex(MozRegressionError,
|
||||
"expected to be bad"):
|
||||
self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ['g', 'g'])
|
||||
with self.assertRaisesRegex(MozRegressionError, "expected to be bad"):
|
||||
self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ["g", "g"])
|
||||
|
||||
def test_ensure_good_bad(self):
|
||||
self.handler.ensure_good_and_bad = True
|
||||
data = MyBuildData([1, 2, 3, 4, 5])
|
||||
self.do__bisect(data,
|
||||
['s', 'r', 'g', 'b', 'e'])
|
||||
self.test_runner.evaluate.assert_has_calls([
|
||||
self.do__bisect(data, ["s", "r", "g", "b", "e"])
|
||||
self.test_runner.evaluate.assert_has_calls(
|
||||
[
|
||||
call(data[0]), # tested good (then skip)
|
||||
call(data[0]), # tested good (then retry)
|
||||
call(data[0]), # tested good
|
||||
call(data[-1]), # tested bad
|
||||
])
|
||||
self.assertEqual(
|
||||
self.bisector.download_manager.download_in_background.call_count,
|
||||
0
|
||||
]
|
||||
)
|
||||
self.assertEqual(self.bisector.download_manager.download_in_background.call_count, 0)
|
||||
|
||||
def test_ensure_good_bad_with_bg_dl(self):
|
||||
self.handler.ensure_good_and_bad = True
|
||||
self.bisector.dl_in_background = True
|
||||
data = MyBuildData([1, 2, 3, 4, 5])
|
||||
self.do__bisect(data,
|
||||
['s', 'r', 'g', 'e'])
|
||||
self.test_runner.evaluate.assert_has_calls([
|
||||
self.do__bisect(data, ["s", "r", "g", "e"])
|
||||
self.test_runner.evaluate.assert_has_calls(
|
||||
[
|
||||
call(data[0]), # tested good (then skip)
|
||||
call(data[0]), # tested good (then retry)
|
||||
call(data[0]), # tested good
|
||||
call(data[-1]), # tested bad
|
||||
])
|
||||
]
|
||||
)
|
||||
self.bisector.download_manager.download_in_background.assert_has_calls(
|
||||
[call(data[-1]), # bad in backgound
|
||||
call(data[data.mid_point()])] # and mid build
|
||||
[call(data[-1]), call(data[data.mid_point()])] # bad in backgound # and mid build
|
||||
)
|
||||
|
||||
def test_ensure_good_bad_with_find_fix(self):
|
||||
self.handler.ensure_good_and_bad = True
|
||||
self.handler.find_fix = True
|
||||
data = MyBuildData([1, 2, 3, 4, 5])
|
||||
self.do__bisect(data, ['g', 'e'])
|
||||
self.test_runner.evaluate.assert_has_calls([
|
||||
call(data[-1]), # tested good (then skip)
|
||||
call(data[0]), # tested bad
|
||||
])
|
||||
self.do__bisect(data, ["g", "e"])
|
||||
self.test_runner.evaluate.assert_has_calls(
|
||||
[call(data[-1]), call(data[0])] # tested good (then skip) # tested bad
|
||||
)
|
||||
|
||||
def test__bisect_case1(self):
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ['g', 'b'])
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ["g", "b"])
|
||||
# check that set_build_range was called
|
||||
self.handler.set_build_range.assert_has_calls([
|
||||
self.handler.set_build_range.assert_has_calls(
|
||||
[
|
||||
# first call
|
||||
call(MyBuildData([1, 2, 3, 4, 5])),
|
||||
# we answered good
|
||||
call(MyBuildData([3, 4, 5])),
|
||||
# we answered bad
|
||||
call(MyBuildData([3, 4])),
|
||||
])
|
||||
]
|
||||
)
|
||||
# ensure that we called the handler's methods
|
||||
self.handler.initialize.assert_called_with()
|
||||
self.handler.build_good.assert_called_with(2, MyBuildData([3, 4, 5]))
|
||||
self.handler.build_bad.assert_called_with(1, MyBuildData([3, 4]))
|
||||
self.assertTrue(self.handler.build_range.ensure_limits_called)
|
||||
# bisection is finished
|
||||
self.assertEqual(test_result['result'], Bisection.FINISHED)
|
||||
self.assertEqual(test_result["result"], Bisection.FINISHED)
|
||||
|
||||
def test__bisect_with_launcher_exception(self):
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3, 4, 5]),
|
||||
['g', LauncherError("err")])
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ["g", LauncherError("err")])
|
||||
# check that set_build_range was called
|
||||
self.handler.set_build_range.assert_has_calls([
|
||||
self.handler.set_build_range.assert_has_calls(
|
||||
[
|
||||
# first call
|
||||
call(MyBuildData([1, 2, 3, 4, 5])),
|
||||
# we answered good
|
||||
call(MyBuildData([3, 4, 5])),
|
||||
# launcher exception, equivalent to a skip
|
||||
call(MyBuildData([3, 5])),
|
||||
])
|
||||
]
|
||||
)
|
||||
# ensure that we called the handler's methods
|
||||
self.handler.initialize.assert_called_with()
|
||||
self.handler.build_good.assert_called_with(2, MyBuildData([3, 4, 5]))
|
||||
self.handler.build_skip.assert_called_with(1)
|
||||
self.assertTrue(self.handler.build_range.ensure_limits_called)
|
||||
# bisection is finished
|
||||
self.assertEqual(test_result['result'], Bisection.FINISHED)
|
||||
self.assertEqual(test_result["result"], Bisection.FINISHED)
|
||||
|
||||
def test__bisect_case1_hunt_fix(self):
|
||||
self.handler.find_fix = True
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ['g', 'b'])
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ["g", "b"])
|
||||
# check that set_build_range was called
|
||||
self.handler.set_build_range.assert_has_calls([
|
||||
self.handler.set_build_range.assert_has_calls(
|
||||
[
|
||||
# first call
|
||||
call(MyBuildData([1, 2, 3, 4, 5])),
|
||||
# we answered good
|
||||
call(MyBuildData([1, 2, 3])),
|
||||
# we answered bad
|
||||
call(MyBuildData([2, 3])),
|
||||
])
|
||||
]
|
||||
)
|
||||
# ensure that we called the handler's methods
|
||||
self.assertEqual(self.handler.initialize.mock_calls, [call()] * 3)
|
||||
self.handler.build_good. \
|
||||
assert_called_once_with(2, MyBuildData([1, 2, 3]))
|
||||
self.handler.build_good.assert_called_once_with(2, MyBuildData([1, 2, 3]))
|
||||
self.handler.build_bad.assert_called_once_with(1, MyBuildData([2, 3]))
|
||||
# bisection is finished
|
||||
self.assertEqual(test_result['result'], Bisection.FINISHED)
|
||||
self.assertEqual(test_result["result"], Bisection.FINISHED)
|
||||
|
||||
def test__bisect_case2(self):
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3]), ['r', 's'])
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3]), ["r", "s"])
|
||||
# check that set_build_range was called
|
||||
self.handler.set_build_range.assert_has_calls([
|
||||
self.handler.set_build_range.assert_has_calls(
|
||||
[
|
||||
# first call
|
||||
call(MyBuildData([1, 2, 3])),
|
||||
# we asked for a retry
|
||||
call(MyBuildData([1, 2, 3])),
|
||||
# we skipped one
|
||||
call(MyBuildData([1, 3])),
|
||||
])
|
||||
]
|
||||
)
|
||||
# ensure that we called the handler's methods
|
||||
self.handler.initialize.assert_called_with()
|
||||
self.handler.build_retry.assert_called_with(1)
|
||||
self.handler.build_skip.assert_called_with(1)
|
||||
self.assertTrue(self.handler.build_range.ensure_limits_called)
|
||||
# bisection is finished
|
||||
self.assertEqual(test_result['result'], Bisection.FINISHED)
|
||||
self.assertEqual(test_result["result"], Bisection.FINISHED)
|
||||
|
||||
def test__bisect_with_back(self):
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3, 4, 5]),
|
||||
['g', 'back', 'b', 'g'])
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ["g", "back", "b", "g"])
|
||||
# check that set_build_range was called
|
||||
self.handler.set_build_range.assert_has_calls([
|
||||
self.handler.set_build_range.assert_has_calls(
|
||||
[
|
||||
# first call
|
||||
call(MyBuildData([1, 2, 3, 4, 5])),
|
||||
# we answered good
|
||||
|
@ -414,62 +409,62 @@ class TestBisector(unittest.TestCase):
|
|||
call(MyBuildData([1, 2, 3])),
|
||||
# then good
|
||||
call(MyBuildData([2, 3])),
|
||||
])
|
||||
]
|
||||
)
|
||||
# bisection is finished
|
||||
self.assertEqual(test_result['result'], Bisection.FINISHED)
|
||||
self.assertEqual(test_result["result"], Bisection.FINISHED)
|
||||
|
||||
def test__bisect_user_exit(self):
|
||||
test_result = self.do__bisect(MyBuildData(list(range(20))), ['e'])
|
||||
test_result = self.do__bisect(MyBuildData(list(range(20))), ["e"])
|
||||
# check that set_build_range was called
|
||||
self.handler.set_build_range.\
|
||||
assert_has_calls([call(MyBuildData(list(range(20))))])
|
||||
self.handler.set_build_range.assert_has_calls([call(MyBuildData(list(range(20))))])
|
||||
# ensure that we called the handler's method
|
||||
self.handler.initialize.assert_called_once_with()
|
||||
self.handler.user_exit.assert_called_with(10)
|
||||
# user exit
|
||||
self.assertEqual(test_result['result'], Bisection.USER_EXIT)
|
||||
self.assertEqual(test_result["result"], Bisection.USER_EXIT)
|
||||
|
||||
def test__bisect_with_background_download(self):
|
||||
self.bisector.dl_in_background = True
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ['g', 'b'])
|
||||
test_result = self.do__bisect(MyBuildData([1, 2, 3, 4, 5]), ["g", "b"])
|
||||
# check that set_build_range was called
|
||||
self.handler.set_build_range.assert_has_calls([
|
||||
self.handler.set_build_range.assert_has_calls(
|
||||
[
|
||||
call(MyBuildData([1, 2, 3, 4, 5])), # first call
|
||||
call(MyBuildData([3, 4, 5])), # we answered good
|
||||
call(MyBuildData([3, 4])) # we answered bad
|
||||
])
|
||||
call(MyBuildData([3, 4])), # we answered bad
|
||||
]
|
||||
)
|
||||
# ensure that we called the handler's methods
|
||||
self.handler.initialize.assert_called_with()
|
||||
self.handler.build_good.assert_called_with(2, MyBuildData([3, 4, 5]))
|
||||
self.handler.build_bad.assert_called_with(1, MyBuildData([3, 4]))
|
||||
self.assertTrue(self.handler.build_range.ensure_limits_called)
|
||||
# bisection is finished
|
||||
self.assertEqual(test_result['result'], Bisection.FINISHED)
|
||||
self.assertEqual(test_result["result"], Bisection.FINISHED)
|
||||
|
||||
@patch('mozregression.bisector.Bisector._bisect')
|
||||
@patch("mozregression.bisector.Bisector._bisect")
|
||||
def test_bisect(self, _bisect):
|
||||
_bisect.return_value = 1
|
||||
build_range = Mock()
|
||||
create_range = Mock(return_value=build_range)
|
||||
self.handler.create_range = create_range
|
||||
result = self.bisector.bisect(self.handler, 'g', 'b', s=1)
|
||||
create_range.assert_called_with(self.bisector.fetch_config,
|
||||
'g', 'b', s=1)
|
||||
result = self.bisector.bisect(self.handler, "g", "b", s=1)
|
||||
create_range.assert_called_with(self.bisector.fetch_config, "g", "b", s=1)
|
||||
self.assertFalse(build_range.reverse.called)
|
||||
_bisect.assert_called_with(self.handler, build_range)
|
||||
self.assertEqual(result, 1)
|
||||
|
||||
@patch('mozregression.bisector.Bisector._bisect')
|
||||
@patch("mozregression.bisector.Bisector._bisect")
|
||||
def test_bisect_reverse(self, _bisect):
|
||||
build_range = Mock()
|
||||
create_range = Mock(return_value=build_range)
|
||||
self.handler.create_range = create_range
|
||||
self.handler.find_fix = True
|
||||
self.bisector.bisect(self.handler, 'g', 'b', s=1)
|
||||
create_range.assert_called_with(self.bisector.fetch_config,
|
||||
'b', 'g', s=1)
|
||||
self.bisector.bisect(self.handler, "g", "b", s=1)
|
||||
create_range.assert_called_with(self.bisector.fetch_config, "b", "g", s=1)
|
||||
_bisect.assert_called_with(self.handler, build_range)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -1,37 +1,45 @@
|
|||
from __future__ import absolute_import
|
||||
from mozregression import branches, errors
|
||||
|
||||
import pytest
|
||||
|
||||
from mozregression import branches, errors
|
||||
|
||||
@pytest.mark.parametrize('branch,alias', [
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"branch,alias",
|
||||
[
|
||||
("mozilla-inbound", "mozilla-inbound"),
|
||||
("mozilla-inbound", "m-i"),
|
||||
("mozilla-central", "mozilla-central"),
|
||||
("mozilla-central", "m-c"),
|
||||
("unknown", "unknown")
|
||||
])
|
||||
("unknown", "unknown"),
|
||||
],
|
||||
)
|
||||
def test_branch_name(branch, alias):
|
||||
assert branch == branches.get_name(alias)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('branch,url', [
|
||||
("m-c",
|
||||
"https://hg.mozilla.org/mozilla-central"),
|
||||
("m-i",
|
||||
"https://hg.mozilla.org/integration/mozilla-inbound"),
|
||||
("mozilla-beta",
|
||||
"https://hg.mozilla.org/releases/mozilla-beta")
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"branch,url",
|
||||
[
|
||||
("m-c", "https://hg.mozilla.org/mozilla-central"),
|
||||
("m-i", "https://hg.mozilla.org/integration/mozilla-inbound"),
|
||||
("mozilla-beta", "https://hg.mozilla.org/releases/mozilla-beta"),
|
||||
],
|
||||
)
|
||||
def test_get_urls(branch, url):
|
||||
assert branches.get_url(branch) == url
|
||||
|
||||
|
||||
@pytest.mark.parametrize('category,present,not_present', [
|
||||
@pytest.mark.parametrize(
|
||||
"category,present,not_present",
|
||||
[
|
||||
# no category, list all branches but not aliases
|
||||
(None, ['mozilla-central', 'mozilla-inbound'], ['m-c', 'm-i']),
|
||||
(None, ["mozilla-central", "mozilla-inbound"], ["m-c", "m-i"]),
|
||||
# specific category list only branches under that category
|
||||
('integration', ['mozilla-inbound'], ['m-i', 'mozilla-central']),
|
||||
])
|
||||
("integration", ["mozilla-inbound"], ["m-i", "mozilla-central"]),
|
||||
],
|
||||
)
|
||||
def test_get_branches(category, present, not_present):
|
||||
names = branches.get_branches(category=category)
|
||||
for name in present:
|
||||
|
@ -45,32 +53,32 @@ def test_get_url_unknown_branch():
|
|||
branches.get_url("unknown branch")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, expected', [
|
||||
('mozilla-central', 'default'),
|
||||
('autoland', 'integration'),
|
||||
('m-i', 'integration'),
|
||||
('release', 'releases'),
|
||||
('mozilla-beta', 'releases'),
|
||||
('', None),
|
||||
(None, None)
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"name, expected",
|
||||
[
|
||||
("mozilla-central", "default"),
|
||||
("autoland", "integration"),
|
||||
("m-i", "integration"),
|
||||
("release", "releases"),
|
||||
("mozilla-beta", "releases"),
|
||||
("", None),
|
||||
(None, None),
|
||||
],
|
||||
)
|
||||
def test_get_category(name, expected):
|
||||
assert branches.get_category(name) == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize('commit, branch, current', [
|
||||
("Merge mozilla-central to autoland",
|
||||
"mozilla-central", "autoland"),
|
||||
("Merge mozilla-central to autoland",
|
||||
"autoland", "mozilla-central"),
|
||||
("Merge autoland to central, a=merge",
|
||||
"autoland", "mozilla-central"),
|
||||
("merge autoland to mozilla-central a=merge",
|
||||
"autoland", "mozilla-central"),
|
||||
("Merge m-i to m-c, a=merge CLOSED TREE",
|
||||
"mozilla-inbound", "mozilla-central"),
|
||||
("Merge mozilla inbound to central a=merge",
|
||||
"mozilla-inbound", "mozilla-central"),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"commit, branch, current",
|
||||
[
|
||||
("Merge mozilla-central to autoland", "mozilla-central", "autoland"),
|
||||
("Merge mozilla-central to autoland", "autoland", "mozilla-central"),
|
||||
("Merge autoland to central, a=merge", "autoland", "mozilla-central"),
|
||||
("merge autoland to mozilla-central a=merge", "autoland", "mozilla-central"),
|
||||
("Merge m-i to m-c, a=merge CLOSED TREE", "mozilla-inbound", "mozilla-central"),
|
||||
("Merge mozilla inbound to central a=merge", "mozilla-inbound", "mozilla-central",),
|
||||
],
|
||||
)
|
||||
def test_find_branch_in_merge_commit(commit, branch, current):
|
||||
assert branches.find_branch_in_merge_commit(commit, current) == branch
|
||||
|
|
|
@ -1,18 +1,20 @@
|
|||
from __future__ import absolute_import
|
||||
import pytest
|
||||
|
||||
from datetime import date, datetime
|
||||
|
||||
from mozregression.fetch_configs import create_config
|
||||
import pytest
|
||||
|
||||
from mozregression import build_info
|
||||
from mozregression.fetch_configs import create_config
|
||||
|
||||
|
||||
def create_build_info(klass, **attrs):
|
||||
defaults = dict(
|
||||
fetch_config=create_config('firefox', 'linux', 64, 'x86_64'),
|
||||
build_url='http://build/url',
|
||||
fetch_config=create_config("firefox", "linux", 64, "x86_64"),
|
||||
build_url="http://build/url",
|
||||
build_date=date(2015, 9, 1),
|
||||
changeset='12ab' * 10,
|
||||
repo_url='http://repo:url',
|
||||
changeset="12ab" * 10,
|
||||
repo_url="http://repo:url",
|
||||
)
|
||||
defaults.update(attrs)
|
||||
return klass(**defaults)
|
||||
|
@ -20,25 +22,24 @@ def create_build_info(klass, **attrs):
|
|||
|
||||
def read_only(klass):
|
||||
defaults = [
|
||||
('app_name', 'firefox'),
|
||||
('build_url', 'http://build/url'),
|
||||
('build_date', date(2015, 9, 1)),
|
||||
('changeset', '12ab' * 10),
|
||||
('short_changeset', '12ab12ab'),
|
||||
('repo_url', 'http://repo:url'),
|
||||
("app_name", "firefox"),
|
||||
("build_url", "http://build/url"),
|
||||
("build_date", date(2015, 9, 1)),
|
||||
("changeset", "12ab" * 10),
|
||||
("short_changeset", "12ab12ab"),
|
||||
("repo_url", "http://repo:url"),
|
||||
]
|
||||
if klass is build_info.NightlyBuildInfo:
|
||||
defaults.extend([('repo_name', 'mozilla-central'),
|
||||
('build_type', 'nightly')])
|
||||
defaults.extend([("repo_name", "mozilla-central"), ("build_type", "nightly")])
|
||||
else:
|
||||
defaults.extend([('repo_name', 'mozilla-central'),
|
||||
('build_type', 'integration')])
|
||||
defaults.extend([("repo_name", "mozilla-central"), ("build_type", "integration")])
|
||||
return [(klass, attr, value) for attr, value in defaults]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('klass, attr, value',
|
||||
read_only(build_info.NightlyBuildInfo) +
|
||||
read_only(build_info.IntegrationBuildInfo))
|
||||
@pytest.mark.parametrize(
|
||||
"klass, attr, value",
|
||||
read_only(build_info.NightlyBuildInfo) + read_only(build_info.IntegrationBuildInfo),
|
||||
)
|
||||
def test_read_only_attrs(klass, attr, value):
|
||||
binfo = create_build_info(klass)
|
||||
assert getattr(binfo, attr) == value
|
||||
|
@ -47,70 +48,68 @@ def test_read_only_attrs(klass, attr, value):
|
|||
setattr(binfo, attr, value)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('klass, attr, value', [
|
||||
(build_info.NightlyBuildInfo, 'build_file', '/build/file'),
|
||||
(build_info.IntegrationBuildInfo, 'build_file', '/build/file'),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"klass, attr, value",
|
||||
[
|
||||
(build_info.NightlyBuildInfo, "build_file", "/build/file"),
|
||||
(build_info.IntegrationBuildInfo, "build_file", "/build/file"),
|
||||
],
|
||||
)
|
||||
def test_writable_attrs(klass, attr, value):
|
||||
binfo = create_build_info(klass)
|
||||
setattr(binfo, attr, value)
|
||||
assert getattr(binfo, attr) == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize('klass', [
|
||||
build_info.NightlyBuildInfo,
|
||||
build_info.IntegrationBuildInfo
|
||||
])
|
||||
@pytest.mark.parametrize("klass", [build_info.NightlyBuildInfo, build_info.IntegrationBuildInfo])
|
||||
def test_update_from_app_info(klass):
|
||||
app_info = {
|
||||
'application_changeset': 'chset',
|
||||
'application_repository': 'repo',
|
||||
"application_changeset": "chset",
|
||||
"application_repository": "repo",
|
||||
}
|
||||
binfo = create_build_info(klass, changeset=None, repo_url=None)
|
||||
assert binfo.changeset is None
|
||||
assert binfo.repo_url is None
|
||||
binfo.update_from_app_info(app_info)
|
||||
# binfo updated
|
||||
assert binfo.changeset == 'chset'
|
||||
assert binfo.repo_url == 'repo'
|
||||
assert binfo.changeset == "chset"
|
||||
assert binfo.repo_url == "repo"
|
||||
|
||||
binfo = create_build_info(klass)
|
||||
# if values were defined, nothing is updated
|
||||
assert binfo.changeset is not None
|
||||
assert binfo.repo_url is not None
|
||||
binfo.update_from_app_info(app_info)
|
||||
assert binfo.changeset != 'chset'
|
||||
assert binfo.repo_url != 'repo'
|
||||
assert binfo.changeset != "chset"
|
||||
assert binfo.repo_url != "repo"
|
||||
|
||||
|
||||
@pytest.mark.parametrize('klass', [
|
||||
build_info.NightlyBuildInfo,
|
||||
build_info.IntegrationBuildInfo
|
||||
])
|
||||
@pytest.mark.parametrize("klass", [build_info.NightlyBuildInfo, build_info.IntegrationBuildInfo])
|
||||
def test_to_dict(klass):
|
||||
binfo = create_build_info(klass)
|
||||
dct = binfo.to_dict()
|
||||
assert isinstance(dct, dict)
|
||||
assert 'app_name' in dct
|
||||
assert dct['app_name'] == 'firefox'
|
||||
assert "app_name" in dct
|
||||
assert dct["app_name"] == "firefox"
|
||||
|
||||
|
||||
@pytest.mark.parametrize('klass,extra,result', [
|
||||
@pytest.mark.parametrize(
|
||||
"klass,extra,result",
|
||||
[
|
||||
# build with defaults given in create_build_info
|
||||
(build_info.NightlyBuildInfo,
|
||||
{},
|
||||
'2015-09-01--mozilla-central--url'),
|
||||
(build_info.NightlyBuildInfo, {}, "2015-09-01--mozilla-central--url"),
|
||||
# this time with a datetime instance (buildid)
|
||||
(build_info.NightlyBuildInfo,
|
||||
{'build_date': datetime(2015, 11, 16, 10, 2, 5)},
|
||||
'2015-11-16-10-02-05--mozilla-central--url'),
|
||||
(
|
||||
build_info.NightlyBuildInfo,
|
||||
{"build_date": datetime(2015, 11, 16, 10, 2, 5)},
|
||||
"2015-11-16-10-02-05--mozilla-central--url",
|
||||
),
|
||||
# same but for integration
|
||||
(build_info.IntegrationBuildInfo,
|
||||
{},
|
||||
'12ab12ab12ab-shippable--mozilla-central--url'),
|
||||
])
|
||||
(build_info.IntegrationBuildInfo, {}, "12ab12ab12ab-shippable--mozilla-central--url",),
|
||||
],
|
||||
)
|
||||
def test_persist_filename(klass, extra, result):
|
||||
persist_part = extra.pop('persist_part', None)
|
||||
persist_part = extra.pop("persist_part", None)
|
||||
binfo = create_build_info(klass, **extra)
|
||||
if persist_part:
|
||||
# fake that the fetch config should return the persist_part
|
||||
|
|
|
@ -1,32 +1,16 @@
|
|||
from __future__ import absolute_import
|
||||
import pytest
|
||||
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
import pytest
|
||||
from six.moves import range
|
||||
|
||||
from mozregression import build_range
|
||||
from mozregression.fetch_build_info import InfoFetcher
|
||||
from mozregression.errors import BuildInfoNotFound
|
||||
from mozregression.fetch_configs import create_config
|
||||
from mozregression.json_pushes import JsonPushes
|
||||
|
||||
from .test_fetch_configs import create_push
|
||||
from six.moves import range
|
||||
|
||||
|
||||
class RangeCreator(object):
|
||||
def __init__(self, mocker):
|
||||
self.mocker = mocker
|
||||
|
||||
def create(self, values):
|
||||
info_fetcher = self.mocker.Mock(spec=InfoFetcher)
|
||||
info_fetcher.find_build_info.side_effect = lambda i: i
|
||||
future_build_infos = [
|
||||
build_range.FutureBuildInfo(info_fetcher, v) for v in values
|
||||
]
|
||||
return build_range.BuildRange(info_fetcher, future_build_infos)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def range_creator(mocker):
|
||||
return RangeCreator(mocker)
|
||||
|
||||
|
||||
def test_len(range_creator):
|
||||
|
@ -37,8 +21,7 @@ def test_access(range_creator):
|
|||
build_range = range_creator.create(list(range(5)))
|
||||
assert build_range[0] == 0
|
||||
|
||||
build_range.build_info_fetcher.find_build_info.side_effect = \
|
||||
BuildInfoNotFound
|
||||
build_range.build_info_fetcher.find_build_info.side_effect = BuildInfoNotFound
|
||||
assert build_range[1] is False
|
||||
|
||||
# even if one build is invalid, item access do not modify the range
|
||||
|
@ -69,11 +52,11 @@ def test_deleted(range_creator):
|
|||
|
||||
|
||||
def fetch_unless(br, func):
|
||||
|
||||
def fetch(index):
|
||||
if func(index):
|
||||
raise BuildInfoNotFound("")
|
||||
return index
|
||||
|
||||
br.build_info_fetcher.find_build_info.side_effect = fetch
|
||||
|
||||
|
||||
|
@ -98,8 +81,8 @@ def test_mid_point_interrupt(range_creator):
|
|||
|
||||
def _build_range(fb, rng):
|
||||
return build_range.BuildRange(
|
||||
fb.build_info_fetcher,
|
||||
[build_range.FutureBuildInfo(fb.build_info_fetcher, i) for i in rng])
|
||||
fb.build_info_fetcher, [build_range.FutureBuildInfo(fb.build_info_fetcher, i) for i in rng],
|
||||
)
|
||||
|
||||
|
||||
def range_before(fb, expand):
|
||||
|
@ -110,7 +93,9 @@ def range_after(fb, expand):
|
|||
return _build_range(fb, list(range(fb.data + 1, fb.data + 1 + expand)))
|
||||
|
||||
|
||||
@pytest.mark.parametrize('size_expand,initial,fail_in,expected,error', [
|
||||
@pytest.mark.parametrize(
|
||||
"size_expand,initial,fail_in,expected,error",
|
||||
[
|
||||
# short range
|
||||
(10, list(range(1)), [], list(range(1)), None),
|
||||
# empty range after removing invalids
|
||||
|
@ -126,21 +111,35 @@ def range_after(fb, expand):
|
|||
# higher missing, with missing builds in the after range
|
||||
(10, list(range(10)), list(range(9, 15)), list(range(0, 9)) + [15], None),
|
||||
# lower and higher missing, with missing builds in the before/after range
|
||||
(10, list(range(10)), list(range(-6, 1)) + list(range(9, 14)), [-7] + list(range(1, 9)) + [14],
|
||||
None),
|
||||
(
|
||||
10,
|
||||
list(range(10)),
|
||||
list(range(-6, 1)) + list(range(9, 14)),
|
||||
[-7] + list(range(1, 9)) + [14],
|
||||
None,
|
||||
),
|
||||
# unable to find any valid builds in before range
|
||||
(10, list(range(10)), list(range(-10, 1)), list(range(1, 10)),
|
||||
["can't find a build before"]),
|
||||
(
|
||||
10,
|
||||
list(range(10)),
|
||||
list(range(-10, 1)),
|
||||
list(range(1, 10)),
|
||||
["can't find a build before"],
|
||||
),
|
||||
# unable to find any valid builds in after range
|
||||
(10, list(range(10)), list(range(9, 20)), list(range(0, 9)),
|
||||
["can't find a build after"]),
|
||||
(10, list(range(10)), list(range(9, 20)), list(range(0, 9)), ["can't find a build after"],),
|
||||
# unable to find valid builds in before and after
|
||||
(10, list(range(10)), list(range(-10, 1)) + list(range(9, 20)), list(range(1, 9)),
|
||||
["can't find a build before", "can't find a build after"]),
|
||||
])
|
||||
def test_check_expand(mocker, range_creator, size_expand, initial, fail_in,
|
||||
expected, error):
|
||||
log = mocker.patch('mozregression.build_range.LOG')
|
||||
(
|
||||
10,
|
||||
list(range(10)),
|
||||
list(range(-10, 1)) + list(range(9, 20)),
|
||||
list(range(1, 9)),
|
||||
["can't find a build before", "can't find a build after"],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_check_expand(mocker, range_creator, size_expand, initial, fail_in, expected, error):
|
||||
log = mocker.patch("mozregression.build_range.LOG")
|
||||
build_range = range_creator.create(initial)
|
||||
fetch_unless(build_range, lambda i: i in fail_in)
|
||||
|
||||
|
@ -161,8 +160,7 @@ def test_check_expand_interrupt(range_creator):
|
|||
build_range.mid_point = lambda **kwa: mp() # do not interrupt in there
|
||||
|
||||
with pytest.raises(StopIteration):
|
||||
build_range.check_expand(5, range_before, range_after,
|
||||
interrupt=lambda: True)
|
||||
build_range.check_expand(5, range_before, range_after, interrupt=lambda: True)
|
||||
|
||||
|
||||
def test_index(range_creator):
|
||||
|
@ -177,19 +175,16 @@ def test_index(range_creator):
|
|||
|
||||
|
||||
def test_get_integration_range(mocker):
|
||||
fetch_config = create_config('firefox', 'linux', 64, 'x86_64')
|
||||
jpush_class = mocker.patch('mozregression.fetch_build_info.JsonPushes')
|
||||
pushes = [create_push('b', 1), create_push('d', 2), create_push('f', 3)]
|
||||
jpush = mocker.Mock(
|
||||
pushes_within_changes=mocker.Mock(return_value=pushes),
|
||||
spec=JsonPushes
|
||||
)
|
||||
fetch_config = create_config("firefox", "linux", 64, "x86_64")
|
||||
jpush_class = mocker.patch("mozregression.fetch_build_info.JsonPushes")
|
||||
pushes = [create_push("b", 1), create_push("d", 2), create_push("f", 3)]
|
||||
jpush = mocker.Mock(pushes_within_changes=mocker.Mock(return_value=pushes), spec=JsonPushes)
|
||||
jpush_class.return_value = jpush
|
||||
|
||||
b_range = build_range.get_integration_range(fetch_config, 'a', 'e')
|
||||
b_range = build_range.get_integration_range(fetch_config, "a", "e")
|
||||
|
||||
jpush_class.assert_called_once_with(branch='mozilla-central')
|
||||
jpush.pushes_within_changes.assert_called_once_with('a', 'e')
|
||||
jpush_class.assert_called_once_with(branch="mozilla-central")
|
||||
jpush.pushes_within_changes.assert_called_once_with("a", "e")
|
||||
assert isinstance(b_range, build_range.BuildRange)
|
||||
assert len(b_range) == 3
|
||||
|
||||
|
@ -198,27 +193,23 @@ def test_get_integration_range(mocker):
|
|||
assert b_range[1] == pushes[1]
|
||||
assert b_range[2] == pushes[2]
|
||||
|
||||
b_range.future_build_infos[0].date_or_changeset() == 'b'
|
||||
b_range.future_build_infos[0].date_or_changeset() == "b"
|
||||
|
||||
|
||||
def test_get_integration_range_with_expand(mocker):
|
||||
fetch_config = create_config('firefox', 'linux', 64, 'x86_64')
|
||||
jpush_class = mocker.patch('mozregression.fetch_build_info.JsonPushes')
|
||||
pushes = [create_push('b', 1), create_push('d', 2), create_push('f', 3)]
|
||||
jpush = mocker.Mock(
|
||||
pushes_within_changes=mocker.Mock(return_value=pushes),
|
||||
spec=JsonPushes
|
||||
)
|
||||
fetch_config = create_config("firefox", "linux", 64, "x86_64")
|
||||
jpush_class = mocker.patch("mozregression.fetch_build_info.JsonPushes")
|
||||
pushes = [create_push("b", 1), create_push("d", 2), create_push("f", 3)]
|
||||
jpush = mocker.Mock(pushes_within_changes=mocker.Mock(return_value=pushes), spec=JsonPushes)
|
||||
jpush_class.return_value = jpush
|
||||
|
||||
check_expand = mocker.patch(
|
||||
'mozregression.build_range.BuildRange.check_expand')
|
||||
check_expand = mocker.patch("mozregression.build_range.BuildRange.check_expand")
|
||||
|
||||
build_range.get_integration_range(fetch_config, 'a', 'e', expand=10)
|
||||
build_range.get_integration_range(fetch_config, "a", "e", expand=10)
|
||||
|
||||
check_expand.assert_called_once_with(
|
||||
10, build_range.tc_range_before, build_range.tc_range_after,
|
||||
interrupt=None)
|
||||
10, build_range.tc_range_before, build_range.tc_range_after, interrupt=None
|
||||
)
|
||||
|
||||
|
||||
DATE_NOW = datetime.now()
|
||||
|
@ -227,34 +218,32 @@ DATE_YEAR_BEFORE = DATE_NOW + timedelta(days=-365)
|
|||
DATE_TOO_OLD = DATE_YEAR_BEFORE + timedelta(days=-5)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start_date,end_date,start_call,end_call', [
|
||||
@pytest.mark.parametrize(
|
||||
"start_date,end_date,start_call,end_call",
|
||||
[
|
||||
(DATE_BEFORE_NOW, DATE_NOW, DATE_BEFORE_NOW, DATE_NOW),
|
||||
# if a date is older than last year, it won't be honored
|
||||
(DATE_TOO_OLD, DATE_NOW, DATE_YEAR_BEFORE, DATE_NOW),
|
||||
])
|
||||
def test_get_integration_range_with_dates(mocker, start_date, end_date,
|
||||
start_call, end_call):
|
||||
fetch_config = create_config('firefox', 'linux', 64, 'x86_64')
|
||||
jpush_class = mocker.patch('mozregression.fetch_build_info.JsonPushes')
|
||||
jpush = mocker.Mock(
|
||||
pushes_within_changes=mocker.Mock(return_value=[]),
|
||||
spec=JsonPushes
|
||||
],
|
||||
)
|
||||
def test_get_integration_range_with_dates(mocker, start_date, end_date, start_call, end_call):
|
||||
fetch_config = create_config("firefox", "linux", 64, "x86_64")
|
||||
jpush_class = mocker.patch("mozregression.fetch_build_info.JsonPushes")
|
||||
jpush = mocker.Mock(pushes_within_changes=mocker.Mock(return_value=[]), spec=JsonPushes)
|
||||
jpush_class.return_value = jpush
|
||||
|
||||
build_range.get_integration_range(fetch_config, start_date, end_date,
|
||||
time_limit=DATE_YEAR_BEFORE)
|
||||
build_range.get_integration_range(
|
||||
fetch_config, start_date, end_date, time_limit=DATE_YEAR_BEFORE
|
||||
)
|
||||
|
||||
jpush.pushes_within_changes.assert_called_once_with(start_call, end_call)
|
||||
|
||||
|
||||
def test_get_nightly_range():
|
||||
fetch_config = create_config('firefox', 'linux', 64, 'x86_64')
|
||||
fetch_config = create_config("firefox", "linux", 64, "x86_64")
|
||||
|
||||
b_range = build_range.get_nightly_range(
|
||||
fetch_config,
|
||||
date(2015, 0o1, 0o1),
|
||||
date(2015, 0o1, 0o3)
|
||||
fetch_config, date(2015, 0o1, 0o1), date(2015, 0o1, 0o3)
|
||||
)
|
||||
|
||||
assert isinstance(b_range, build_range.BuildRange)
|
||||
|
@ -266,14 +255,16 @@ def test_get_nightly_range():
|
|||
assert b_range[2] == date(2015, 0o1, 0o3)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('start,end,range_size', [
|
||||
@pytest.mark.parametrize(
|
||||
"start,end,range_size",
|
||||
[
|
||||
(datetime(2015, 11, 16, 10, 2, 5), date(2015, 11, 19), 4),
|
||||
(date(2015, 11, 14), datetime(2015, 11, 16, 10, 2, 5), 3),
|
||||
(datetime(2015, 11, 16, 10, 2, 5),
|
||||
datetime(2015, 11, 20, 11, 4, 9), 5),
|
||||
])
|
||||
(datetime(2015, 11, 16, 10, 2, 5), datetime(2015, 11, 20, 11, 4, 9), 5),
|
||||
],
|
||||
)
|
||||
def test_get_nightly_range_datetime(start, end, range_size):
|
||||
fetch_config = create_config('firefox', 'linux', 64, 'x86_64')
|
||||
fetch_config = create_config("firefox", "linux", 64, "x86_64")
|
||||
|
||||
b_range = build_range.get_nightly_range(fetch_config, start, end)
|
||||
|
||||
|
@ -288,13 +279,15 @@ def test_get_nightly_range_datetime(start, end, range_size):
|
|||
assert isinstance(b_range[i], date)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('func,start,size,expected_range', [
|
||||
@pytest.mark.parametrize(
|
||||
"func,start,size,expected_range",
|
||||
[
|
||||
(build_range.tc_range_before, 1, 2, [-1, 0]),
|
||||
(build_range.tc_range_after, 1, 3, list(range(2, 5))),
|
||||
])
|
||||
],
|
||||
)
|
||||
def test_tc_range_before_after(mocker, func, start, size, expected_range):
|
||||
ftc = build_range.FutureBuildInfo(mocker.Mock(),
|
||||
mocker.Mock(push_id=start))
|
||||
ftc = build_range.FutureBuildInfo(mocker.Mock(), mocker.Mock(push_id=start))
|
||||
|
||||
def pushes(startID, endID):
|
||||
# startID: greaterThan - endID: up to and including
|
||||
|
|
|
@ -1,27 +1,26 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
from mozregression.class_registry import ClassRegistry
|
||||
|
||||
TEST_REGISTRY = ClassRegistry()
|
||||
|
||||
|
||||
@TEST_REGISTRY.register('c1')
|
||||
@TEST_REGISTRY.register("c1")
|
||||
class C1(object):
|
||||
pass
|
||||
|
||||
|
||||
@TEST_REGISTRY.register('c2')
|
||||
@TEST_REGISTRY.register("c2")
|
||||
class C2(object):
|
||||
pass
|
||||
|
||||
|
||||
@TEST_REGISTRY.register('c3', some_other_attr=True)
|
||||
@TEST_REGISTRY.register("c3", some_other_attr=True)
|
||||
class C3(object):
|
||||
pass
|
||||
|
||||
|
||||
def test_get_names():
|
||||
TEST_REGISTRY.names() == ['c1', 'c2', 'c3']
|
||||
TEST_REGISTRY.names() == ["c1", "c2", "c3"]
|
||||
|
||||
TEST_REGISTRY.names(
|
||||
lambda klass: getattr(klass, 'some_other_attr', None)
|
||||
) == ['c3']
|
||||
TEST_REGISTRY.names(lambda klass: getattr(klass, "some_other_attr", None)) == ["c3"]
|
||||
|
|
|
@ -3,19 +3,20 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import absolute_import
|
||||
import unittest
|
||||
import tempfile
|
||||
import os
|
||||
import datetime
|
||||
import pytest
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
import six
|
||||
from mock import patch
|
||||
from mozlog import get_default_logger
|
||||
|
||||
from mozregression import cli, errors
|
||||
from mozregression.releases import releases
|
||||
from mozregression.fetch_configs import create_config
|
||||
import six
|
||||
from mozregression.releases import releases
|
||||
|
||||
|
||||
class TestParseDate(unittest.TestCase):
|
||||
|
@ -28,25 +29,23 @@ class TestParseDate(unittest.TestCase):
|
|||
self.assertEqual(date, datetime.datetime(2015, 11, 3, 3, 2, 48))
|
||||
|
||||
def test_invalid_date(self):
|
||||
self.assertRaises(errors.DateFormatError, cli.parse_date,
|
||||
"invalid_format")
|
||||
self.assertRaises(errors.DateFormatError, cli.parse_date, "invalid_format")
|
||||
# test invalid buildid (43 is not a valid day)
|
||||
self.assertRaises(errors.DateFormatError, cli.parse_date,
|
||||
"20151143030248")
|
||||
self.assertRaises(errors.DateFormatError, cli.parse_date, "20151143030248")
|
||||
|
||||
|
||||
class TestParseBits(unittest.TestCase):
|
||||
@patch('mozregression.cli.mozinfo')
|
||||
@patch("mozregression.cli.mozinfo")
|
||||
def test_parse_32(self, mozinfo):
|
||||
mozinfo.bits = 32
|
||||
self.assertEqual(cli.parse_bits('32'), 32)
|
||||
self.assertEqual(cli.parse_bits('64'), 32)
|
||||
self.assertEqual(cli.parse_bits("32"), 32)
|
||||
self.assertEqual(cli.parse_bits("64"), 32)
|
||||
|
||||
@patch('mozregression.cli.mozinfo')
|
||||
@patch("mozregression.cli.mozinfo")
|
||||
def test_parse_64(self, mozinfo):
|
||||
mozinfo.bits = 64
|
||||
self.assertEqual(cli.parse_bits('32'), 32)
|
||||
self.assertEqual(cli.parse_bits('64'), 64)
|
||||
self.assertEqual(cli.parse_bits("32"), 32)
|
||||
self.assertEqual(cli.parse_bits("64"), 64)
|
||||
|
||||
|
||||
class TestPreferences(unittest.TestCase):
|
||||
|
@ -54,18 +53,18 @@ class TestPreferences(unittest.TestCase):
|
|||
handle, filepath = tempfile.mkstemp()
|
||||
self.addCleanup(os.unlink, filepath)
|
||||
|
||||
with os.fdopen(handle, 'w') as conf_file:
|
||||
with os.fdopen(handle, "w") as conf_file:
|
||||
conf_file.write('{ "browser.tabs.remote.autostart": false }')
|
||||
|
||||
prefs_files = [filepath]
|
||||
prefs = cli.preferences(prefs_files, None, None)
|
||||
self.assertEqual(prefs, [('browser.tabs.remote.autostart', False)])
|
||||
self.assertEqual(prefs, [("browser.tabs.remote.autostart", False)])
|
||||
|
||||
def test_preferences_args(self):
|
||||
prefs_args = ["browser.tabs.remote.autostart:false"]
|
||||
|
||||
prefs = cli.preferences(None, prefs_args, None)
|
||||
self.assertEqual(prefs, [('browser.tabs.remote.autostart', False)])
|
||||
self.assertEqual(prefs, [("browser.tabs.remote.autostart", False)])
|
||||
|
||||
prefs_args = ["browser.tabs.remote.autostart"]
|
||||
|
||||
|
@ -78,41 +77,40 @@ class TestCli(unittest.TestCase):
|
|||
handle, filepath = tempfile.mkstemp()
|
||||
self.addCleanup(os.unlink, filepath)
|
||||
|
||||
with os.fdopen(handle, 'w') as conf_file:
|
||||
with os.fdopen(handle, "w") as conf_file:
|
||||
conf_file.write(content)
|
||||
return filepath
|
||||
|
||||
def test_get_erronous_cfg_defaults(self):
|
||||
filepath = self._create_conf_file('aaaaaaaaaaa [Defaults]\n')
|
||||
filepath = self._create_conf_file("aaaaaaaaaaa [Defaults]\n")
|
||||
|
||||
with self.assertRaises(errors.MozRegressionError):
|
||||
cli.cli(conf_file=filepath)
|
||||
|
||||
def test_get_defaults(self):
|
||||
valid_values = {'http-timeout': '10.2',
|
||||
'persist': '/home/foo/.mozregression',
|
||||
'bits': '64'}
|
||||
valid_values = {
|
||||
"http-timeout": "10.2",
|
||||
"persist": "/home/foo/.mozregression",
|
||||
"bits": "64",
|
||||
}
|
||||
|
||||
content = ["%s=%s\n" % (key, value)
|
||||
for key, value in six.iteritems(valid_values)]
|
||||
filepath = self._create_conf_file('\n'.join(content))
|
||||
content = ["%s=%s\n" % (key, value) for key, value in six.iteritems(valid_values)]
|
||||
filepath = self._create_conf_file("\n".join(content))
|
||||
|
||||
options = cli.cli(['--bits=32'], conf_file=filepath).options
|
||||
options = cli.cli(["--bits=32"], conf_file=filepath).options
|
||||
|
||||
self.assertEqual(options.http_timeout, 10.2)
|
||||
self.assertEqual(options.persist, '/home/foo/.mozregression')
|
||||
self.assertEqual(options.bits, '32')
|
||||
self.assertEqual(options.persist, "/home/foo/.mozregression")
|
||||
self.assertEqual(options.bits, "32")
|
||||
|
||||
def test_warn_invalid_build_type_in_conf(self):
|
||||
filepath = self._create_conf_file('build-type=foo\n')
|
||||
filepath = self._create_conf_file("build-type=foo\n")
|
||||
conf = cli.cli([], conf_file=filepath)
|
||||
warns = []
|
||||
conf.logger.warning = warns.append
|
||||
conf.validate()
|
||||
self.assertIn(
|
||||
"Unable to find a suitable build type 'foo'."
|
||||
" (Defaulting to 'shippable')",
|
||||
warns
|
||||
"Unable to find a suitable build type 'foo'." " (Defaulting to 'shippable')", warns,
|
||||
)
|
||||
|
||||
|
||||
|
@ -124,45 +122,45 @@ def do_cli(*argv):
|
|||
|
||||
def test_get_usage():
|
||||
output = []
|
||||
with patch('sys.stdout') as stdout:
|
||||
with patch("sys.stdout") as stdout:
|
||||
stdout.write.side_effect = output.append
|
||||
|
||||
with pytest.raises(SystemExit) as exc:
|
||||
do_cli('-h')
|
||||
do_cli("-h")
|
||||
assert exc.value.code == 0
|
||||
assert "usage:" in ''.join(output)
|
||||
assert "usage:" in "".join(output)
|
||||
|
||||
|
||||
def test_list_build_types(mocker):
|
||||
output = []
|
||||
with patch('sys.stdout') as stdout:
|
||||
with patch("sys.stdout") as stdout:
|
||||
stdout.write.side_effect = output.append
|
||||
|
||||
with pytest.raises(SystemExit) as exc:
|
||||
do_cli('--list-build-types')
|
||||
do_cli("--list-build-types")
|
||||
assert exc.value.code == 0
|
||||
assert "firefox:\n shippable" in ''.join(output)
|
||||
assert "firefox:\n shippable" in "".join(output)
|
||||
|
||||
|
||||
DEFAULTS_DATE = [
|
||||
('linux', 64, datetime.date(2009, 1, 1)),
|
||||
('linux', 32, datetime.date(2009, 1, 1)),
|
||||
('mac', 64, datetime.date(2009, 1, 1)),
|
||||
('win', 32, datetime.date(2009, 1, 1)),
|
||||
('win', 64, datetime.date(2010, 5, 28)),
|
||||
("linux", 64, datetime.date(2009, 1, 1)),
|
||||
("linux", 32, datetime.date(2009, 1, 1)),
|
||||
("mac", 64, datetime.date(2009, 1, 1)),
|
||||
("win", 32, datetime.date(2009, 1, 1)),
|
||||
("win", 64, datetime.date(2010, 5, 28)),
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("os,bits,default_good_date", DEFAULTS_DATE)
|
||||
def test_no_args(os, bits, default_good_date):
|
||||
with patch('mozregression.cli.mozinfo') as mozinfo:
|
||||
with patch("mozregression.cli.mozinfo") as mozinfo:
|
||||
mozinfo.os = os
|
||||
mozinfo.bits = bits
|
||||
config = do_cli()
|
||||
# application is by default firefox
|
||||
assert config.fetch_config.app_name == 'firefox'
|
||||
assert config.fetch_config.app_name == "firefox"
|
||||
# nightly by default
|
||||
assert config.action == 'bisect_nightlies'
|
||||
assert config.action == "bisect_nightlies"
|
||||
assert config.options.good == default_good_date
|
||||
assert config.options.bad == datetime.date.today()
|
||||
|
||||
|
@ -172,72 +170,80 @@ SOME_DATE = TODAY + datetime.timedelta(days=-20)
|
|||
SOME_OLDER_DATE = TODAY + datetime.timedelta(days=-10)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('params,good,bad', [
|
||||
@pytest.mark.parametrize(
|
||||
"params,good,bad",
|
||||
[
|
||||
# we can use dates with integration branches
|
||||
(['--good=%s' % SOME_DATE, '--bad=%s' % SOME_OLDER_DATE, '--repo=m-i'],
|
||||
SOME_DATE, SOME_OLDER_DATE),
|
||||
(
|
||||
["--good=%s" % SOME_DATE, "--bad=%s" % SOME_OLDER_DATE, "--repo=m-i"],
|
||||
SOME_DATE,
|
||||
SOME_OLDER_DATE,
|
||||
),
|
||||
# non opt build flavors are also found using taskcluster
|
||||
(['--good=%s' % SOME_DATE, '--bad=%s' % SOME_OLDER_DATE, '-B', 'debug'],
|
||||
SOME_DATE, SOME_OLDER_DATE)
|
||||
])
|
||||
(
|
||||
["--good=%s" % SOME_DATE, "--bad=%s" % SOME_OLDER_DATE, "-B", "debug"],
|
||||
SOME_DATE,
|
||||
SOME_OLDER_DATE,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_use_taskcluster_bisection_method(params, good, bad):
|
||||
config = do_cli(*params)
|
||||
|
||||
assert config.action == 'bisect_integration' # meaning taskcluster usage
|
||||
assert config.action == "bisect_integration" # meaning taskcluster usage
|
||||
# compare dates using the representation, as we may have
|
||||
# date / datetime instances
|
||||
assert config.options.good.strftime('%Y-%m-%d') \
|
||||
== good.strftime('%Y-%m-%d')
|
||||
assert config.options.bad.strftime('%Y-%m-%d') \
|
||||
== bad.strftime('%Y-%m-%d')
|
||||
assert config.options.good.strftime("%Y-%m-%d") == good.strftime("%Y-%m-%d")
|
||||
assert config.options.bad.strftime("%Y-%m-%d") == bad.strftime("%Y-%m-%d")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("os,bits,default_bad_date", DEFAULTS_DATE)
|
||||
def test_find_fix_reverse_default_dates(os, bits, default_bad_date):
|
||||
with patch('mozregression.cli.mozinfo') as mozinfo:
|
||||
with patch("mozregression.cli.mozinfo") as mozinfo:
|
||||
mozinfo.os = os
|
||||
mozinfo.bits = bits
|
||||
config = do_cli('--find-fix')
|
||||
config = do_cli("--find-fix")
|
||||
# application is by default firefox
|
||||
assert config.fetch_config.app_name == 'firefox'
|
||||
assert config.fetch_config.app_name == "firefox"
|
||||
# nightly by default
|
||||
assert config.action == 'bisect_nightlies'
|
||||
assert config.action == "bisect_nightlies"
|
||||
assert config.options.bad == default_bad_date
|
||||
assert config.options.good == datetime.date.today()
|
||||
|
||||
|
||||
def test_with_releases():
|
||||
releases_data = sorted(((k, v) for k, v in releases().items()),
|
||||
key=(lambda k_v: k_v[0]))
|
||||
conf = do_cli(
|
||||
'--bad=%s' % releases_data[-1][0],
|
||||
'--good=%s' % releases_data[0][0],
|
||||
)
|
||||
releases_data = sorted(((k, v) for k, v in releases().items()), key=(lambda k_v: k_v[0]))
|
||||
conf = do_cli("--bad=%s" % releases_data[-1][0], "--good=%s" % releases_data[0][0],)
|
||||
assert str(conf.options.good) == releases_data[0][1]
|
||||
assert str(conf.options.bad) == releases_data[-1][1]
|
||||
|
||||
|
||||
@pytest.mark.parametrize('args,action,value', [
|
||||
(['--launch=34'], 'launch_nightlies', cli.parse_date(releases()[34])),
|
||||
(['--launch=2015-11-01'], 'launch_nightlies', datetime.date(2015, 11, 1)),
|
||||
(['--launch=abc123'], 'launch_integration', 'abc123'),
|
||||
(['--launch=2015-11-01', '--repo=m-i'], 'launch_integration',
|
||||
datetime.date(2015, 11, 1)),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"args,action,value",
|
||||
[
|
||||
(["--launch=34"], "launch_nightlies", cli.parse_date(releases()[34])),
|
||||
(["--launch=2015-11-01"], "launch_nightlies", datetime.date(2015, 11, 1)),
|
||||
(["--launch=abc123"], "launch_integration", "abc123"),
|
||||
(["--launch=2015-11-01", "--repo=m-i"], "launch_integration", datetime.date(2015, 11, 1),),
|
||||
],
|
||||
)
|
||||
def test_launch(args, action, value):
|
||||
config = do_cli(*args)
|
||||
assert config.action == action
|
||||
assert config.options.launch == value
|
||||
|
||||
|
||||
@pytest.mark.parametrize('args,repo,value', [
|
||||
(['--launch=60.0', '--repo=m-r'], 'mozilla-release', 'FIREFOX_60_0_RELEASE'),
|
||||
(['--launch=61', '--repo=m-r'], 'mozilla-release', 'FIREFOX_61_0_RELEASE'),
|
||||
(['--launch=62.0.1'], 'mozilla-release', 'FIREFOX_62_0_1_RELEASE'),
|
||||
(['--launch=63.0b4', '--repo=m-b'], 'mozilla-beta', 'FIREFOX_63_0b4_RELEASE'),
|
||||
(['--launch=64', '--repo=m-b'], 'mozilla-beta', 'FIREFOX_RELEASE_64_BASE'),
|
||||
(['--launch=65.0b11'], 'mozilla-beta', 'FIREFOX_65_0b11_RELEASE'),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"args,repo,value",
|
||||
[
|
||||
(["--launch=60.0", "--repo=m-r"], "mozilla-release", "FIREFOX_60_0_RELEASE"),
|
||||
(["--launch=61", "--repo=m-r"], "mozilla-release", "FIREFOX_61_0_RELEASE"),
|
||||
(["--launch=62.0.1"], "mozilla-release", "FIREFOX_62_0_1_RELEASE"),
|
||||
(["--launch=63.0b4", "--repo=m-b"], "mozilla-beta", "FIREFOX_63_0b4_RELEASE"),
|
||||
(["--launch=64", "--repo=m-b"], "mozilla-beta", "FIREFOX_RELEASE_64_BASE"),
|
||||
(["--launch=65.0b11"], "mozilla-beta", "FIREFOX_65_0b11_RELEASE"),
|
||||
],
|
||||
)
|
||||
def test_versions(args, repo, value):
|
||||
config = do_cli(*args)
|
||||
assert config.fetch_config.repo == repo
|
||||
|
@ -246,71 +252,76 @@ def test_versions(args, repo, value):
|
|||
|
||||
def test_bad_date_later_than_good():
|
||||
with pytest.raises(errors.MozRegressionError) as exc:
|
||||
do_cli('--good=2015-01-01', '--bad=2015-01-10', '--find-fix')
|
||||
assert 'is later than good' in str(exc.value)
|
||||
do_cli("--good=2015-01-01", "--bad=2015-01-10", "--find-fix")
|
||||
assert "is later than good" in str(exc.value)
|
||||
assert "You should not use the --find-fix" in str(exc.value)
|
||||
|
||||
|
||||
def test_good_date_later_than_bad():
|
||||
with pytest.raises(errors.MozRegressionError) as exc:
|
||||
do_cli('--good=2015-01-10', '--bad=2015-01-01')
|
||||
assert 'is later than bad' in str(exc.value)
|
||||
do_cli("--good=2015-01-10", "--bad=2015-01-01")
|
||||
assert "is later than bad" in str(exc.value)
|
||||
assert "you wanted to use the --find-fix" in str(exc.value)
|
||||
|
||||
|
||||
def test_basic_integration():
|
||||
config = do_cli('--good=c1', '--bad=c5')
|
||||
assert config.fetch_config.app_name == 'firefox'
|
||||
assert config.action == 'bisect_integration'
|
||||
assert config.options.good == 'c1'
|
||||
assert config.options.bad == 'c5'
|
||||
config = do_cli("--good=c1", "--bad=c5")
|
||||
assert config.fetch_config.app_name == "firefox"
|
||||
assert config.action == "bisect_integration"
|
||||
assert config.options.good == "c1"
|
||||
assert config.options.bad == "c5"
|
||||
|
||||
|
||||
def test_list_releases(mocker):
|
||||
out = []
|
||||
stdout = mocker.patch('sys.stdout')
|
||||
stdout = mocker.patch("sys.stdout")
|
||||
stdout.write = out.append
|
||||
with pytest.raises(SystemExit):
|
||||
do_cli('--list-releases')
|
||||
assert 'Valid releases:' in '\n'.join(out)
|
||||
do_cli("--list-releases")
|
||||
assert "Valid releases:" in "\n".join(out)
|
||||
|
||||
|
||||
def test_write_confif(mocker):
|
||||
out = []
|
||||
stdout = mocker.patch('sys.stdout')
|
||||
stdout = mocker.patch("sys.stdout")
|
||||
stdout.write = out.append
|
||||
write_conf = mocker.patch('mozregression.cli.write_conf')
|
||||
write_conf = mocker.patch("mozregression.cli.write_conf")
|
||||
with pytest.raises(SystemExit):
|
||||
do_cli('--write-conf')
|
||||
do_cli("--write-conf")
|
||||
assert len(write_conf.mock_calls) == 1
|
||||
|
||||
|
||||
def test_warning_no_conf(mocker):
|
||||
out = []
|
||||
stdout = mocker.patch('sys.stdout')
|
||||
stdout = mocker.patch("sys.stdout")
|
||||
stdout.write = out.append
|
||||
cli.cli([], conf_file='blah_this is not a valid file_I_hope')
|
||||
assert "You should use a config file" in '\n'.join(out)
|
||||
cli.cli([], conf_file="blah_this is not a valid file_I_hope")
|
||||
assert "You should use a config file" in "\n".join(out)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('args,enabled', [
|
||||
@pytest.mark.parametrize(
|
||||
"args,enabled",
|
||||
[
|
||||
([], False), # not enabled by default, because build_type is opt
|
||||
(['-P=stdout'], True), # explicitly enabled
|
||||
(['-B=debug'], True), # enabled because build type is not opt
|
||||
(['-B=debug', '-P=none'], False), # explicitly disabled
|
||||
])
|
||||
(["-P=stdout"], True), # explicitly enabled
|
||||
(["-B=debug"], True), # enabled because build type is not opt
|
||||
(["-B=debug", "-P=none"], False), # explicitly disabled
|
||||
],
|
||||
)
|
||||
def test_process_output_enabled(args, enabled):
|
||||
do_cli(*args)
|
||||
log_filter = get_default_logger("process").component_filter
|
||||
|
||||
result = log_filter({'some': 'data'})
|
||||
result = log_filter({"some": "data"})
|
||||
if enabled:
|
||||
assert result
|
||||
else:
|
||||
assert not result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('mozversion_msg,shown', [
|
||||
@pytest.mark.parametrize(
|
||||
"mozversion_msg,shown",
|
||||
[
|
||||
("platform_changeset: abc123", False),
|
||||
("application_changeset: abc123", True),
|
||||
("application_version: stuff:thing", True),
|
||||
|
@ -319,11 +330,12 @@ def test_process_output_enabled(args, enabled):
|
|||
("application_vendor: stuff", False),
|
||||
("application_display_name: stuff", False),
|
||||
("not a valid key value pair", True),
|
||||
])
|
||||
],
|
||||
)
|
||||
def test_mozversion_output_filtered(mozversion_msg, shown):
|
||||
do_cli()
|
||||
log_filter = get_default_logger("mozversion").component_filter
|
||||
log_data = {'message': mozversion_msg}
|
||||
log_data = {"message": mozversion_msg}
|
||||
|
||||
result = log_filter(log_data)
|
||||
if shown:
|
||||
|
@ -332,20 +344,22 @@ def test_mozversion_output_filtered(mozversion_msg, shown):
|
|||
assert not result
|
||||
|
||||
|
||||
@pytest.mark.parametrize('app, os, bits, processor, build_type, expected_range', [
|
||||
('jsshell', 'win', 64, 'x86_64', None, (datetime.date(2014, 5, 27), TODAY)),
|
||||
('jsshell', 'linux', 64, 'x86_64', 'asan', (datetime.date(2013, 9, 1), TODAY)),
|
||||
('jsshell', 'linux', 64, 'x86_64', 'asan-debug', (datetime.date(2013, 9, 1), TODAY)),
|
||||
('jsshell', 'linux', 32, 'x86', None, (datetime.date(2012, 4, 18), TODAY)),
|
||||
('jsshell', 'mac', 64, 'x86_64', None, (datetime.date(2012, 4, 18), TODAY)),
|
||||
('jsshell', 'win', 32, 'x86', None, (datetime.date(2012, 4, 18), TODAY)),
|
||||
@pytest.mark.parametrize(
|
||||
"app, os, bits, processor, build_type, expected_range",
|
||||
[
|
||||
("jsshell", "win", 64, "x86_64", None, (datetime.date(2014, 5, 27), TODAY)),
|
||||
("jsshell", "linux", 64, "x86_64", "asan", (datetime.date(2013, 9, 1), TODAY)),
|
||||
("jsshell", "linux", 64, "x86_64", "asan-debug", (datetime.date(2013, 9, 1), TODAY),),
|
||||
("jsshell", "linux", 32, "x86", None, (datetime.date(2012, 4, 18), TODAY)),
|
||||
("jsshell", "mac", 64, "x86_64", None, (datetime.date(2012, 4, 18), TODAY)),
|
||||
("jsshell", "win", 32, "x86", None, (datetime.date(2012, 4, 18), TODAY)),
|
||||
# anything else on win 64
|
||||
('firefox', 'win', 64, 'x86_64', None, (datetime.date(2010, 5, 28), TODAY)),
|
||||
("firefox", "win", 64, "x86_64", None, (datetime.date(2010, 5, 28), TODAY)),
|
||||
# anything else
|
||||
('firefox', 'linux', 64, 'x86_64', None, (datetime.date(2009, 1, 1), TODAY)),
|
||||
])
|
||||
def test_get_default_date_range(app, os, bits, processor, build_type,
|
||||
expected_range):
|
||||
("firefox", "linux", 64, "x86_64", None, (datetime.date(2009, 1, 1), TODAY)),
|
||||
],
|
||||
)
|
||||
def test_get_default_date_range(app, os, bits, processor, build_type, expected_range):
|
||||
fetch_config = create_config(app, os, bits, processor)
|
||||
if build_type:
|
||||
fetch_config.set_build_type(build_type)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import absolute_import
|
||||
import pytest
|
||||
|
||||
import os
|
||||
import mozfile
|
||||
import tempfile
|
||||
|
||||
from mozregression.config import write_conf, get_defaults
|
||||
import mozfile
|
||||
import pytest
|
||||
|
||||
from mozregression.config import get_defaults, write_conf
|
||||
|
||||
|
||||
@pytest.yield_fixture
|
||||
|
@ -14,33 +16,43 @@ def tmp():
|
|||
mozfile.remove(temp_dir)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('os_,bits,inputs,conf_dir_exists,results', [
|
||||
('mac', 64, ['', ''], False,
|
||||
{'persist': None, 'persist-size-limit': '20.0'}),
|
||||
('linux', 32, ['NONE', 'NONE'], True,
|
||||
{'persist': '', 'persist-size-limit': '0.0'}),
|
||||
('win', 32, ['', '10'], True,
|
||||
{'persist': None, 'persist-size-limit': '10.0'}),
|
||||
@pytest.mark.parametrize(
|
||||
"os_,bits,inputs,conf_dir_exists,results",
|
||||
[
|
||||
("mac", 64, ["", ""], False, {"persist": None, "persist-size-limit": "20.0"}),
|
||||
("linux", 32, ["NONE", "NONE"], True, {"persist": "", "persist-size-limit": "0.0"},),
|
||||
("win", 32, ["", "10"], True, {"persist": None, "persist-size-limit": "10.0"}),
|
||||
# on 64 bit (except for mac), bits option is asked
|
||||
('linu', 64, ['NONE', 'NONE', ''], True,
|
||||
{'persist': '', 'persist-size-limit': '0.0', 'bits': '64'}),
|
||||
('win', 64, ['NONE', 'NONE', '32'], True,
|
||||
{'persist': '', 'persist-size-limit': '0.0', 'bits': '32'}),
|
||||
])
|
||||
(
|
||||
"linu",
|
||||
64,
|
||||
["NONE", "NONE", ""],
|
||||
True,
|
||||
{"persist": "", "persist-size-limit": "0.0", "bits": "64"},
|
||||
),
|
||||
(
|
||||
"win",
|
||||
64,
|
||||
["NONE", "NONE", "32"],
|
||||
True,
|
||||
{"persist": "", "persist-size-limit": "0.0", "bits": "32"},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_write_conf(tmp, mocker, os_, bits, inputs, conf_dir_exists, results):
|
||||
mozinfo = mocker.patch('mozregression.config.mozinfo')
|
||||
mozinfo = mocker.patch("mozregression.config.mozinfo")
|
||||
mozinfo.os = os_
|
||||
mozinfo.bits = bits
|
||||
mocked_input = mocker.patch('mozregression.config.input')
|
||||
mocked_input = mocker.patch("mozregression.config.input")
|
||||
mocked_input.return_value = ""
|
||||
mocked_input.side_effect = inputs
|
||||
conf_path = os.path.join(tmp, 'conf.cfg')
|
||||
conf_path = os.path.join(tmp, "conf.cfg")
|
||||
if not conf_dir_exists:
|
||||
mozfile.remove(conf_path)
|
||||
write_conf(conf_path)
|
||||
if 'persist' in results and results['persist'] is None:
|
||||
if "persist" in results and results["persist"] is None:
|
||||
# default persist is base on the directory of the conf file
|
||||
results['persist'] = os.path.join(tmp, 'persist')
|
||||
results["persist"] = os.path.join(tmp, "persist")
|
||||
conf = get_defaults(conf_path)
|
||||
for key in results:
|
||||
assert conf[key] == results[key]
|
||||
|
@ -50,9 +62,9 @@ def test_write_conf(tmp, mocker, os_, bits, inputs, conf_dir_exists, results):
|
|||
|
||||
|
||||
def test_write_existing_conf(tmp, mocker):
|
||||
mocked_input = mocker.patch('mozregression.config.input')
|
||||
mocked_input = mocker.patch("mozregression.config.input")
|
||||
mocked_input.return_value = ""
|
||||
conf_path = os.path.join(tmp, 'conf.cfg')
|
||||
conf_path = os.path.join(tmp, "conf.cfg")
|
||||
write_conf(conf_path)
|
||||
results = get_defaults(conf_path)
|
||||
assert results
|
||||
|
|
|
@ -3,12 +3,14 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import absolute_import
|
||||
import unittest
|
||||
import tempfile
|
||||
import shutil
|
||||
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
import time
|
||||
from mock import Mock, patch, ANY
|
||||
import unittest
|
||||
|
||||
from mock import ANY, Mock, patch
|
||||
|
||||
from mozregression import download_manager
|
||||
|
||||
|
@ -28,7 +30,7 @@ def mock_response(response, data, wait=0):
|
|||
rest = rest[chunk_size:]
|
||||
yield chunk
|
||||
|
||||
response.headers = {'Content-length': str(len(data))}
|
||||
response.headers = {"Content-length": str(len(data))}
|
||||
response.iter_content = iter_content
|
||||
|
||||
|
||||
|
@ -38,24 +40,27 @@ class TestDownload(unittest.TestCase):
|
|||
self.addCleanup(shutil.rmtree, self.tempdir)
|
||||
self.finished = Mock()
|
||||
self.session, self.session_response = mock_session()
|
||||
self.tempfile = os.path.join(self.tempdir, 'dest')
|
||||
self.dl = download_manager.Download('http://url', self.tempfile,
|
||||
self.tempfile = os.path.join(self.tempdir, "dest")
|
||||
self.dl = download_manager.Download(
|
||||
"http://url",
|
||||
self.tempfile,
|
||||
finished_callback=self.finished,
|
||||
chunk_size=4,
|
||||
session=self.session)
|
||||
session=self.session,
|
||||
)
|
||||
|
||||
def test_creation(self):
|
||||
self.assertFalse(self.dl.is_canceled())
|
||||
self.assertFalse(self.dl.is_running())
|
||||
self.assertIsNone(self.dl.error())
|
||||
self.assertEqual(self.dl.get_url(), 'http://url')
|
||||
self.assertEqual(self.dl.get_url(), "http://url")
|
||||
self.assertEqual(self.dl.get_dest(), self.tempfile)
|
||||
|
||||
def create_response(self, data, wait=0):
|
||||
mock_response(self.session_response, data, wait)
|
||||
|
||||
def test_download(self):
|
||||
self.create_response(b'1234' * 4, 0.01)
|
||||
self.create_response(b"1234" * 4, 0.01)
|
||||
|
||||
# no file present yet
|
||||
self.assertFalse(os.path.exists(self.tempfile))
|
||||
|
@ -68,10 +73,10 @@ class TestDownload(unittest.TestCase):
|
|||
self.finished.assert_called_with(self.dl)
|
||||
# file has been downloaded
|
||||
with open(self.tempfile) as f:
|
||||
self.assertEqual(f.read(), '1234' * 4)
|
||||
self.assertEqual(f.read(), "1234" * 4)
|
||||
|
||||
def test_download_cancel(self):
|
||||
self.create_response(b'1234' * 1000, wait=0.01)
|
||||
self.create_response(b"1234" * 1000, wait=0.01)
|
||||
|
||||
start = time.time()
|
||||
self.dl.start()
|
||||
|
@ -98,27 +103,30 @@ class TestDownload(unittest.TestCase):
|
|||
def update_progress(_dl, current, total):
|
||||
data.append((_dl, current, total))
|
||||
|
||||
self.create_response(b'1234' * 4)
|
||||
self.create_response(b"1234" * 4)
|
||||
|
||||
self.dl.set_progress(update_progress)
|
||||
self.dl.start()
|
||||
self.dl.wait()
|
||||
|
||||
self.assertEqual(data, [
|
||||
self.assertEqual(
|
||||
data,
|
||||
[
|
||||
(self.dl, 0, 16),
|
||||
(self.dl, 4, 16),
|
||||
(self.dl, 8, 16),
|
||||
(self.dl, 12, 16),
|
||||
(self.dl, 16, 16),
|
||||
])
|
||||
],
|
||||
)
|
||||
# file has been downloaded
|
||||
with open(self.tempfile) as f:
|
||||
self.assertEqual(f.read(), '1234' * 4)
|
||||
self.assertEqual(f.read(), "1234" * 4)
|
||||
# finished callback was called
|
||||
self.finished.assert_called_with(self.dl)
|
||||
|
||||
def test_download_error_in_thread(self):
|
||||
self.session_response.headers = {'Content-length': '24'}
|
||||
self.session_response.headers = {"Content-length": "24"}
|
||||
self.session_response.iter_content.side_effect = IOError
|
||||
|
||||
self.dl.start()
|
||||
|
@ -132,10 +140,10 @@ class TestDownload(unittest.TestCase):
|
|||
def test_wait_does_not_block_on_exception(self):
|
||||
# this test the case when a user may hit CTRL-C for example
|
||||
# during a dl.wait() call.
|
||||
self.create_response(b'1234' * 1000, wait=0.01)
|
||||
self.create_response(b"1234" * 1000, wait=0.01)
|
||||
|
||||
original_join = self.dl.thread.join
|
||||
it = iter('123')
|
||||
it = iter("123")
|
||||
|
||||
def join(timeout=None):
|
||||
next(it) # will throw StopIteration after a few calls
|
||||
|
@ -178,17 +186,17 @@ class TestDownloadManager(unittest.TestCase):
|
|||
return self.dl_manager.download(url, fname)
|
||||
|
||||
def test_download(self):
|
||||
dl1 = self.do_download('http://foo', 'foo', b'hello' * 4, wait=0.02)
|
||||
dl1 = self.do_download("http://foo", "foo", b"hello" * 4, wait=0.02)
|
||||
self.assertIsInstance(dl1, download_manager.Download)
|
||||
self.assertTrue(dl1.is_running())
|
||||
|
||||
# with the same fname, no new download is started. The same instance
|
||||
# is returned since the download is running.
|
||||
dl2 = self.do_download('http://bar', 'foo', b'hello2' * 4, wait=0.02)
|
||||
dl2 = self.do_download("http://bar", "foo", b"hello2" * 4, wait=0.02)
|
||||
self.assertEqual(dl1, dl2)
|
||||
|
||||
# starting a download with another fname will trigger a new download
|
||||
dl3 = self.do_download('http://bar', 'foo2', b'hello you' * 4)
|
||||
dl3 = self.do_download("http://bar", "foo2", b"hello you" * 4)
|
||||
self.assertIsInstance(dl3, download_manager.Download)
|
||||
self.assertNotEqual(dl3, dl1)
|
||||
|
||||
|
@ -197,28 +205,30 @@ class TestDownloadManager(unittest.TestCase):
|
|||
dl1.wait()
|
||||
|
||||
# now if we try to download a fname that exists, None is returned
|
||||
dl4 = self.do_download('http://bar', 'foo', b'hello2' * 4, wait=0.02)
|
||||
dl4 = self.do_download("http://bar", "foo", b"hello2" * 4, wait=0.02)
|
||||
self.assertIsNone(dl4)
|
||||
|
||||
# downloaded files are what is expected
|
||||
def content(fname):
|
||||
with open(os.path.join(self.tempdir, fname)) as f:
|
||||
return f.read()
|
||||
self.assertEqual(content('foo'), 'hello' * 4)
|
||||
self.assertEqual(content('foo2'), 'hello you' * 4)
|
||||
|
||||
self.assertEqual(content("foo"), "hello" * 4)
|
||||
self.assertEqual(content("foo2"), "hello you" * 4)
|
||||
|
||||
# download instances are removed from the manager (internal test)
|
||||
self.assertEqual(self.dl_manager._downloads, {})
|
||||
|
||||
def test_cancel(self):
|
||||
dl1 = self.do_download('http://foo', 'foo', b'foo' * 50000, wait=0.02)
|
||||
dl2 = self.do_download('http://foo', 'bar', b'bar' * 50000, wait=0.02)
|
||||
dl3 = self.do_download('http://foo', 'foobar', b'foobar' * 4)
|
||||
dl1 = self.do_download("http://foo", "foo", b"foo" * 50000, wait=0.02)
|
||||
dl2 = self.do_download("http://foo", "bar", b"bar" * 50000, wait=0.02)
|
||||
dl3 = self.do_download("http://foo", "foobar", b"foobar" * 4)
|
||||
|
||||
# let's cancel only one
|
||||
def cancel_if(dl):
|
||||
if os.path.basename(dl.get_dest()) == 'foo':
|
||||
if os.path.basename(dl.get_dest()) == "foo":
|
||||
return True
|
||||
|
||||
self.dl_manager.cancel(cancel_if=cancel_if)
|
||||
|
||||
self.assertTrue(dl1.is_canceled())
|
||||
|
@ -243,8 +253,8 @@ class TestDownloadManager(unittest.TestCase):
|
|||
# at the end, only dl3 has been downloaded
|
||||
self.assertEqual(os.listdir(self.tempdir), ["foobar"])
|
||||
|
||||
with open(os.path.join(self.tempdir, 'foobar')) as f:
|
||||
self.assertEqual(f.read(), 'foobar' * 4)
|
||||
with open(os.path.join(self.tempdir, "foobar")) as f:
|
||||
self.assertEqual(f.read(), "foobar" * 4)
|
||||
|
||||
# download instances are removed from the manager (internal test)
|
||||
self.assertEqual(self.dl_manager._downloads, {})
|
||||
|
@ -261,46 +271,44 @@ class TestDownloadProgress(unittest.TestCase):
|
|||
class TestBuildDownloadManager(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.session, self.session_response = mock_session()
|
||||
self.dl_manager = \
|
||||
download_manager.BuildDownloadManager('dest',
|
||||
session=self.session)
|
||||
self.dl_manager = download_manager.BuildDownloadManager("dest", session=self.session)
|
||||
self.dl_manager.logger = Mock()
|
||||
|
||||
def test__extract_download_info(self):
|
||||
url, fname = self.dl_manager._extract_download_info(Mock(**{
|
||||
'build_url': 'http://some/thing',
|
||||
'persist_filename': '2015-01-03--my-repo--thing'
|
||||
}))
|
||||
self.assertEqual(url, 'http://some/thing')
|
||||
self.assertEqual(fname, '2015-01-03--my-repo--thing')
|
||||
url, fname = self.dl_manager._extract_download_info(
|
||||
Mock(
|
||||
**{
|
||||
"build_url": "http://some/thing",
|
||||
"persist_filename": "2015-01-03--my-repo--thing",
|
||||
}
|
||||
)
|
||||
)
|
||||
self.assertEqual(url, "http://some/thing")
|
||||
self.assertEqual(fname, "2015-01-03--my-repo--thing")
|
||||
|
||||
@patch("mozregression.download_manager.BuildDownloadManager."
|
||||
"_extract_download_info")
|
||||
@patch("mozregression.download_manager.BuildDownloadManager." "_extract_download_info")
|
||||
@patch("mozregression.download_manager.BuildDownloadManager.download")
|
||||
def test_download_in_background(self, download, extract):
|
||||
extract.return_value = ('http://foo/bar', 'myfile')
|
||||
extract.return_value = ("http://foo/bar", "myfile")
|
||||
download.return_value = ANY
|
||||
|
||||
result = self.dl_manager.download_in_background({'build': 'info'})
|
||||
result = self.dl_manager.download_in_background({"build": "info"})
|
||||
|
||||
extract.assert_called_with({'build': 'info'})
|
||||
download.assert_called_with('http://foo/bar', 'myfile')
|
||||
self.assertIn('myfile', self.dl_manager._downloads_bg)
|
||||
extract.assert_called_with({"build": "info"})
|
||||
download.assert_called_with("http://foo/bar", "myfile")
|
||||
self.assertIn("myfile", self.dl_manager._downloads_bg)
|
||||
self.assertEqual(result, ANY)
|
||||
|
||||
@patch('mozregression.download_manager.LOG')
|
||||
@patch("mozregression.download_manager.BuildDownloadManager."
|
||||
"_extract_download_info")
|
||||
@patch("mozregression.download_manager.LOG")
|
||||
@patch("mozregression.download_manager.BuildDownloadManager." "_extract_download_info")
|
||||
def _test_focus_download(self, other_canceled, extract, log):
|
||||
extract.return_value = ('http://foo/bar', 'myfile')
|
||||
current_dest = os.path.join('dest', 'myfile')
|
||||
other_dest = os.path.join('dest', 'otherfile')
|
||||
curent_download = download_manager.Download('http://url',
|
||||
current_dest)
|
||||
extract.return_value = ("http://foo/bar", "myfile")
|
||||
current_dest = os.path.join("dest", "myfile")
|
||||
other_dest = os.path.join("dest", "otherfile")
|
||||
curent_download = download_manager.Download("http://url", current_dest)
|
||||
curent_download.wait = Mock()
|
||||
curent_download.set_progress = Mock()
|
||||
other_download = download_manager.Download('http://url',
|
||||
other_dest)
|
||||
other_download = download_manager.Download("http://url", other_dest)
|
||||
# fake some download activity
|
||||
self.dl_manager._downloads = {
|
||||
current_dest: curent_download,
|
||||
|
@ -309,7 +317,7 @@ class TestBuildDownloadManager(unittest.TestCase):
|
|||
curent_download.is_running = Mock(return_value=True)
|
||||
other_download.is_running = Mock(return_value=True)
|
||||
|
||||
build_info = Mock(build='info')
|
||||
build_info = Mock(build="info")
|
||||
result = self.dl_manager.focus_download(build_info)
|
||||
|
||||
self.assertFalse(curent_download.is_canceled())
|
||||
|
@ -317,8 +325,7 @@ class TestBuildDownloadManager(unittest.TestCase):
|
|||
|
||||
self.assertEqual(other_download.is_canceled(), other_canceled)
|
||||
|
||||
log.info.assert_called_with(
|
||||
"Downloading build from: http://foo/bar")
|
||||
log.info.assert_called_with("Downloading build from: http://foo/bar")
|
||||
|
||||
self.assertEqual(result, current_dest)
|
||||
self.assertEqual(result, build_info.build_file)
|
||||
|
@ -330,23 +337,21 @@ class TestBuildDownloadManager(unittest.TestCase):
|
|||
self.dl_manager.background_dl_policy = "keep"
|
||||
self._test_focus_download(False)
|
||||
|
||||
@patch('mozregression.download_manager.LOG')
|
||||
@patch("mozregression.download_manager.BuildDownloadManager."
|
||||
"_extract_download_info")
|
||||
@patch("mozregression.download_manager.LOG")
|
||||
@patch("mozregression.download_manager.BuildDownloadManager." "_extract_download_info")
|
||||
@patch("mozregression.download_manager.BuildDownloadManager.download")
|
||||
def test_focus_download_file_already_exists(self, download, extract, log):
|
||||
extract.return_value = ('http://foo/bar', 'myfile')
|
||||
extract.return_value = ("http://foo/bar", "myfile")
|
||||
download.return_value = None
|
||||
|
||||
# fake that we downloaded that in background
|
||||
self.dl_manager._downloads_bg.add('myfile')
|
||||
self.dl_manager._downloads_bg.add("myfile")
|
||||
|
||||
build_info = Mock(build='info')
|
||||
build_info = Mock(build="info")
|
||||
result = self.dl_manager.focus_download(build_info)
|
||||
|
||||
dest_file = os.path.join('dest', 'myfile')
|
||||
log.info.assert_called_with(
|
||||
"Using local file: %s (downloaded in background)" % dest_file)
|
||||
dest_file = os.path.join("dest", "myfile")
|
||||
log.info.assert_called_with("Using local file: %s (downloaded in background)" % dest_file)
|
||||
|
||||
self.assertEqual(result, dest_file)
|
||||
self.assertEqual(result, build_info.build_file)
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
import unittest
|
||||
import datetime
|
||||
from mock import patch, Mock
|
||||
import re
|
||||
import time
|
||||
import unittest
|
||||
|
||||
from mock import Mock, patch
|
||||
|
||||
from mozregression import config, errors, fetch_build_info, fetch_configs
|
||||
|
||||
from mozregression import config, fetch_build_info, fetch_configs, errors
|
||||
from .test_fetch_configs import create_push
|
||||
|
||||
|
||||
class TestInfoFetcher(unittest.TestCase):
|
||||
def setUp(self):
|
||||
fetch_config = fetch_configs.create_config('firefox', 'linux', 64,
|
||||
'x86_64')
|
||||
fetch_config = fetch_configs.create_config("firefox", "linux", 64, "x86_64")
|
||||
self.info_fetcher = fetch_build_info.InfoFetcher(fetch_config)
|
||||
|
||||
@patch('requests.get')
|
||||
@patch("requests.get")
|
||||
def test__fetch_txt_info(self, get):
|
||||
response = Mock(text="20141101030205\nhttps://hg.mozilla.org/\
|
||||
mozilla-central/rev/b695d9575654\n")
|
||||
response = Mock(
|
||||
text="20141101030205\nhttps://hg.mozilla.org/\
|
||||
mozilla-central/rev/b695d9575654\n"
|
||||
)
|
||||
get.return_value = response
|
||||
expected = {
|
||||
'repository': 'https://hg.mozilla.org/mozilla-central',
|
||||
'changeset': 'b695d9575654',
|
||||
"repository": "https://hg.mozilla.org/mozilla-central",
|
||||
"changeset": "b695d9575654",
|
||||
}
|
||||
self.assertEqual(self.info_fetcher._fetch_txt_info('http://foo.txt'),
|
||||
expected)
|
||||
self.assertEqual(self.info_fetcher._fetch_txt_info("http://foo.txt"), expected)
|
||||
|
||||
@patch('requests.get')
|
||||
@patch("requests.get")
|
||||
def test__fetch_txt_info_old_format(self, get):
|
||||
response = Mock(text="20110126030333 e0fc18b3bc41\n")
|
||||
get.return_value = response
|
||||
expected = {
|
||||
'changeset': 'e0fc18b3bc41',
|
||||
"changeset": "e0fc18b3bc41",
|
||||
}
|
||||
self.assertEqual(self.info_fetcher._fetch_txt_info('http://foo.txt'),
|
||||
expected)
|
||||
self.assertEqual(self.info_fetcher._fetch_txt_info("http://foo.txt"), expected)
|
||||
|
||||
|
||||
class TestNightlyInfoFetcher(unittest.TestCase):
|
||||
def setUp(self):
|
||||
fetch_config = fetch_configs.create_config('firefox', 'linux', 64,
|
||||
'x86_64')
|
||||
fetch_config = fetch_configs.create_config("firefox", "linux", 64, "x86_64")
|
||||
self.info_fetcher = fetch_build_info.NightlyInfoFetcher(fetch_config)
|
||||
|
||||
@patch('mozregression.fetch_build_info.url_links')
|
||||
@patch("mozregression.fetch_build_info.url_links")
|
||||
def test__find_build_info_from_url(self, url_links):
|
||||
url_links.return_value = [
|
||||
'file1.txt.gz',
|
||||
'file2.txt',
|
||||
'firefox01linux-x86_64.txt',
|
||||
'firefox01linux-x86_64.tar.bz2',
|
||||
"file1.txt.gz",
|
||||
"file2.txt",
|
||||
"firefox01linux-x86_64.txt",
|
||||
"firefox01linux-x86_64.tar.bz2",
|
||||
]
|
||||
expected = {
|
||||
'build_txt_url': 'http://foo/firefox01linux-x86_64.txt',
|
||||
'build_url': 'http://foo/firefox01linux-x86_64.tar.bz2',
|
||||
"build_txt_url": "http://foo/firefox01linux-x86_64.txt",
|
||||
"build_url": "http://foo/firefox01linux-x86_64.tar.bz2",
|
||||
}
|
||||
builds = []
|
||||
self.info_fetcher._fetch_build_info_from_url('http://foo', 0, builds)
|
||||
self.info_fetcher._fetch_build_info_from_url("http://foo", 0, builds)
|
||||
self.assertEqual(builds, [(0, expected)])
|
||||
|
||||
@patch('mozregression.fetch_build_info.url_links')
|
||||
@patch("mozregression.fetch_build_info.url_links")
|
||||
def test__find_build_info_incomplete_data_raises_exception(self, url_links):
|
||||
# We want to find a valid match for one of the build file regexes,
|
||||
# build_info_regex. But we will make the build filename regex fail. This
|
||||
|
@ -70,7 +70,7 @@ class TestNightlyInfoFetcher(unittest.TestCase):
|
|||
# regex.
|
||||
url_links.return_value = [
|
||||
"validinfofilename.txt",
|
||||
"invalidbuildfilename.tar.bz2"
|
||||
"invalidbuildfilename.tar.bz2",
|
||||
]
|
||||
# build_regex doesn't match any of the files in the web directory.
|
||||
self.info_fetcher.build_regex = re.compile("xxx")
|
||||
|
@ -80,47 +80,46 @@ class TestNightlyInfoFetcher(unittest.TestCase):
|
|||
with self.assertRaises(errors.BuildInfoNotFound):
|
||||
self.info_fetcher._fetch_build_info_from_url("some-url", 1, [])
|
||||
|
||||
@patch('mozregression.fetch_build_info.url_links')
|
||||
@patch("mozregression.fetch_build_info.url_links")
|
||||
def test__get_url(self, url_links):
|
||||
url_links.return_value = [
|
||||
'2014-11-01-03-02-05-mozilla-central/',
|
||||
'2014-11-01-03-02-05-foo/',
|
||||
'foo',
|
||||
'bar/'
|
||||
"2014-11-01-03-02-05-mozilla-central/",
|
||||
"2014-11-01-03-02-05-foo/",
|
||||
"foo",
|
||||
"bar/",
|
||||
]
|
||||
urls = self.info_fetcher._get_urls(datetime.date(2014, 11, 0o1))
|
||||
self.assertEqual(
|
||||
urls[0],
|
||||
fetch_configs.ARCHIVE_BASE_URL +
|
||||
'/firefox/nightly/2014/11/2014-11-01-03-02-05-mozilla-central/')
|
||||
fetch_configs.ARCHIVE_BASE_URL
|
||||
+ "/firefox/nightly/2014/11/2014-11-01-03-02-05-mozilla-central/",
|
||||
)
|
||||
urls = self.info_fetcher._get_urls(datetime.date(2014, 11, 0o2))
|
||||
self.assertEqual(urls, [])
|
||||
|
||||
def test_find_build_info(self):
|
||||
get_urls = self.info_fetcher._get_urls = Mock(return_value=[
|
||||
'https://archive.mozilla.org/pub/mozilla.org/\
|
||||
bar/nightly/2014/11/2014-11-15-08-02-05-mozilla-central/',
|
||||
'https://archive.mozilla.org/pub/mozilla.org/\
|
||||
bar/nightly/2014/11/2014-11-15-04-02-05-mozilla-central/',
|
||||
'https://archive.mozilla.org/pub/mozilla.org/\
|
||||
bar/nightly/2014/11/2014-11-15-03-02-05-mozilla-central',
|
||||
'https://archive.mozilla.org/pub/mozilla.org/\
|
||||
bar/nightly/2014/11/2014-11-15-02-02-05-mozilla-central/',
|
||||
'https://archive.mozilla.org/pub/mozilla.org/\
|
||||
bar/nightly/2014/11/2014-11-15-01-02-05-mozilla-central/',
|
||||
])
|
||||
get_urls = self.info_fetcher._get_urls = Mock(
|
||||
return_value=[
|
||||
"https://archive.mozilla.org/pub/mozilla.org/\
|
||||
bar/nightly/2014/11/2014-11-15-08-02-05-mozilla-central/",
|
||||
"https://archive.mozilla.org/pub/mozilla.org/\
|
||||
bar/nightly/2014/11/2014-11-15-04-02-05-mozilla-central/",
|
||||
"https://archive.mozilla.org/pub/mozilla.org/\
|
||||
bar/nightly/2014/11/2014-11-15-03-02-05-mozilla-central",
|
||||
"https://archive.mozilla.org/pub/mozilla.org/\
|
||||
bar/nightly/2014/11/2014-11-15-02-02-05-mozilla-central/",
|
||||
"https://archive.mozilla.org/pub/mozilla.org/\
|
||||
bar/nightly/2014/11/2014-11-15-01-02-05-mozilla-central/",
|
||||
]
|
||||
)
|
||||
|
||||
def my_find_build_info(url, index, lst):
|
||||
# say only the last build url is invalid
|
||||
if url in get_urls.return_value[:-1]:
|
||||
return
|
||||
lst.append((index, {
|
||||
'build_txt_url': url,
|
||||
'build_url': url,
|
||||
}))
|
||||
self.info_fetcher._fetch_build_info_from_url = Mock(
|
||||
side_effect=my_find_build_info
|
||||
)
|
||||
lst.append((index, {"build_txt_url": url, "build_url": url}))
|
||||
|
||||
self.info_fetcher._fetch_build_info_from_url = Mock(side_effect=my_find_build_info)
|
||||
self.info_fetcher._fetch_txt_info = Mock(return_value={})
|
||||
result = self.info_fetcher.find_build_info(datetime.date(2014, 11, 15))
|
||||
# we must have found the last build url valid
|
||||
|
@ -134,72 +133,64 @@ bar/nightly/2014/11/2014-11-15-01-02-05-mozilla-central/',
|
|||
|
||||
class TestIntegrationInfoFetcher(unittest.TestCase):
|
||||
def setUp(self):
|
||||
fetch_config = fetch_configs.create_config('firefox', 'linux', 64,
|
||||
'x86_64')
|
||||
fetch_config = fetch_configs.create_config("firefox", "linux", 64, "x86_64")
|
||||
self.info_fetcher = fetch_build_info.IntegrationInfoFetcher(fetch_config)
|
||||
|
||||
@patch('taskcluster.Index')
|
||||
@patch('taskcluster.Queue')
|
||||
@patch("taskcluster.Index")
|
||||
@patch("taskcluster.Queue")
|
||||
def test_find_build_info(self, Queue, Index):
|
||||
Index.return_value.findTask.return_value = {'taskId': 'task1'}
|
||||
Index.return_value.findTask.return_value = {"taskId": "task1"}
|
||||
Queue.return_value.status.return_value = {
|
||||
"status": {"runs": [{
|
||||
"state": "completed",
|
||||
"runId": 0,
|
||||
"resolved": '2015-06-01T22:13:02.115Z'
|
||||
}]}
|
||||
"status": {
|
||||
"runs": [{"state": "completed", "runId": 0, "resolved": "2015-06-01T22:13:02.115Z"}]
|
||||
}
|
||||
}
|
||||
Queue.return_value.listArtifacts.return_value = {
|
||||
"artifacts": [
|
||||
# return two valid artifact names
|
||||
{'name': 'firefox-42.0a1.en-US.linux-x86_64.tar.bz2'},
|
||||
{'name': 'firefox-42.0a1.en-US.linux-x86_64.txt'},
|
||||
{"name": "firefox-42.0a1.en-US.linux-x86_64.tar.bz2"},
|
||||
{"name": "firefox-42.0a1.en-US.linux-x86_64.txt"},
|
||||
]
|
||||
}
|
||||
Queue.return_value.buildUrl.return_value = (
|
||||
'http://firefox-42.0a1.en-US.linux-x86_64.tar.bz2'
|
||||
"http://firefox-42.0a1.en-US.linux-x86_64.tar.bz2"
|
||||
)
|
||||
self.info_fetcher._fetch_txt_info = \
|
||||
Mock(return_value={'changeset': '123456789'})
|
||||
self.info_fetcher._fetch_txt_info = Mock(return_value={"changeset": "123456789"})
|
||||
|
||||
# test that we start searching using the correct tc root url
|
||||
for push_timestamp in [
|
||||
0,
|
||||
time.mktime(
|
||||
config.TC_ROOT_URL_MIGRATION_FLAG_DATE.timetuple()) + 100
|
||||
time.mktime(config.TC_ROOT_URL_MIGRATION_FLAG_DATE.timetuple()) + 100,
|
||||
]:
|
||||
result = self.info_fetcher.find_build_info(
|
||||
create_push('123456789', push_timestamp))
|
||||
result = self.info_fetcher.find_build_info(create_push("123456789", push_timestamp))
|
||||
if push_timestamp == 0:
|
||||
Index.assert_called_with({'rootUrl': config.OLD_TC_ROOT_URL})
|
||||
Index.assert_called_with({"rootUrl": config.OLD_TC_ROOT_URL})
|
||||
else:
|
||||
Index.assert_called_with({'rootUrl': config.TC_ROOT_URL})
|
||||
self.assertEqual(result.build_url,
|
||||
'http://firefox-42.0a1.en-US.linux-x86_64.tar.bz2')
|
||||
self.assertEqual(result.changeset, '123456789')
|
||||
Index.assert_called_with({"rootUrl": config.TC_ROOT_URL})
|
||||
self.assertEqual(result.build_url, "http://firefox-42.0a1.en-US.linux-x86_64.tar.bz2")
|
||||
self.assertEqual(result.changeset, "123456789")
|
||||
self.assertEqual(result.build_type, "integration")
|
||||
|
||||
@patch('taskcluster.Index')
|
||||
@patch("taskcluster.Index")
|
||||
def test_find_build_info_no_task(self, Index):
|
||||
Index.findTask = Mock(
|
||||
side_effect=fetch_build_info.TaskclusterFailure
|
||||
)
|
||||
Index.findTask = Mock(side_effect=fetch_build_info.TaskclusterFailure)
|
||||
with self.assertRaises(errors.BuildInfoNotFound):
|
||||
self.info_fetcher.find_build_info(
|
||||
create_push('123456789', 1))
|
||||
self.info_fetcher.find_build_info(create_push("123456789", 1))
|
||||
|
||||
@patch('taskcluster.Index')
|
||||
@patch('taskcluster.Queue')
|
||||
@patch("taskcluster.Index")
|
||||
@patch("taskcluster.Queue")
|
||||
def test_get_valid_build_no_artifacts(self, Queue, Index):
|
||||
def find_task(route):
|
||||
return {'taskId': 'task1'}
|
||||
return {"taskId": "task1"}
|
||||
|
||||
def status(task_id):
|
||||
return {"status": {"runs": [{
|
||||
"state": "completed",
|
||||
"runId": 0,
|
||||
"resolved": '2015-06-01T22:13:02.115Z'
|
||||
}]}}
|
||||
return {
|
||||
"status": {
|
||||
"runs": [
|
||||
{"state": "completed", "runId": 0, "resolved": "2015-06-01T22:13:02.115Z"}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
def list_artifacts(taskid, run_id):
|
||||
return {"artifacts": []}
|
||||
|
@ -209,12 +200,11 @@ class TestIntegrationInfoFetcher(unittest.TestCase):
|
|||
Queue.listArtifacts = list_artifacts
|
||||
|
||||
with self.assertRaises(errors.BuildInfoNotFound):
|
||||
self.info_fetcher.find_build_info(
|
||||
create_push('123456789', 1))
|
||||
self.info_fetcher.find_build_info(create_push("123456789", 1))
|
||||
|
||||
@patch('mozregression.json_pushes.JsonPushes.push')
|
||||
@patch("mozregression.json_pushes.JsonPushes.push")
|
||||
def test_find_build_info_check_changeset_error(self, push):
|
||||
push.side_effect = errors.MozRegressionError
|
||||
with self.assertRaises(errors.BuildInfoNotFound):
|
||||
self.info_fetcher.find_build_info('123456789',)
|
||||
push.assert_called_with('123456789')
|
||||
self.info_fetcher.find_build_info("123456789",)
|
||||
push.assert_called_with("123456789")
|
||||
|
|
|
@ -1,43 +1,43 @@
|
|||
from __future__ import absolute_import
|
||||
import unittest
|
||||
|
||||
import datetime
|
||||
import re
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
|
||||
from mozregression.dates import to_utc_timestamp
|
||||
from mozregression.json_pushes import Push
|
||||
from mozregression.fetch_configs import (FirefoxConfig, create_config, errors,
|
||||
get_build_regex, ARCHIVE_BASE_URL,
|
||||
from mozregression.fetch_configs import (
|
||||
ARCHIVE_BASE_URL,
|
||||
TIMESTAMP_FENNEC_API_15,
|
||||
TIMESTAMP_FENNEC_API_16)
|
||||
|
||||
|
||||
TIMESTAMP_TEST = to_utc_timestamp(
|
||||
datetime.datetime(2017, 11, 14, 0, 0, 0)
|
||||
TIMESTAMP_FENNEC_API_16,
|
||||
FirefoxConfig,
|
||||
create_config,
|
||||
errors,
|
||||
get_build_regex,
|
||||
)
|
||||
from mozregression.json_pushes import Push
|
||||
|
||||
TIMESTAMP_TEST = to_utc_timestamp(datetime.datetime(2017, 11, 14, 0, 0, 0))
|
||||
|
||||
|
||||
def create_push(chset, timestamp):
|
||||
return Push(1, {
|
||||
'changesets': [chset],
|
||||
'date': timestamp
|
||||
})
|
||||
return Push(1, {"changesets": [chset], "date": timestamp})
|
||||
|
||||
|
||||
class TestFirefoxConfigLinux64(unittest.TestCase):
|
||||
app_name = 'firefox'
|
||||
os = 'linux'
|
||||
app_name = "firefox"
|
||||
os = "linux"
|
||||
bits = 64
|
||||
processor = 'x86_64'
|
||||
processor = "x86_64"
|
||||
|
||||
build_examples = ['firefox-38.0a1.en-US.linux-x86_64.tar.bz2']
|
||||
build_info_examples = ['firefox-38.0a1.en-US.linux-x86_64.txt']
|
||||
build_examples = ["firefox-38.0a1.en-US.linux-x86_64.tar.bz2"]
|
||||
build_info_examples = ["firefox-38.0a1.en-US.linux-x86_64.txt"]
|
||||
|
||||
instance_type = FirefoxConfig
|
||||
|
||||
def setUp(self):
|
||||
self.conf = create_config(self.app_name, self.os, self.bits,
|
||||
self.processor)
|
||||
self.conf = create_config(self.app_name, self.os, self.bits, self.processor)
|
||||
|
||||
def test_instance(self):
|
||||
self.assertIsInstance(self.conf, self.instance_type)
|
||||
|
@ -53,153 +53,137 @@ class TestFirefoxConfigLinux64(unittest.TestCase):
|
|||
self.assertIsNotNone(res)
|
||||
|
||||
def test_get_nighly_base_url(self):
|
||||
base_url = self.conf.get_nighly_base_url(datetime.date(2008,
|
||||
6, 27))
|
||||
self.assertEqual(base_url,
|
||||
ARCHIVE_BASE_URL + '/firefox/nightly/2008/06/')
|
||||
base_url = self.conf.get_nighly_base_url(datetime.date(2008, 6, 27))
|
||||
self.assertEqual(base_url, ARCHIVE_BASE_URL + "/firefox/nightly/2008/06/")
|
||||
|
||||
def test_get_nightly_base_url_with_specific_base(self):
|
||||
self.conf.set_base_url("http://ftp-origin-scl3.mozilla.org/pub/")
|
||||
self.assertEqual(
|
||||
"http://ftp-origin-scl3.mozilla.org/pub/firefox/nightly/2008/06/",
|
||||
self.conf.get_nighly_base_url(datetime.date(2008, 6, 27))
|
||||
self.conf.get_nighly_base_url(datetime.date(2008, 6, 27)),
|
||||
)
|
||||
|
||||
def test_nightly_repo_regex(self):
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2008,
|
||||
6, 15))
|
||||
self.assertEqual(repo_regex, '^2008-06-15-[\\d-]+trunk/$')
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2008,
|
||||
6, 27))
|
||||
self.assertEqual(repo_regex, '^2008-06-27-[\\d-]+mozilla-central/$')
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2008, 6, 15))
|
||||
self.assertEqual(repo_regex, "^2008-06-15-[\\d-]+trunk/$")
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2008, 6, 27))
|
||||
self.assertEqual(repo_regex, "^2008-06-27-[\\d-]+mozilla-central/$")
|
||||
# test with a datetime instance (buildid)
|
||||
repo_regex = self.conf.get_nightly_repo_regex(
|
||||
datetime.datetime(2015, 11, 27, 6, 5, 58))
|
||||
self.assertEqual(repo_regex, '^2015-11-27-06-05-58-mozilla-central/$')
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.datetime(2015, 11, 27, 6, 5, 58))
|
||||
self.assertEqual(repo_regex, "^2015-11-27-06-05-58-mozilla-central/$")
|
||||
|
||||
def test_set_repo(self):
|
||||
self.conf.set_repo('foo-bar')
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2008,
|
||||
6, 27))
|
||||
self.assertEqual(repo_regex, '^2008-06-27-[\\d-]+foo-bar/$')
|
||||
self.conf.set_repo("foo-bar")
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2008, 6, 27))
|
||||
self.assertEqual(repo_regex, "^2008-06-27-[\\d-]+foo-bar/$")
|
||||
# with a value of None, default is applied
|
||||
self.conf.set_repo(None)
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2008,
|
||||
6, 27))
|
||||
self.assertEqual(repo_regex, '^2008-06-27-[\\d-]+mozilla-central/$')
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2008, 6, 27))
|
||||
self.assertEqual(repo_regex, "^2008-06-27-[\\d-]+mozilla-central/$")
|
||||
|
||||
|
||||
class TestFirefoxConfigLinux32(TestFirefoxConfigLinux64):
|
||||
bits = 32
|
||||
processor = 'x86'
|
||||
build_examples = ['firefox-38.0a1.en-US.linux-i686.tar.bz2']
|
||||
build_info_examples = ['firefox-38.0a1.en-US.linux-i686.txt']
|
||||
processor = "x86"
|
||||
build_examples = ["firefox-38.0a1.en-US.linux-i686.tar.bz2"]
|
||||
build_info_examples = ["firefox-38.0a1.en-US.linux-i686.txt"]
|
||||
|
||||
|
||||
class TestFirefoxConfigWin64(TestFirefoxConfigLinux64):
|
||||
os = 'win'
|
||||
build_examples = ['firefox-38.0a1.en-US.win64-x86_64.zip',
|
||||
'firefox-38.0a1.en-US.win64.zip']
|
||||
build_info_examples = ['firefox-38.0a1.en-US.win64-x86_64.txt',
|
||||
'firefox-38.0a1.en-US.win64.txt']
|
||||
os = "win"
|
||||
build_examples = [
|
||||
"firefox-38.0a1.en-US.win64-x86_64.zip",
|
||||
"firefox-38.0a1.en-US.win64.zip",
|
||||
]
|
||||
build_info_examples = [
|
||||
"firefox-38.0a1.en-US.win64-x86_64.txt",
|
||||
"firefox-38.0a1.en-US.win64.txt",
|
||||
]
|
||||
|
||||
|
||||
class TestFirefoxConfigWin32(TestFirefoxConfigWin64):
|
||||
bits = 32
|
||||
processor = 'x86'
|
||||
build_examples = ['firefox-38.0a1.en-US.win32.zip']
|
||||
build_info_examples = ['firefox-38.0a1.en-US.win32.txt']
|
||||
processor = "x86"
|
||||
build_examples = ["firefox-38.0a1.en-US.win32.zip"]
|
||||
build_info_examples = ["firefox-38.0a1.en-US.win32.txt"]
|
||||
|
||||
|
||||
class TestFirefoxConfigMac(TestFirefoxConfigLinux64):
|
||||
os = 'mac'
|
||||
build_examples = ['firefox-38.0a1.en-US.mac.dmg']
|
||||
build_info_examples = ['firefox-38.0a1.en-US.mac.txt']
|
||||
os = "mac"
|
||||
build_examples = ["firefox-38.0a1.en-US.mac.dmg"]
|
||||
build_info_examples = ["firefox-38.0a1.en-US.mac.txt"]
|
||||
|
||||
|
||||
class TestThunderbirdConfig(unittest.TestCase):
|
||||
os = 'linux'
|
||||
os = "linux"
|
||||
bits = 64
|
||||
processor = 'x86_64'
|
||||
processor = "x86_64"
|
||||
|
||||
def setUp(self):
|
||||
self.conf = create_config('thunderbird', self.os, self.bits,
|
||||
self.processor)
|
||||
self.conf = create_config("thunderbird", self.os, self.bits, self.processor)
|
||||
|
||||
def test_nightly_repo_regex_before_2008_07_26(self):
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2008,
|
||||
7, 25))
|
||||
self.assertEqual(repo_regex, '^2008-07-25-[\\d-]+trunk/$')
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2008, 7, 25))
|
||||
self.assertEqual(repo_regex, "^2008-07-25-[\\d-]+trunk/$")
|
||||
|
||||
def test_nightly_repo_regex_before_2009_01_09(self):
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2009,
|
||||
1, 8))
|
||||
self.assertEqual(repo_regex, '^2009-01-08-[\\d-]+comm-central/$')
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2009, 1, 8))
|
||||
self.assertEqual(repo_regex, "^2009-01-08-[\\d-]+comm-central/$")
|
||||
|
||||
def test_nightly_repo_regex_before_2010_08_21(self):
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2010,
|
||||
8, 20))
|
||||
self.assertEqual(repo_regex, '^2010-08-20-[\\d-]+comm-central-trunk/$')
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2010, 8, 20))
|
||||
self.assertEqual(repo_regex, "^2010-08-20-[\\d-]+comm-central-trunk/$")
|
||||
|
||||
def test_nightly_repo_regex_since_2010_08_21(self):
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2010,
|
||||
8, 21))
|
||||
self.assertEqual(repo_regex, '^2010-08-21-[\\d-]+comm-central/$')
|
||||
repo_regex = self.conf.get_nightly_repo_regex(datetime.date(2010, 8, 21))
|
||||
self.assertEqual(repo_regex, "^2010-08-21-[\\d-]+comm-central/$")
|
||||
|
||||
|
||||
class TestThunderbirdConfigWin(TestThunderbirdConfig):
|
||||
os = 'win'
|
||||
os = "win"
|
||||
|
||||
def test_nightly_repo_regex_before_2008_07_26(self):
|
||||
with self.assertRaises(errors.WinTooOldBuildError):
|
||||
TestThunderbirdConfig.\
|
||||
test_nightly_repo_regex_before_2008_07_26(self)
|
||||
TestThunderbirdConfig.test_nightly_repo_regex_before_2008_07_26(self)
|
||||
|
||||
def test_nightly_repo_regex_before_2009_01_09(self):
|
||||
with self.assertRaises(errors.WinTooOldBuildError):
|
||||
TestThunderbirdConfig.\
|
||||
test_nightly_repo_regex_before_2009_01_09(self)
|
||||
TestThunderbirdConfig.test_nightly_repo_regex_before_2009_01_09(self)
|
||||
|
||||
|
||||
class TestFennecConfig(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.conf = create_config('fennec', 'linux', 64, None)
|
||||
self.conf = create_config("fennec", "linux", 64, None)
|
||||
|
||||
def test_get_nightly_repo_regex(self):
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2014,
|
||||
12, 5))
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2014, 12, 5))
|
||||
self.assertIn("mozilla-central-android", regex)
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2014,
|
||||
12, 10))
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2014, 12, 10))
|
||||
self.assertIn("mozilla-central-android-api-10", regex)
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2015,
|
||||
1, 1))
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2015, 1, 1))
|
||||
self.assertIn("mozilla-central-android-api-11", regex)
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2016,
|
||||
1, 28))
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2016, 1, 28))
|
||||
self.assertIn("mozilla-central-android-api-11", regex)
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2016,
|
||||
1, 29))
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2016, 1, 29))
|
||||
self.assertIn("mozilla-central-android-api-15", regex)
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2017,
|
||||
8, 30))
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2017, 8, 30))
|
||||
self.assertIn("mozilla-central-android-api-16", regex)
|
||||
|
||||
def test_build_regex(self):
|
||||
regex = re.compile(self.conf.build_regex())
|
||||
self.assertTrue(regex.match('fennec-36.0a1.multi.android-arm.apk'))
|
||||
self.assertTrue(regex.match("fennec-36.0a1.multi.android-arm.apk"))
|
||||
|
||||
def test_build_info_regex(self):
|
||||
regex = re.compile(self.conf.build_info_regex())
|
||||
self.assertTrue(regex.match('fennec-36.0a1.multi.android-arm.txt'))
|
||||
self.assertTrue(regex.match("fennec-36.0a1.multi.android-arm.txt"))
|
||||
|
||||
|
||||
class TestFennec23Config(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.conf = create_config('fennec-2.3', 'linux', 64, None)
|
||||
self.conf = create_config("fennec-2.3", "linux", 64, None)
|
||||
|
||||
def test_class_attr_name(self):
|
||||
self.assertEqual(self.conf.app_name, 'fennec')
|
||||
self.assertEqual(self.conf.app_name, "fennec")
|
||||
|
||||
def test_get_nightly_repo_regex(self):
|
||||
regex = self.conf.get_nightly_repo_regex(datetime.date(2014, 12, 5))
|
||||
|
@ -210,127 +194,203 @@ class TestFennec23Config(unittest.TestCase):
|
|||
|
||||
class TestGetBuildUrl(unittest.TestCase):
|
||||
def test_for_linux(self):
|
||||
self.assertEqual(get_build_regex('test', 'linux', 32, 'x86'),
|
||||
r'(target|test.*linux-i686)\.tar.bz2')
|
||||
self.assertEqual(
|
||||
get_build_regex("test", "linux", 32, "x86"), r"(target|test.*linux-i686)\.tar.bz2",
|
||||
)
|
||||
|
||||
self.assertEqual(get_build_regex('test', 'linux', 64, 'x86_64'),
|
||||
r'(target|test.*linux-x86_64)\.tar.bz2')
|
||||
self.assertEqual(
|
||||
get_build_regex("test", "linux", 64, "x86_64"), r"(target|test.*linux-x86_64)\.tar.bz2",
|
||||
)
|
||||
|
||||
self.assertEqual(get_build_regex('test', 'linux', 64, 'x86_64',
|
||||
with_ext=False),
|
||||
r'(target|test.*linux-x86_64)')
|
||||
self.assertEqual(
|
||||
get_build_regex("test", "linux", 64, "x86_64", with_ext=False),
|
||||
r"(target|test.*linux-x86_64)",
|
||||
)
|
||||
|
||||
def test_for_win(self):
|
||||
self.assertEqual(get_build_regex('test', 'win', 32, 'x86'),
|
||||
r'(target|test.*win32)\.zip')
|
||||
self.assertEqual(get_build_regex('test', 'win', 64, 'x86_64'),
|
||||
r'(target|test.*win64(-x86_64)?)\.zip')
|
||||
self.assertEqual(get_build_regex('test', 'win', 64, 'x86_64',
|
||||
with_ext=False),
|
||||
r'(target|test.*win64(-x86_64)?)')
|
||||
self.assertEqual(get_build_regex('test', 'win', 32, 'aarch64'),
|
||||
r'(target|test.*win32)\.zip')
|
||||
self.assertEqual(get_build_regex('test', 'win', 64, 'aarch64'),
|
||||
r'(target|test.*win64-aarch64)\.zip')
|
||||
self.assertEqual(get_build_regex("test", "win", 32, "x86"), r"(target|test.*win32)\.zip")
|
||||
self.assertEqual(
|
||||
get_build_regex("test", "win", 64, "x86_64"), r"(target|test.*win64(-x86_64)?)\.zip",
|
||||
)
|
||||
self.assertEqual(
|
||||
get_build_regex("test", "win", 64, "x86_64", with_ext=False),
|
||||
r"(target|test.*win64(-x86_64)?)",
|
||||
)
|
||||
self.assertEqual(
|
||||
get_build_regex("test", "win", 32, "aarch64"), r"(target|test.*win32)\.zip"
|
||||
)
|
||||
self.assertEqual(
|
||||
get_build_regex("test", "win", 64, "aarch64"), r"(target|test.*win64-aarch64)\.zip",
|
||||
)
|
||||
|
||||
def test_for_mac(self):
|
||||
self.assertEqual(get_build_regex('test', 'mac', 32, 'x86'),
|
||||
r'(target|test.*mac.*)\.dmg')
|
||||
self.assertEqual(get_build_regex('test', 'mac', 64, 'x86_64'),
|
||||
r'(target|test.*mac.*)\.dmg')
|
||||
self.assertEqual(get_build_regex('test', 'mac', 64, 'x86_64',
|
||||
with_ext=False),
|
||||
r'(target|test.*mac.*)')
|
||||
self.assertEqual(get_build_regex("test", "mac", 32, "x86"), r"(target|test.*mac.*)\.dmg")
|
||||
self.assertEqual(get_build_regex("test", "mac", 64, "x86_64"), r"(target|test.*mac.*)\.dmg")
|
||||
self.assertEqual(
|
||||
get_build_regex("test", "mac", 64, "x86_64", with_ext=False), r"(target|test.*mac.*)",
|
||||
)
|
||||
|
||||
def test_unknown_os(self):
|
||||
with self.assertRaises(errors.MozRegressionError):
|
||||
get_build_regex('test', 'unknown', 32, 'x86')
|
||||
get_build_regex("test", "unknown", 32, "x86")
|
||||
|
||||
|
||||
class TestFallbacksConfig(TestFirefoxConfigLinux64):
|
||||
def setUp(self):
|
||||
self.conf = create_config(self.app_name, self.os, self.bits,
|
||||
self.processor)
|
||||
self.conf.BUILD_TYPE_FALLBACKS = {
|
||||
'opt': ('test', 'fallback')
|
||||
}
|
||||
self.conf.set_build_type('opt')
|
||||
self.conf = create_config(self.app_name, self.os, self.bits, self.processor)
|
||||
self.conf.BUILD_TYPE_FALLBACKS = {"opt": ("test", "fallback")}
|
||||
self.conf.set_build_type("opt")
|
||||
|
||||
def test_fallbacking(self):
|
||||
assert self.conf.build_type == 'opt'
|
||||
assert self.conf.build_type == "opt"
|
||||
self.conf._inc_used_build()
|
||||
assert self.conf.build_type == 'test'
|
||||
assert self.conf.build_type == "test"
|
||||
self.conf._inc_used_build()
|
||||
assert self.conf.build_type == 'fallback'
|
||||
assert self.conf.build_type == "fallback"
|
||||
# Check we wrap
|
||||
self.conf._inc_used_build()
|
||||
assert self.conf.build_type == 'opt'
|
||||
assert self.conf.build_type == "opt"
|
||||
|
||||
def test_fallback_routes(self):
|
||||
routes = list(self.conf.tk_routes(
|
||||
create_push('1a', TIMESTAMP_TEST)
|
||||
))
|
||||
routes = list(self.conf.tk_routes(create_push("1a", TIMESTAMP_TEST)))
|
||||
assert len(routes) == 3
|
||||
assert routes[0] == (
|
||||
'gecko.v2.mozilla-central.revision.1a.firefox.linux64-opt'
|
||||
)
|
||||
assert routes[2] == (
|
||||
'gecko.v2.mozilla-central.revision.1a.firefox.linux64-fallback'
|
||||
)
|
||||
assert routes[0] == ("gecko.v2.mozilla-central.revision.1a.firefox.linux64-opt")
|
||||
assert routes[2] == ("gecko.v2.mozilla-central.revision.1a.firefox.linux64-fallback")
|
||||
|
||||
|
||||
class TestAarch64AvailableBuildTypes(unittest.TestCase):
|
||||
app_name = 'firefox'
|
||||
os = 'win'
|
||||
app_name = "firefox"
|
||||
os = "win"
|
||||
bits = 64
|
||||
processor = 'aarch64'
|
||||
processor = "aarch64"
|
||||
|
||||
def setUp(self):
|
||||
self.conf = create_config(self.app_name, self.os, self.bits,
|
||||
self.processor)
|
||||
self.conf = create_config(self.app_name, self.os, self.bits, self.processor)
|
||||
self.conf.BUILD_TYPES = (
|
||||
'excluded[win64]', 'included[win64-aarch64]', 'default'
|
||||
"excluded[win64]",
|
||||
"included[win64-aarch64]",
|
||||
"default",
|
||||
)
|
||||
|
||||
def test_aarch64_build_types(self):
|
||||
build_types = self.conf.available_build_types()
|
||||
assert len(build_types) == 2
|
||||
assert 'default' in build_types
|
||||
assert 'included' in build_types
|
||||
assert 'excluded' not in build_types
|
||||
assert "default" in build_types
|
||||
assert "included" in build_types
|
||||
assert "excluded" not in build_types
|
||||
|
||||
|
||||
CHSET = "47856a21491834da3ab9b308145caa8ec1b98ee1"
|
||||
CHSET12 = "47856a214918"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("app,os,bits,processor,repo,push_date,expected", [
|
||||
@pytest.mark.parametrize(
|
||||
"app,os,bits,processor,repo,push_date,expected",
|
||||
[
|
||||
# firefox
|
||||
("firefox", 'win', 64, 'aarch64', 'm-i', TIMESTAMP_TEST,
|
||||
'gecko.v2.mozilla-inbound.shippable.revision.%s.firefox.win64-aarch64-opt' % CHSET),
|
||||
("firefox", 'win', 32, 'aarch64', 'm-i', TIMESTAMP_TEST,
|
||||
'gecko.v2.mozilla-inbound.shippable.revision.%s.firefox.win32-opt' % CHSET),
|
||||
("firefox", 'mac', 64, 'x86_64', 'm-i', TIMESTAMP_TEST,
|
||||
'gecko.v2.mozilla-inbound.shippable.revision.%s.firefox.macosx64-opt' % CHSET),
|
||||
("firefox", 'linux', 64, 'x86_64', 'm-i', TIMESTAMP_TEST,
|
||||
'gecko.v2.mozilla-inbound.shippable.revision.%s.firefox.linux64-opt' % CHSET),
|
||||
("firefox", 'linux', 64, 'x86_64', 'try', TIMESTAMP_TEST,
|
||||
'gecko.v2.try.shippable.revision.%s.firefox.linux64-opt' % CHSET),
|
||||
(
|
||||
"firefox",
|
||||
"win",
|
||||
64,
|
||||
"aarch64",
|
||||
"m-i",
|
||||
TIMESTAMP_TEST,
|
||||
"gecko.v2.mozilla-inbound.shippable.revision.%s.firefox.win64-aarch64-opt" % CHSET,
|
||||
),
|
||||
(
|
||||
"firefox",
|
||||
"win",
|
||||
32,
|
||||
"aarch64",
|
||||
"m-i",
|
||||
TIMESTAMP_TEST,
|
||||
"gecko.v2.mozilla-inbound.shippable.revision.%s.firefox.win32-opt" % CHSET,
|
||||
),
|
||||
(
|
||||
"firefox",
|
||||
"mac",
|
||||
64,
|
||||
"x86_64",
|
||||
"m-i",
|
||||
TIMESTAMP_TEST,
|
||||
"gecko.v2.mozilla-inbound.shippable.revision.%s.firefox.macosx64-opt" % CHSET,
|
||||
),
|
||||
(
|
||||
"firefox",
|
||||
"linux",
|
||||
64,
|
||||
"x86_64",
|
||||
"m-i",
|
||||
TIMESTAMP_TEST,
|
||||
"gecko.v2.mozilla-inbound.shippable.revision.%s.firefox.linux64-opt" % CHSET,
|
||||
),
|
||||
(
|
||||
"firefox",
|
||||
"linux",
|
||||
64,
|
||||
"x86_64",
|
||||
"try",
|
||||
TIMESTAMP_TEST,
|
||||
"gecko.v2.try.shippable.revision.%s.firefox.linux64-opt" % CHSET,
|
||||
),
|
||||
# fennec
|
||||
("fennec", None, None, None, None, TIMESTAMP_FENNEC_API_15 - 1,
|
||||
'gecko.v2.mozilla-central.revision.%s.mobile.android-api-11-opt' % CHSET),
|
||||
("fennec", None, None, None, None, TIMESTAMP_FENNEC_API_15,
|
||||
'gecko.v2.mozilla-central.revision.%s.mobile.android-api-15-opt' % CHSET),
|
||||
("fennec", None, None, None, None, TIMESTAMP_FENNEC_API_16,
|
||||
'gecko.v2.mozilla-central.revision.%s.mobile.android-api-16-opt' % CHSET),
|
||||
("fennec-2.3", None, None, None, 'm-i', TIMESTAMP_TEST,
|
||||
'gecko.v2.mozilla-inbound.revision.%s.mobile.android-api-9-opt' % CHSET),
|
||||
(
|
||||
"fennec",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
TIMESTAMP_FENNEC_API_15 - 1,
|
||||
"gecko.v2.mozilla-central.revision.%s.mobile.android-api-11-opt" % CHSET,
|
||||
),
|
||||
(
|
||||
"fennec",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
TIMESTAMP_FENNEC_API_15,
|
||||
"gecko.v2.mozilla-central.revision.%s.mobile.android-api-15-opt" % CHSET,
|
||||
),
|
||||
(
|
||||
"fennec",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
TIMESTAMP_FENNEC_API_16,
|
||||
"gecko.v2.mozilla-central.revision.%s.mobile.android-api-16-opt" % CHSET,
|
||||
),
|
||||
(
|
||||
"fennec-2.3",
|
||||
None,
|
||||
None,
|
||||
None,
|
||||
"m-i",
|
||||
TIMESTAMP_TEST,
|
||||
"gecko.v2.mozilla-inbound.revision.%s.mobile.android-api-9-opt" % CHSET,
|
||||
),
|
||||
# thunderbird
|
||||
('thunderbird', 'win', 32, 'x86_64', 'comm-central', TIMESTAMP_TEST,
|
||||
'comm.v2.comm-central.revision.%s.thunderbird.win32-opt' % CHSET),
|
||||
('thunderbird', 'linux', 64, 'x86_64', 'comm-beta', TIMESTAMP_TEST,
|
||||
'comm.v2.comm-beta.revision.%s.thunderbird.linux64-opt' % CHSET),
|
||||
])
|
||||
(
|
||||
"thunderbird",
|
||||
"win",
|
||||
32,
|
||||
"x86_64",
|
||||
"comm-central",
|
||||
TIMESTAMP_TEST,
|
||||
"comm.v2.comm-central.revision.%s.thunderbird.win32-opt" % CHSET,
|
||||
),
|
||||
(
|
||||
"thunderbird",
|
||||
"linux",
|
||||
64,
|
||||
"x86_64",
|
||||
"comm-beta",
|
||||
TIMESTAMP_TEST,
|
||||
"comm.v2.comm-beta.revision.%s.thunderbird.linux64-opt" % CHSET,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_tk_route(app, os, bits, processor, repo, push_date, expected):
|
||||
conf = create_config(app, os, bits, processor)
|
||||
conf.set_repo(repo)
|
||||
|
@ -338,80 +398,95 @@ def test_tk_route(app, os, bits, processor, repo, push_date, expected):
|
|||
assert result == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize("app,os,bits,processor,build_type,expected", [
|
||||
@pytest.mark.parametrize(
|
||||
"app,os,bits,processor,build_type,expected",
|
||||
[
|
||||
# firefox
|
||||
("firefox", 'linux', 64, 'x86_64', "asan",
|
||||
'gecko.v2.mozilla-central.revision.%s.firefox.linux64-asan' % CHSET),
|
||||
("firefox", 'linux', 64, 'x86_64', 'shippable',
|
||||
'gecko.v2.mozilla-central.shippable.revision.%s.firefox.linux64-opt'
|
||||
% CHSET),
|
||||
])
|
||||
def test_tk_route_with_build_type(app, os, bits, processor, build_type,
|
||||
expected):
|
||||
(
|
||||
"firefox",
|
||||
"linux",
|
||||
64,
|
||||
"x86_64",
|
||||
"asan",
|
||||
"gecko.v2.mozilla-central.revision.%s.firefox.linux64-asan" % CHSET,
|
||||
),
|
||||
(
|
||||
"firefox",
|
||||
"linux",
|
||||
64,
|
||||
"x86_64",
|
||||
"shippable",
|
||||
"gecko.v2.mozilla-central.shippable.revision.%s.firefox.linux64-opt" % CHSET,
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_tk_route_with_build_type(app, os, bits, processor, build_type, expected):
|
||||
conf = create_config(app, os, bits, processor)
|
||||
conf.set_build_type(build_type)
|
||||
result = conf.tk_route(
|
||||
create_push(CHSET, TIMESTAMP_TEST))
|
||||
result = conf.tk_route(create_push(CHSET, TIMESTAMP_TEST))
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_set_build_type():
|
||||
conf = create_config('firefox', 'linux', 64, 'x86_64')
|
||||
assert conf.build_type == 'shippable' # desktop Fx default is shippable
|
||||
conf.set_build_type('debug')
|
||||
assert conf.build_type == 'debug'
|
||||
conf = create_config("firefox", "linux", 64, "x86_64")
|
||||
assert conf.build_type == "shippable" # desktop Fx default is shippable
|
||||
conf.set_build_type("debug")
|
||||
assert conf.build_type == "debug"
|
||||
|
||||
|
||||
def test_set_bad_build_type():
|
||||
conf = create_config('firefox', 'linux', 64, 'x86_64')
|
||||
conf = create_config("firefox", "linux", 64, "x86_64")
|
||||
with pytest.raises(errors.MozRegressionError):
|
||||
conf.set_build_type("wrong build type")
|
||||
|
||||
|
||||
def test_jsshell_build_info_regex():
|
||||
conf = create_config('jsshell', 'linux', 64, 'x86_64')
|
||||
assert re.match(conf.build_info_regex(),
|
||||
'firefox-38.0a1.en-US.linux-x86_64.txt')
|
||||
conf = create_config("jsshell", "linux", 64, "x86_64")
|
||||
assert re.match(conf.build_info_regex(), "firefox-38.0a1.en-US.linux-x86_64.txt")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('os,bits,processor,name', [
|
||||
('linux', 32, 'x86', 'jsshell-linux-i686.zip'),
|
||||
('linux', 64, 'x86_64', 'jsshell-linux-x86_64.zip'),
|
||||
('mac', 64, 'x86_64', 'jsshell-mac.zip'),
|
||||
('win', 32, 'x86', 'jsshell-win32.zip'),
|
||||
('win', 64, 'x86_64', 'jsshell-win64.zip'),
|
||||
('win', 64, 'x86_64', 'jsshell-win64-x86_64.zip'),
|
||||
('win', 32, 'aarch64', 'jsshell-win32.zip'),
|
||||
('win', 64, 'aarch64', 'jsshell-win64-aarch64.zip'),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"os,bits,processor,name",
|
||||
[
|
||||
("linux", 32, "x86", "jsshell-linux-i686.zip"),
|
||||
("linux", 64, "x86_64", "jsshell-linux-x86_64.zip"),
|
||||
("mac", 64, "x86_64", "jsshell-mac.zip"),
|
||||
("win", 32, "x86", "jsshell-win32.zip"),
|
||||
("win", 64, "x86_64", "jsshell-win64.zip"),
|
||||
("win", 64, "x86_64", "jsshell-win64-x86_64.zip"),
|
||||
("win", 32, "aarch64", "jsshell-win32.zip"),
|
||||
("win", 64, "aarch64", "jsshell-win64-aarch64.zip"),
|
||||
],
|
||||
)
|
||||
def test_jsshell_build_regex(os, bits, processor, name):
|
||||
conf = create_config('jsshell', os, bits, processor)
|
||||
conf = create_config("jsshell", os, bits, processor)
|
||||
assert re.match(conf.build_regex(), name)
|
||||
|
||||
|
||||
def test_jsshell_x86_64_build_regex():
|
||||
conf = create_config('jsshell', 'win', 64, 'x86_64')
|
||||
assert not re.match(conf.build_regex(), 'jsshell-win64-aarch64.zip')
|
||||
conf = create_config("jsshell", "win", 64, "x86_64")
|
||||
assert not re.match(conf.build_regex(), "jsshell-win64-aarch64.zip")
|
||||
|
||||
|
||||
@pytest.mark.parametrize('os,bits,processor,tc_suffix', [
|
||||
('linux', 32, 'x86', 'linux-pgo'),
|
||||
('linux', 64, 'x86_64', 'linux64-pgo'),
|
||||
('mac', 64, 'x86_64', errors.MozRegressionError),
|
||||
('win', 32, 'x86', 'win32-pgo'),
|
||||
('win', 64, 'x86_64', 'win64-pgo'),
|
||||
])
|
||||
@pytest.mark.parametrize(
|
||||
"os,bits,processor,tc_suffix",
|
||||
[
|
||||
("linux", 32, "x86", "linux-pgo"),
|
||||
("linux", 64, "x86_64", "linux64-pgo"),
|
||||
("mac", 64, "x86_64", errors.MozRegressionError),
|
||||
("win", 32, "x86", "win32-pgo"),
|
||||
("win", 64, "x86_64", "win64-pgo"),
|
||||
],
|
||||
)
|
||||
def test_set_firefox_build_type_pgo(os, bits, processor, tc_suffix):
|
||||
conf = create_config('firefox', os, bits, processor)
|
||||
conf = create_config("firefox", os, bits, processor)
|
||||
if type(tc_suffix) is not str:
|
||||
with pytest.raises(tc_suffix):
|
||||
conf.set_build_type('pgo')
|
||||
conf.set_build_type("pgo")
|
||||
else:
|
||||
conf.set_build_type('pgo')
|
||||
assert conf.tk_route(
|
||||
create_push(CHSET, TIMESTAMP_TEST)) \
|
||||
.endswith('.' + tc_suffix)
|
||||
conf.set_build_type("pgo")
|
||||
assert conf.tk_route(create_push(CHSET, TIMESTAMP_TEST)).endswith("." + tc_suffix)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -5,110 +5,106 @@ from datetime import date, datetime
|
|||
|
||||
import pytest
|
||||
from mock import Mock, call
|
||||
|
||||
from mozregression.errors import EmptyPushlogError, MozRegressionError
|
||||
from mozregression.json_pushes import JsonPushes, Push
|
||||
from mozregression.errors import MozRegressionError, EmptyPushlogError
|
||||
|
||||
|
||||
def test_push(mocker):
|
||||
pushlog = {'1': {
|
||||
'changesets': ['a', 'b', 'c'],
|
||||
'date': 123456,
|
||||
}}
|
||||
retry_get = mocker.patch('mozregression.json_pushes.retry_get')
|
||||
pushlog = {"1": {"changesets": ["a", "b", "c"], "date": 123456}}
|
||||
retry_get = mocker.patch("mozregression.json_pushes.retry_get")
|
||||
response = Mock(json=Mock(return_value=pushlog))
|
||||
retry_get.return_value = response
|
||||
|
||||
jpushes = JsonPushes()
|
||||
push = jpushes.push('validchangeset')
|
||||
push = jpushes.push("validchangeset")
|
||||
assert isinstance(push, Push)
|
||||
assert push.push_id == '1'
|
||||
assert push.changeset == 'c'
|
||||
assert push.changesets[0] == 'a'
|
||||
assert push.push_id == "1"
|
||||
assert push.changeset == "c"
|
||||
assert push.changesets[0] == "a"
|
||||
assert push.timestamp == 123456
|
||||
assert push.utc_date == datetime(1970, 1, 2, 10, 17, 36)
|
||||
assert str(push) == 'c'
|
||||
assert str(push) == "c"
|
||||
retry_get.assert_called_once_with(
|
||||
'https://hg.mozilla.org/mozilla-central/json-pushes'
|
||||
'?changeset=validchangeset'
|
||||
"https://hg.mozilla.org/mozilla-central/json-pushes" "?changeset=validchangeset"
|
||||
)
|
||||
|
||||
|
||||
def test_push_404_error(mocker):
|
||||
retry_get = mocker.patch('mozregression.json_pushes.retry_get')
|
||||
retry_get = mocker.patch("mozregression.json_pushes.retry_get")
|
||||
response = Mock(status_code=404, json=Mock(return_value={"error": "unknown revision"}))
|
||||
retry_get.return_value = response
|
||||
|
||||
jpushes = JsonPushes()
|
||||
with pytest.raises(MozRegressionError):
|
||||
jpushes.push('invalid_changeset')
|
||||
jpushes.push("invalid_changeset")
|
||||
|
||||
|
||||
def test_push_nothing_found(mocker):
|
||||
retry_get = mocker.patch('mozregression.json_pushes.retry_get')
|
||||
retry_get = mocker.patch("mozregression.json_pushes.retry_get")
|
||||
response = Mock(json=Mock(return_value={}))
|
||||
retry_get.return_value = response
|
||||
|
||||
jpushes = JsonPushes()
|
||||
with pytest.raises(MozRegressionError):
|
||||
jpushes.push('invalid_changeset')
|
||||
jpushes.push("invalid_changeset")
|
||||
|
||||
|
||||
@pytest.mark.skipif(sys.version_info < (3, 0), reason="fails on python 2 for some unknown reason")
|
||||
def test_pushes_within_changes(mocker):
|
||||
push_first = {'1': {'changesets': ['a']}}
|
||||
other_pushes = {
|
||||
'2': {'changesets': ['b']},
|
||||
'3': {'changesets': ['c']}
|
||||
}
|
||||
push_first = {"1": {"changesets": ["a"]}}
|
||||
other_pushes = {"2": {"changesets": ["b"]}, "3": {"changesets": ["c"]}}
|
||||
|
||||
retry_get = mocker.patch('mozregression.json_pushes.retry_get')
|
||||
retry_get = mocker.patch("mozregression.json_pushes.retry_get")
|
||||
response = Mock(json=Mock(side_effect=[push_first, other_pushes]))
|
||||
retry_get.return_value = response
|
||||
|
||||
jpushes = JsonPushes()
|
||||
pushes = jpushes.pushes_within_changes('fromchset', "tochset")
|
||||
pushes = jpushes.pushes_within_changes("fromchset", "tochset")
|
||||
|
||||
assert pushes[0].push_id == '1'
|
||||
assert pushes[0].changeset == 'a'
|
||||
assert pushes[1].push_id == '2'
|
||||
assert pushes[1].changeset == 'b'
|
||||
assert pushes[2].push_id == '3'
|
||||
assert pushes[2].changeset == 'c'
|
||||
assert pushes[0].push_id == "1"
|
||||
assert pushes[0].changeset == "a"
|
||||
assert pushes[1].push_id == "2"
|
||||
assert pushes[1].changeset == "b"
|
||||
assert pushes[2].push_id == "3"
|
||||
assert pushes[2].changeset == "c"
|
||||
|
||||
retry_get.assert_has_calls([
|
||||
call('https://hg.mozilla.org/mozilla-central/json-pushes'
|
||||
'?changeset=fromchset'),
|
||||
call('https://hg.mozilla.org/mozilla-central/json-pushes'
|
||||
'?fromchange=fromchset&tochange=tochset'),
|
||||
])
|
||||
retry_get.assert_has_calls(
|
||||
[
|
||||
call("https://hg.mozilla.org/mozilla-central/json-pushes" "?changeset=fromchset"),
|
||||
call(
|
||||
"https://hg.mozilla.org/mozilla-central/json-pushes"
|
||||
"?fromchange=fromchset&tochange=tochset"
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def test_pushes_within_changes_using_dates(mocker):
|
||||
p1 = {'changesets': ['abc'], 'date': 12345}
|
||||
p2 = {'changesets': ['def'], 'date': 67891}
|
||||
pushes = {'1': p1, '2': p2}
|
||||
p1 = {"changesets": ["abc"], "date": 12345}
|
||||
p2 = {"changesets": ["def"], "date": 67891}
|
||||
pushes = {"1": p1, "2": p2}
|
||||
|
||||
retry_get = mocker.patch('mozregression.json_pushes.retry_get')
|
||||
retry_get = mocker.patch("mozregression.json_pushes.retry_get")
|
||||
retry_get.return_value = Mock(json=Mock(return_value=pushes))
|
||||
|
||||
jpushes = JsonPushes(branch='m-i')
|
||||
jpushes = JsonPushes(branch="m-i")
|
||||
|
||||
pushes = jpushes.pushes_within_changes(date(2015, 1, 1), date(2015, 2, 2))
|
||||
assert pushes[0].push_id == '1'
|
||||
assert pushes[1].push_id == '2'
|
||||
assert pushes[0].push_id == "1"
|
||||
assert pushes[1].push_id == "2"
|
||||
|
||||
retry_get.assert_called_once_with(
|
||||
'https://hg.mozilla.org/integration/mozilla-inbound/json-pushes?'
|
||||
'enddate=2015-02-03&startdate=2015-01-01'
|
||||
"https://hg.mozilla.org/integration/mozilla-inbound/json-pushes?"
|
||||
"enddate=2015-02-03&startdate=2015-01-01"
|
||||
)
|
||||
|
||||
|
||||
def test_push_with_date_raise_appropriate_error():
|
||||
jpushes = JsonPushes(branch='inbound')
|
||||
jpushes = JsonPushes(branch="inbound")
|
||||
jpushes.pushes_within_changes = Mock(side_effect=EmptyPushlogError)
|
||||
|
||||
with pytest.raises(EmptyPushlogError) as ctx:
|
||||
jpushes.push(date(2015, 1, 1))
|
||||
|
||||
assert str(ctx.value) == \
|
||||
'No pushes available for the date 2015-01-01 on inbound.'
|
||||
assert str(ctx.value) == "No pushes available for the date 2015-01-01 on inbound."
|
||||
|
|
|
@ -1,17 +1,19 @@
|
|||
from __future__ import absolute_import
|
||||
from mozregression import launchers
|
||||
import unittest
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import tempfile
|
||||
import unittest
|
||||
|
||||
import mozfile
|
||||
import mozinfo
|
||||
import mozversion
|
||||
|
||||
from mock import patch, Mock, ANY
|
||||
from mozprofile import Profile
|
||||
from mozregression.errors import LauncherNotRunnable, LauncherError
|
||||
import pytest
|
||||
from mock import ANY, Mock, patch
|
||||
from mozdevice import ADBError
|
||||
from mozprofile import Profile
|
||||
|
||||
from mozregression import launchers
|
||||
from mozregression.errors import LauncherError, LauncherNotRunnable
|
||||
|
||||
|
||||
class MyLauncher(launchers.Launcher):
|
||||
|
@ -33,9 +35,8 @@ class MyLauncher(launchers.Launcher):
|
|||
|
||||
|
||||
class TestLauncher(unittest.TestCase):
|
||||
|
||||
def test_start_stop(self):
|
||||
launcher = MyLauncher('/foo/persist.zip')
|
||||
launcher = MyLauncher("/foo/persist.zip")
|
||||
self.assertFalse(launcher.started)
|
||||
launcher.start()
|
||||
# now it has been started
|
||||
|
@ -50,7 +51,7 @@ class TestLauncher(unittest.TestCase):
|
|||
self.assertTrue(launcher.started)
|
||||
|
||||
def test_wait(self):
|
||||
launcher = MyLauncher('/foo/persist.zip')
|
||||
launcher = MyLauncher("/foo/persist.zip")
|
||||
self.assertFalse(launcher.started)
|
||||
launcher.start()
|
||||
# now it has been started
|
||||
|
@ -65,17 +66,17 @@ class TestLauncher(unittest.TestCase):
|
|||
raise Exception()
|
||||
|
||||
with self.assertRaises(LauncherError):
|
||||
FailingLauncher('/foo/persist.zip')
|
||||
FailingLauncher("/foo/persist.zip")
|
||||
|
||||
def test_start_fail(self):
|
||||
launcher = MyLauncher('/foo/persist.zip')
|
||||
launcher = MyLauncher("/foo/persist.zip")
|
||||
launcher._start = Mock(side_effect=Exception)
|
||||
|
||||
with self.assertRaises(LauncherError):
|
||||
launcher.start()
|
||||
|
||||
def test_stop_fail(self):
|
||||
launcher = MyLauncher('/foo/persist.zip')
|
||||
launcher = MyLauncher("/foo/persist.zip")
|
||||
launcher._stop = Mock(side_effect=Exception)
|
||||
|
||||
launcher.start()
|
||||
|
@ -84,36 +85,39 @@ class TestLauncher(unittest.TestCase):
|
|||
|
||||
|
||||
class TestMozRunnerLauncher(unittest.TestCase):
|
||||
@patch('mozregression.launchers.mozinstall')
|
||||
@patch("mozregression.launchers.mozinstall")
|
||||
def setUp(self, mozinstall):
|
||||
mozinstall.get_binary.return_value = '/binary'
|
||||
self.launcher = launchers.MozRunnerLauncher('/binary')
|
||||
mozinstall.get_binary.return_value = "/binary"
|
||||
self.launcher = launchers.MozRunnerLauncher("/binary")
|
||||
|
||||
# patch profile_class else we will have some temporary dirs not deleted
|
||||
@patch('mozregression.launchers.MozRunnerLauncher.\
|
||||
profile_class', spec=Profile)
|
||||
@patch(
|
||||
"mozregression.launchers.MozRunnerLauncher.\
|
||||
profile_class",
|
||||
spec=Profile,
|
||||
)
|
||||
def launcher_start(self, profile_class, *args, **kwargs):
|
||||
self.profile_class = profile_class
|
||||
self.launcher.start(*args, **kwargs)
|
||||
|
||||
def test_installed(self):
|
||||
with self.launcher:
|
||||
self.assertEqual(self.launcher.binary, '/binary')
|
||||
self.assertEqual(self.launcher.binary, "/binary")
|
||||
|
||||
@patch('mozregression.launchers.Runner')
|
||||
@patch("mozregression.launchers.Runner")
|
||||
def test_start_no_args(self, Runner):
|
||||
with self.launcher:
|
||||
self.launcher_start()
|
||||
kwargs = Runner.call_args[1]
|
||||
|
||||
self.assertEqual(kwargs['cmdargs'], ())
|
||||
self.assertEqual(kwargs['binary'], '/binary')
|
||||
self.assertIsInstance(kwargs['profile'], Profile)
|
||||
self.assertEqual(kwargs["cmdargs"], ())
|
||||
self.assertEqual(kwargs["binary"], "/binary")
|
||||
self.assertIsInstance(kwargs["profile"], Profile)
|
||||
# runner is started
|
||||
self.launcher.runner.start.assert_called_once_with()
|
||||
self.launcher.stop()
|
||||
|
||||
@patch('mozregression.launchers.Runner')
|
||||
@patch("mozregression.launchers.Runner")
|
||||
def test_wait(self, Runner):
|
||||
runner = Mock(wait=Mock(return_value=0))
|
||||
Runner.return_value = runner
|
||||
|
@ -122,42 +126,43 @@ profile_class', spec=Profile)
|
|||
self.assertEqual(self.launcher.wait(), 0)
|
||||
runner.wait.assert_called_once_with()
|
||||
|
||||
@patch('mozregression.launchers.Runner')
|
||||
@patch("mozregression.launchers.Runner")
|
||||
def test_start_with_addons(self, Runner):
|
||||
with self.launcher:
|
||||
self.launcher_start(addons=['my-addon'], preferences='my-prefs')
|
||||
self.profile_class.assert_called_once_with(addons=['my-addon'],
|
||||
preferences='my-prefs')
|
||||
self.launcher_start(addons=["my-addon"], preferences="my-prefs")
|
||||
self.profile_class.assert_called_once_with(addons=["my-addon"], preferences="my-prefs")
|
||||
# runner is started
|
||||
self.launcher.runner.start.assert_called_once_with()
|
||||
self.launcher.stop()
|
||||
|
||||
@patch('mozregression.launchers.Runner')
|
||||
@patch("mozregression.launchers.Runner")
|
||||
def test_start_with_profile_and_addons(self, Runner):
|
||||
temp_dir_profile = tempfile.mkdtemp()
|
||||
self.addCleanup(mozfile.remove, temp_dir_profile)
|
||||
|
||||
with self.launcher:
|
||||
self.launcher_start(profile=temp_dir_profile, addons=['my-addon'],
|
||||
preferences='my-prefs')
|
||||
self.launcher_start(
|
||||
profile=temp_dir_profile, addons=["my-addon"], preferences="my-prefs"
|
||||
)
|
||||
self.profile_class.clone.assert_called_once_with(
|
||||
temp_dir_profile, addons=['my-addon'], preferences='my-prefs')
|
||||
temp_dir_profile, addons=["my-addon"], preferences="my-prefs"
|
||||
)
|
||||
# runner is started
|
||||
self.launcher.runner.start.assert_called_once_with()
|
||||
self.launcher.stop()
|
||||
|
||||
@patch('mozregression.launchers.Runner')
|
||||
@patch('mozregression.launchers.mozversion')
|
||||
@patch("mozregression.launchers.Runner")
|
||||
@patch("mozregression.launchers.mozversion")
|
||||
def test_get_app_infos(self, mozversion, Runner):
|
||||
mozversion.get_version.return_value = {'some': 'infos'}
|
||||
mozversion.get_version.return_value = {"some": "infos"}
|
||||
with self.launcher:
|
||||
self.launcher_start()
|
||||
self.assertEqual(self.launcher.get_app_info(), {'some': 'infos'})
|
||||
mozversion.get_version.assert_called_once_with(binary='/binary')
|
||||
self.assertEqual(self.launcher.get_app_info(), {"some": "infos"})
|
||||
mozversion.get_version.assert_called_once_with(binary="/binary")
|
||||
self.launcher.stop()
|
||||
|
||||
@patch('mozregression.launchers.Runner')
|
||||
@patch('mozversion.get_version')
|
||||
@patch("mozregression.launchers.Runner")
|
||||
@patch("mozversion.get_version")
|
||||
def test_get_app_infos_error(self, get_version, Runner):
|
||||
get_version.side_effect = mozversion.VersionError("err")
|
||||
with self.launcher:
|
||||
|
@ -174,97 +179,89 @@ profile_class', spec=Profile)
|
|||
|
||||
def test_firefox_install(mocker):
|
||||
install_ext, binary_name = (
|
||||
('zip', 'firefox.exe') if mozinfo.isWin else
|
||||
('tar.bz2', 'firefox') if mozinfo.isLinux else
|
||||
('dmg', 'firefox') # if mozinfo.ismac
|
||||
("zip", "firefox.exe")
|
||||
if mozinfo.isWin
|
||||
else ("tar.bz2", "firefox")
|
||||
if mozinfo.isLinux
|
||||
else ("dmg", "firefox") # if mozinfo.ismac
|
||||
)
|
||||
|
||||
installer_file = 'firefox.{}'.format(install_ext)
|
||||
installer_file = "firefox.{}".format(install_ext)
|
||||
|
||||
installer = os.path.abspath(
|
||||
os.path.join('tests', 'unit', 'installer_stubs', installer_file)
|
||||
)
|
||||
installer = os.path.abspath(os.path.join("tests", "unit", "installer_stubs", installer_file))
|
||||
assert os.path.isfile(installer)
|
||||
with launchers.FirefoxLauncher(installer) as fx:
|
||||
assert os.path.isdir(fx.tempdir)
|
||||
assert os.path.basename(fx.binary) == binary_name
|
||||
installdir = os.path.dirname(fx.binary)
|
||||
if mozinfo.isMac:
|
||||
installdir = os.path.normpath(
|
||||
os.path.join(installdir, '..', 'Resources')
|
||||
)
|
||||
assert os.path.exists(os.path.join(installdir, 'distribution', 'policies.json'))
|
||||
installdir = os.path.normpath(os.path.join(installdir, "..", "Resources"))
|
||||
assert os.path.exists(os.path.join(installdir, "distribution", "policies.json"))
|
||||
assert not os.path.isdir(fx.tempdir)
|
||||
|
||||
|
||||
class TestFennecLauncher(unittest.TestCase):
|
||||
|
||||
test_root = '/sdcard/tmp'
|
||||
test_root = "/sdcard/tmp"
|
||||
|
||||
def setUp(self):
|
||||
self.profile = Profile()
|
||||
self.addCleanup(self.profile.cleanup)
|
||||
self.remote_profile_path = self.test_root + \
|
||||
'/' + os.path.basename(self.profile.profile)
|
||||
self.remote_profile_path = self.test_root + "/" + os.path.basename(self.profile.profile)
|
||||
|
||||
@patch('mozregression.launchers.mozversion.get_version')
|
||||
@patch('mozregression.launchers.ADBAndroid')
|
||||
@patch("mozregression.launchers.mozversion.get_version")
|
||||
@patch("mozregression.launchers.ADBAndroid")
|
||||
def create_launcher(self, ADBAndroid, get_version, **kwargs):
|
||||
self.adb = Mock(test_root=self.test_root)
|
||||
if kwargs.get('uninstall_error'):
|
||||
if kwargs.get("uninstall_error"):
|
||||
self.adb.uninstall_app.side_effect = launchers.ADBError
|
||||
ADBAndroid.return_value = self.adb
|
||||
get_version.return_value = kwargs.get('version_value', {})
|
||||
return launchers.FennecLauncher('/binary')
|
||||
get_version.return_value = kwargs.get("version_value", {})
|
||||
return launchers.FennecLauncher("/binary")
|
||||
|
||||
def test_install(self):
|
||||
self.create_launcher()
|
||||
self.adb.uninstall_app.assert_called_with("org.mozilla.fennec")
|
||||
self.adb.install_app.assert_called_with('/binary')
|
||||
self.adb.install_app.assert_called_with("/binary")
|
||||
|
||||
@patch('mozregression.launchers.FennecLauncher._create_profile')
|
||||
@patch("mozregression.launchers.FennecLauncher._create_profile")
|
||||
def test_start_stop(self, _create_profile):
|
||||
# Force use of existing profile
|
||||
_create_profile.return_value = self.profile
|
||||
launcher = self.create_launcher()
|
||||
launcher.start(profile='my_profile')
|
||||
launcher.start(profile="my_profile")
|
||||
self.adb.exists.assert_called_once_with(self.remote_profile_path)
|
||||
self.adb.rm.assert_called_once_with(self.remote_profile_path,
|
||||
recursive=True)
|
||||
self.adb.push.assert_called_once_with(self.profile.profile,
|
||||
self.remote_profile_path)
|
||||
self.adb.rm.assert_called_once_with(self.remote_profile_path, recursive=True)
|
||||
self.adb.push.assert_called_once_with(self.profile.profile, self.remote_profile_path)
|
||||
self.adb.launch_fennec.assert_called_once_with(
|
||||
"org.mozilla.fennec",
|
||||
extra_args=['-profile', self.remote_profile_path]
|
||||
"org.mozilla.fennec", extra_args=["-profile", self.remote_profile_path]
|
||||
)
|
||||
# ensure get_app_info returns something
|
||||
self.assertIsNotNone(launcher.get_app_info())
|
||||
launcher.stop()
|
||||
self.adb.stop_application.assert_called_once_with("org.mozilla.fennec")
|
||||
|
||||
@patch('mozregression.launchers.FennecLauncher._create_profile')
|
||||
@patch("mozregression.launchers.FennecLauncher._create_profile")
|
||||
def test_adb_calls_with_custom_package_name(self, _create_profile):
|
||||
# Force use of existing profile
|
||||
_create_profile.return_value = self.profile
|
||||
pkg_name = 'org.mozilla.custom'
|
||||
launcher = \
|
||||
self.create_launcher(version_value={'package_name': pkg_name})
|
||||
pkg_name = "org.mozilla.custom"
|
||||
launcher = self.create_launcher(version_value={"package_name": pkg_name})
|
||||
self.adb.uninstall_app.assert_called_once_with(pkg_name)
|
||||
launcher.start(profile='my_profile')
|
||||
launcher.start(profile="my_profile")
|
||||
self.adb.launch_fennec.assert_called_once_with(
|
||||
pkg_name,
|
||||
extra_args=['-profile', self.remote_profile_path]
|
||||
pkg_name, extra_args=["-profile", self.remote_profile_path]
|
||||
)
|
||||
launcher.stop()
|
||||
self.adb.stop_application.assert_called_once_with(pkg_name)
|
||||
|
||||
@patch('mozregression.launchers.LOG')
|
||||
@patch("mozregression.launchers.LOG")
|
||||
def test_adb_first_uninstall_fail(self, log):
|
||||
self.create_launcher(uninstall_error=True)
|
||||
log.warning.assert_called_once_with(ANY)
|
||||
self.adb.install_app.assert_called_once_with(ANY)
|
||||
|
||||
@patch('mozregression.launchers.ADBHost')
|
||||
@patch("mozregression.launchers.ADBHost")
|
||||
def test_check_is_runnable(self, ADBHost):
|
||||
devices = Mock(return_value=True)
|
||||
ADBHost.return_value = Mock(devices=devices)
|
||||
|
@ -273,16 +270,14 @@ class TestFennecLauncher(unittest.TestCase):
|
|||
|
||||
# exception raised if there is no device
|
||||
devices.return_value = False
|
||||
self.assertRaises(LauncherNotRunnable,
|
||||
launchers.FennecLauncher.check_is_runnable)
|
||||
self.assertRaises(LauncherNotRunnable, launchers.FennecLauncher.check_is_runnable)
|
||||
|
||||
# or if ADBHost().devices() raise an unexpected IOError
|
||||
devices.side_effect = ADBError()
|
||||
self.assertRaises(LauncherNotRunnable,
|
||||
launchers.FennecLauncher.check_is_runnable)
|
||||
self.assertRaises(LauncherNotRunnable, launchers.FennecLauncher.check_is_runnable)
|
||||
|
||||
@patch('time.sleep')
|
||||
@patch('mozregression.launchers.FennecLauncher._create_profile')
|
||||
@patch("time.sleep")
|
||||
@patch("mozregression.launchers.FennecLauncher._create_profile")
|
||||
def test_wait(self, _create_profile, sleep):
|
||||
# Force use of existing profile
|
||||
_create_profile.return_value = self.profile
|
||||
|
@ -299,7 +294,7 @@ class TestFennecLauncher(unittest.TestCase):
|
|||
self.adb.process_exist = Mock(side_effect=proc_exists)
|
||||
launcher.start()
|
||||
launcher.wait()
|
||||
self.adb.process_exist.assert_called_with('org.mozilla.fennec')
|
||||
self.adb.process_exist.assert_called_with("org.mozilla.fennec")
|
||||
|
||||
|
||||
class Zipfile(object):
|
||||
|
@ -313,47 +308,42 @@ class Zipfile(object):
|
|||
pass
|
||||
|
||||
def extractall(self, dirname):
|
||||
fname = 'js' if launchers.mozinfo.os != 'win' else 'js.exe'
|
||||
with open(os.path.join(dirname, fname), 'w') as f:
|
||||
f.write('1')
|
||||
fname = "js" if launchers.mozinfo.os != "win" else "js.exe"
|
||||
with open(os.path.join(dirname, fname), "w") as f:
|
||||
f.write("1")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("mos,binary_name", [
|
||||
('win', 'js.exe'),
|
||||
('linux', 'js'),
|
||||
('mac', 'js'),
|
||||
])
|
||||
@pytest.mark.parametrize("mos,binary_name", [("win", "js.exe"), ("linux", "js"), ("mac", "js")])
|
||||
def test_jsshell_install(mocker, mos, binary_name):
|
||||
zipfile = mocker.patch('mozregression.launchers.zipfile')
|
||||
zipfile = mocker.patch("mozregression.launchers.zipfile")
|
||||
zipfile.ZipFile = Zipfile
|
||||
|
||||
mocker.patch('mozregression.launchers.mozinfo').os = mos
|
||||
mocker.patch("mozregression.launchers.mozinfo").os = mos
|
||||
|
||||
with launchers.JsShellLauncher('/path/to') as js:
|
||||
with launchers.JsShellLauncher("/path/to") as js:
|
||||
assert os.path.isdir(js.tempdir)
|
||||
assert os.path.basename(js.binary) == binary_name
|
||||
assert not os.path.isdir(js.tempdir)
|
||||
|
||||
|
||||
def test_jsshell_install_except(mocker):
|
||||
mocker.patch('mozregression.launchers.zipfile').ZipFile.side_effect \
|
||||
= Exception
|
||||
mocker.patch("mozregression.launchers.zipfile").ZipFile.side_effect = Exception
|
||||
|
||||
with pytest.raises(Exception):
|
||||
launchers.JsShellLauncher('/path/to')
|
||||
launchers.JsShellLauncher("/path/to")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("return_code", [0, 1])
|
||||
def test_jsshell_start(mocker, return_code):
|
||||
zipfile = mocker.patch('mozregression.launchers.zipfile')
|
||||
zipfile = mocker.patch("mozregression.launchers.zipfile")
|
||||
zipfile.ZipFile = Zipfile
|
||||
|
||||
call = mocker.patch('mozregression.launchers.call')
|
||||
call = mocker.patch("mozregression.launchers.call")
|
||||
call.return_code = return_code
|
||||
|
||||
logger = Mock()
|
||||
|
||||
with launchers.JsShellLauncher('/path/to') as js:
|
||||
with launchers.JsShellLauncher("/path/to") as js:
|
||||
js._logger = logger
|
||||
js.start()
|
||||
assert js.get_app_info() == {}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from mozregression import log
|
||||
from six import StringIO
|
||||
from colorama import Fore, Style
|
||||
from six import StringIO
|
||||
|
||||
from mozregression import log
|
||||
|
||||
|
||||
def init_logger(mocker, **kwargs):
|
||||
|
@ -41,5 +44,6 @@ def test_logger_debug(mocker, debug):
|
|||
def test_colorize():
|
||||
assert log.colorize("stuff", allow_color=True) == "stuff"
|
||||
assert log.colorize("{fRED}stuff{sRESET_ALL}", allow_color=True) == (
|
||||
Fore.RED + "stuff" + Style.RESET_ALL)
|
||||
Fore.RED + "stuff" + Style.RESET_ALL
|
||||
)
|
||||
assert log.colorize("{fRED}stuf{sRESET_ALL}", allow_color=False) == "stuf"
|
||||
|
|
|
@ -1,42 +1,45 @@
|
|||
from __future__ import absolute_import
|
||||
import pytest
|
||||
|
||||
from argparse import ArgumentParser, Namespace
|
||||
|
||||
import pytest
|
||||
|
||||
from mozregression import __version__
|
||||
from mozregression.config import DEFAULTS
|
||||
from mozregression.mach_interface import new_release_on_pypi, parser, run
|
||||
|
||||
|
||||
@pytest.mark.parametrize('pypi_version, result', [
|
||||
(lambda: 'latest', 'latest'),
|
||||
@pytest.mark.parametrize(
|
||||
"pypi_version, result",
|
||||
[
|
||||
(lambda: "latest", "latest"),
|
||||
(lambda: __version__, None), # same version, None is returned
|
||||
(lambda: None, None),
|
||||
(Exception, None), # on exception, None is returned
|
||||
])
|
||||
def test_new_release_on_pypi(mocker, pypi_version, result):
|
||||
pypi_latest_version = mocker.patch(
|
||||
'mozregression.mach_interface.pypi_latest_version'
|
||||
],
|
||||
)
|
||||
def test_new_release_on_pypi(mocker, pypi_version, result):
|
||||
pypi_latest_version = mocker.patch("mozregression.mach_interface.pypi_latest_version")
|
||||
pypi_latest_version.side_effect = pypi_version
|
||||
assert new_release_on_pypi() == result
|
||||
|
||||
|
||||
def test_parser(mocker):
|
||||
defaults = dict(DEFAULTS)
|
||||
defaults.update({'persist': 'stuff'})
|
||||
get_defaults = mocker.patch('mozregression.mach_interface.get_defaults')
|
||||
defaults.update({"persist": "stuff"})
|
||||
get_defaults = mocker.patch("mozregression.mach_interface.get_defaults")
|
||||
get_defaults.return_value = defaults
|
||||
p = parser()
|
||||
assert isinstance(p, ArgumentParser)
|
||||
options = p.parse_args(['--persist-size-limit=1'])
|
||||
options = p.parse_args(["--persist-size-limit=1"])
|
||||
|
||||
assert options.persist == 'stuff'
|
||||
assert options.persist == "stuff"
|
||||
assert options.persist_size_limit == 1.0
|
||||
|
||||
|
||||
def test_run(mocker):
|
||||
main = mocker.patch('mozregression.mach_interface.main')
|
||||
run({'persist': 'foo', 'bits': 64})
|
||||
main.assert_called_once_with(check_new_version=False,
|
||||
namespace=Namespace(bits=64, persist='foo'))
|
||||
main = mocker.patch("mozregression.mach_interface.main")
|
||||
run({"persist": "foo", "bits": 64})
|
||||
main.assert_called_once_with(
|
||||
check_new_version=False, namespace=Namespace(bits=64, persist="foo")
|
||||
)
|
||||
|
|
|
@ -2,22 +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/.
|
||||
|
||||
from __future__ import absolute_import
|
||||
from __future__ import print_function
|
||||
import pytest
|
||||
from __future__ import absolute_import, print_function
|
||||
|
||||
import unittest
|
||||
import requests
|
||||
|
||||
from datetime import date
|
||||
from mock import patch, Mock, MagicMock, ANY
|
||||
|
||||
from mozregression import main, errors, __version__, config
|
||||
from mozregression.test_runner import ManualTestRunner, CommandTestRunner
|
||||
from mozregression.download_manager import BuildDownloadManager
|
||||
from mozregression.bisector import Bisector, Bisection, IntegrationHandler, \
|
||||
NightlyHandler
|
||||
import pytest
|
||||
import requests
|
||||
from mock import ANY, MagicMock, Mock, patch
|
||||
from six.moves import range
|
||||
|
||||
from mozregression import __version__, config, errors, main
|
||||
from mozregression.bisector import Bisection, Bisector, IntegrationHandler, NightlyHandler
|
||||
from mozregression.download_manager import BuildDownloadManager
|
||||
from mozregression.test_runner import CommandTestRunner, ManualTestRunner
|
||||
|
||||
|
||||
class AppCreator(object):
|
||||
def __init__(self, logger):
|
||||
|
@ -47,40 +46,44 @@ class AppCreator(object):
|
|||
@pytest.yield_fixture
|
||||
def create_app(mocker):
|
||||
"""allow to create an Application and ensure that clear() is called"""
|
||||
creator = AppCreator(mocker.patch('mozregression.main.LOG'))
|
||||
creator = AppCreator(mocker.patch("mozregression.main.LOG"))
|
||||
yield creator
|
||||
creator.clear()
|
||||
|
||||
|
||||
def test_app_get_manual_test_runner(create_app):
|
||||
app = create_app(['--profile=/prof'])
|
||||
app = create_app(["--profile=/prof"])
|
||||
assert isinstance(app.test_runner, ManualTestRunner)
|
||||
assert app.test_runner.launcher_kwargs == dict(
|
||||
addons=[], profile='/prof', cmdargs=['--allow-downgrade'], preferences=[],
|
||||
adb_profile_dir=None
|
||||
addons=[],
|
||||
profile="/prof",
|
||||
cmdargs=["--allow-downgrade"],
|
||||
preferences=[],
|
||||
adb_profile_dir=None,
|
||||
)
|
||||
|
||||
|
||||
def test_app_get_command_test_runner(create_app):
|
||||
app = create_app(['--command=echo {binary}'])
|
||||
app = create_app(["--command=echo {binary}"])
|
||||
assert isinstance(app.test_runner, CommandTestRunner)
|
||||
assert app.test_runner.command == 'echo {binary}'
|
||||
assert app.test_runner.command == "echo {binary}"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("argv,background_dl_policy,size_limit", [
|
||||
@pytest.mark.parametrize(
|
||||
"argv,background_dl_policy,size_limit",
|
||||
[
|
||||
([], "cancel", 0),
|
||||
# without persist, cancel policy is forced
|
||||
(['--background-dl-policy=keep'], "cancel", 0),
|
||||
(['--persist=1', "--background-dl-policy=keep"], "keep", 0),
|
||||
(["--background-dl-policy=keep"], "cancel", 0),
|
||||
(["--persist=1", "--background-dl-policy=keep"], "keep", 0),
|
||||
# persist limit
|
||||
(['--persist-size-limit=10'], "cancel", 10 * 1073741824),
|
||||
])
|
||||
def test_app_get_download_manager(create_app, argv, background_dl_policy,
|
||||
size_limit):
|
||||
(["--persist-size-limit=10"], "cancel", 10 * 1073741824),
|
||||
],
|
||||
)
|
||||
def test_app_get_download_manager(create_app, argv, background_dl_policy, size_limit):
|
||||
app = create_app(argv)
|
||||
assert isinstance(app.build_download_manager, BuildDownloadManager)
|
||||
assert app.build_download_manager.background_dl_policy == \
|
||||
background_dl_policy
|
||||
assert app.build_download_manager.background_dl_policy == background_dl_policy
|
||||
assert app.build_download_manager.persist_limit.size_limit == size_limit
|
||||
assert app.build_download_manager.persist_limit.file_limit == 5
|
||||
|
||||
|
@ -91,75 +94,62 @@ def test_app_get_bisector(create_app):
|
|||
|
||||
|
||||
def test_app_bisect_nightlies_finished(create_app, mocker):
|
||||
app = create_app(['-g=2015-06-01', '-b=2015-06-02'])
|
||||
app = create_app(["-g=2015-06-01", "-b=2015-06-02"])
|
||||
app.bisector.bisect = Mock(return_value=Bisection.FINISHED)
|
||||
app._bisect_integration = Mock(return_value=0)
|
||||
NightlyHandler = mocker.patch(
|
||||
"mozregression.main.NightlyHandler"
|
||||
)
|
||||
nh = Mock(bad_date=date.today(), good_revision='c1',
|
||||
bad_revision='c2')
|
||||
NightlyHandler = mocker.patch("mozregression.main.NightlyHandler")
|
||||
nh = Mock(bad_date=date.today(), good_revision="c1", bad_revision="c2")
|
||||
NightlyHandler.return_value = nh
|
||||
assert app.bisect_nightlies() == 0
|
||||
app.bisector.bisect.assert_called_once_with(
|
||||
ANY,
|
||||
date(2015, 0o6, 0o1),
|
||||
date(2015, 0o6, 0o2)
|
||||
)
|
||||
assert create_app.find_in_log(
|
||||
"Got as far as we can go bisecting nightlies..."
|
||||
)
|
||||
app._bisect_integration.assert_called_once_with(
|
||||
'c1', 'c2', expand=config.DEFAULT_EXPAND)
|
||||
app.bisector.bisect.assert_called_once_with(ANY, date(2015, 0o6, 0o1), date(2015, 0o6, 0o2))
|
||||
assert create_app.find_in_log("Got as far as we can go bisecting nightlies...")
|
||||
app._bisect_integration.assert_called_once_with("c1", "c2", expand=config.DEFAULT_EXPAND)
|
||||
|
||||
|
||||
def test_app_bisect_nightlies_no_data(create_app):
|
||||
app = create_app(['-g=2015-06-01', '-b=2015-06-02'])
|
||||
app = create_app(["-g=2015-06-01", "-b=2015-06-02"])
|
||||
app.bisector.bisect = Mock(return_value=Bisection.NO_DATA)
|
||||
assert app.bisect_nightlies() == 1
|
||||
assert create_app.find_in_log(
|
||||
"Unable to get valid builds within the given range.",
|
||||
False
|
||||
)
|
||||
assert create_app.find_in_log("Unable to get valid builds within the given range.", False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("same_chsets", [True, False])
|
||||
def test_app_bisect_integration_finished(create_app, same_chsets):
|
||||
argv = [
|
||||
'--good=c1',
|
||||
'--bad=%s' % ('c1' if same_chsets else 'c2')
|
||||
]
|
||||
argv = ["--good=c1", "--bad=%s" % ("c1" if same_chsets else "c2")]
|
||||
app = create_app(argv)
|
||||
app.bisector.bisect = Mock(return_value=Bisection.FINISHED)
|
||||
assert app.bisect_integration() == 0
|
||||
assert create_app.find_in_log("No more integration revisions, bisection finished.")
|
||||
if same_chsets:
|
||||
assert create_app.find_in_log("It seems that you used two changesets"
|
||||
" that are in the same push.", False)
|
||||
assert create_app.find_in_log(
|
||||
"It seems that you used two changesets" " that are in the same push.", False
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("argv,expected_log", [
|
||||
(['--app=firefox', '--bits=64'], "--app=firefox --bits=64"),
|
||||
(['--persist', 'blah stuff'], "--persist 'blah stuff'"),
|
||||
(['--addon=a b c', '--addon=d'], "'--addon=a b c' --addon=d"),
|
||||
(['--find-fix', '--arg=a b'], "--find-fix '--arg=a b'"),
|
||||
(['--profile=pro file'], "'--profile=pro file'"),
|
||||
(['-g', '2015-11-01'], "--good=2015-11-01"),
|
||||
(['--bad=2015-11-03'], "--bad=2015-11-03"),
|
||||
])
|
||||
def test_app_bisect_nightlies_user_exit(create_app, argv, expected_log,
|
||||
mocker):
|
||||
@pytest.mark.parametrize(
|
||||
"argv,expected_log",
|
||||
[
|
||||
(["--app=firefox", "--bits=64"], "--app=firefox --bits=64"),
|
||||
(["--persist", "blah stuff"], "--persist 'blah stuff'"),
|
||||
(["--addon=a b c", "--addon=d"], "'--addon=a b c' --addon=d"),
|
||||
(["--find-fix", "--arg=a b"], "--find-fix '--arg=a b'"),
|
||||
(["--profile=pro file"], "'--profile=pro file'"),
|
||||
(["-g", "2015-11-01"], "--good=2015-11-01"),
|
||||
(["--bad=2015-11-03"], "--bad=2015-11-03"),
|
||||
],
|
||||
)
|
||||
def test_app_bisect_nightlies_user_exit(create_app, argv, expected_log, mocker):
|
||||
Handler = mocker.patch("mozregression.main.NightlyHandler")
|
||||
Handler.return_value = Mock(
|
||||
build_range=[Mock(repo_name="mozilla-central")],
|
||||
good_date='2015-11-01',
|
||||
bad_date='2015-11-03',
|
||||
spec=NightlyHandler
|
||||
good_date="2015-11-01",
|
||||
bad_date="2015-11-03",
|
||||
spec=NightlyHandler,
|
||||
)
|
||||
|
||||
app = create_app(argv)
|
||||
app.bisector.bisect = Mock(return_value=Bisection.USER_EXIT)
|
||||
sys = mocker.patch('mozregression.main.sys')
|
||||
sys = mocker.patch("mozregression.main.sys")
|
||||
sys.argv = argv
|
||||
assert app.bisect_nightlies() == 0
|
||||
assert create_app.find_in_log("To resume, run:")
|
||||
|
@ -169,12 +159,14 @@ def test_app_bisect_nightlies_user_exit(create_app, argv, expected_log,
|
|||
|
||||
def test_app_bisect_integration_user_exit(create_app, mocker):
|
||||
Handler = mocker.patch("mozregression.main.IntegrationHandler")
|
||||
Handler.return_value = Mock(build_range=[Mock(repo_name="mozilla-central")],
|
||||
good_revision='c1',
|
||||
bad_revision='c2',
|
||||
spec=IntegrationHandler)
|
||||
Handler.return_value = Mock(
|
||||
build_range=[Mock(repo_name="mozilla-central")],
|
||||
good_revision="c1",
|
||||
bad_revision="c2",
|
||||
spec=IntegrationHandler,
|
||||
)
|
||||
|
||||
app = create_app(['--good=c1', '--bad=c2'])
|
||||
app = create_app(["--good=c1", "--bad=c2"])
|
||||
app.bisector.bisect = Mock(return_value=Bisection.USER_EXIT)
|
||||
assert app.bisect_integration() == 0
|
||||
assert create_app.find_in_log("To resume, run:")
|
||||
|
@ -182,20 +174,17 @@ def test_app_bisect_integration_user_exit(create_app, mocker):
|
|||
|
||||
|
||||
def test_app_bisect_integration_no_data(create_app):
|
||||
app = create_app(['--good=c1', '--bad=c2'])
|
||||
app = create_app(["--good=c1", "--bad=c2"])
|
||||
app.bisector.bisect = Mock(return_value=Bisection.NO_DATA)
|
||||
assert app.bisect_integration() == 1
|
||||
assert create_app.find_in_log(
|
||||
"There are no build artifacts for these changesets",
|
||||
False
|
||||
)
|
||||
assert create_app.find_in_log("There are no build artifacts for these changesets", False)
|
||||
|
||||
|
||||
def test_app_bisect_ctrl_c_exit(create_app, mocker):
|
||||
app = create_app([])
|
||||
app.bisector.bisect = Mock(side_effect=KeyboardInterrupt)
|
||||
at_exit = mocker.patch('atexit.register')
|
||||
handler = MagicMock(good_revision='c1', bad_revision='c2')
|
||||
at_exit = mocker.patch("atexit.register")
|
||||
handler = MagicMock(good_revision="c1", bad_revision="c2")
|
||||
Handler = mocker.patch("mozregression.main.NightlyHandler")
|
||||
Handler.return_value = handler
|
||||
with pytest.raises(KeyboardInterrupt):
|
||||
|
@ -209,25 +198,25 @@ def test_app_bisect_ctrl_c_exit(create_app, mocker):
|
|||
|
||||
|
||||
class TestCheckMozregresionVersion(unittest.TestCase):
|
||||
@patch('mozregression.main.LOG')
|
||||
@patch('requests.get')
|
||||
@patch("mozregression.main.LOG")
|
||||
@patch("requests.get")
|
||||
def test_version_is_upto_date(self, get, log):
|
||||
response = Mock(json=lambda: {'info': {'version': __version__}})
|
||||
response = Mock(json=lambda: {"info": {"version": __version__}})
|
||||
get.return_value = response
|
||||
main.check_mozregression_version()
|
||||
self.assertFalse(log.critical.called)
|
||||
|
||||
@patch('requests.get')
|
||||
@patch("requests.get")
|
||||
def test_Exception_error(self, get):
|
||||
get.side_effect = requests.RequestException
|
||||
# exception is handled inside main.check_mozregression_version
|
||||
main.check_mozregression_version()
|
||||
self.assertRaises(requests.RequestException, get)
|
||||
|
||||
@patch('mozregression.main.LOG')
|
||||
@patch('requests.get')
|
||||
@patch("mozregression.main.LOG")
|
||||
@patch("requests.get")
|
||||
def test_warn_if_version_is_not_up_to_date(self, get, log):
|
||||
response = Mock(json=lambda: {'info': {'version': 0}})
|
||||
response = Mock(json=lambda: {"info": {"version": 0}})
|
||||
get.return_value = response
|
||||
main.check_mozregression_version()
|
||||
self.assertEqual(log.warning.call_count, 2)
|
||||
|
@ -238,26 +227,28 @@ class TestMain(unittest.TestCase):
|
|||
self.app = Mock()
|
||||
self.logger = Mock()
|
||||
|
||||
@patch('mozregression.main.LOG')
|
||||
@patch('mozregression.main.check_mozregression_version')
|
||||
@patch('mozlog.structured.commandline.setup_logging')
|
||||
@patch('mozregression.main.set_http_session')
|
||||
@patch('mozregression.main.Application')
|
||||
def do_cli(self, argv, Application, set_http_session,
|
||||
setup_logging, check_mozregression_version, log):
|
||||
@patch("mozregression.main.LOG")
|
||||
@patch("mozregression.main.check_mozregression_version")
|
||||
@patch("mozlog.structured.commandline.setup_logging")
|
||||
@patch("mozregression.main.set_http_session")
|
||||
@patch("mozregression.main.Application")
|
||||
def do_cli(
|
||||
self, argv, Application, set_http_session, setup_logging, check_mozregression_version, log,
|
||||
):
|
||||
self.logger = log
|
||||
|
||||
def create_app(fetch_config, options):
|
||||
self.app.fetch_config = fetch_config
|
||||
self.app.options = options
|
||||
return self.app
|
||||
|
||||
Application.side_effect = create_app
|
||||
try:
|
||||
main.main(argv)
|
||||
except SystemExit as exc:
|
||||
return exc.code
|
||||
else:
|
||||
self.fail('mozregression.main.cli did not call sys.exit')
|
||||
self.fail("mozregression.main.cli did not call sys.exit")
|
||||
|
||||
def pop_logs(self):
|
||||
logs = []
|
||||
|
@ -268,7 +259,7 @@ class TestMain(unittest.TestCase):
|
|||
|
||||
def pop_exit_error_msg(self):
|
||||
for lvl, msg in reversed(self.pop_logs()):
|
||||
if lvl == 'error':
|
||||
if lvl == "error":
|
||||
return msg
|
||||
|
||||
def test_without_args(self):
|
||||
|
@ -281,7 +272,7 @@ class TestMain(unittest.TestCase):
|
|||
|
||||
def test_bisect_integration(self):
|
||||
self.app.bisect_integration.return_value = 0
|
||||
exitcode = self.do_cli(['--good=a1', '--bad=b5'])
|
||||
exitcode = self.do_cli(["--good=a1", "--bad=b5"])
|
||||
self.assertEqual(exitcode, 0)
|
||||
self.app.bisect_integration.assert_called_with()
|
||||
|
||||
|
@ -289,15 +280,14 @@ class TestMain(unittest.TestCase):
|
|||
# KeyboardInterrupt is handled with a nice error message.
|
||||
self.app.bisect_nightlies.side_effect = KeyboardInterrupt
|
||||
exitcode = self.do_cli([])
|
||||
self.assertIn('Interrupted', exitcode)
|
||||
self.assertIn("Interrupted", exitcode)
|
||||
|
||||
def test_handle_mozregression_errors(self):
|
||||
# Any MozRegressionError subclass is handled with a nice error message
|
||||
self.app.bisect_nightlies.side_effect = \
|
||||
errors.MozRegressionError('my error')
|
||||
self.app.bisect_nightlies.side_effect = errors.MozRegressionError("my error")
|
||||
exitcode = self.do_cli([])
|
||||
self.assertNotEqual(exitcode, 0)
|
||||
self.assertIn('my error', self.pop_exit_error_msg())
|
||||
self.assertIn("my error", self.pop_exit_error_msg())
|
||||
|
||||
def test_handle_other_errors(self):
|
||||
# other exceptions are just thrown as usual
|
||||
|
|
|
@ -1,71 +1,75 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from mock import patch, Mock
|
||||
from mock import Mock, patch
|
||||
|
||||
from mozregression import network
|
||||
|
||||
|
||||
class TestUrlLinks(unittest.TestCase):
|
||||
@patch('requests.get')
|
||||
@patch("requests.get")
|
||||
def test_url_no_links(self, get):
|
||||
get.return_value = Mock(text='')
|
||||
self.assertEqual(network.url_links(''), [])
|
||||
get.return_value = Mock(text="")
|
||||
self.assertEqual(network.url_links(""), [])
|
||||
|
||||
@patch('requests.get')
|
||||
@patch("requests.get")
|
||||
def test_url_with_links(self, get):
|
||||
get.return_value = Mock(text="""
|
||||
get.return_value = Mock(
|
||||
text="""
|
||||
<body>
|
||||
<a href="thing/">thing</a>
|
||||
<a href="thing2/">thing2</a>
|
||||
</body>
|
||||
""")
|
||||
self.assertEqual(network.url_links(''),
|
||||
['thing/', 'thing2/'])
|
||||
"""
|
||||
)
|
||||
self.assertEqual(network.url_links(""), ["thing/", "thing2/"])
|
||||
|
||||
@patch('requests.get')
|
||||
@patch("requests.get")
|
||||
def test_url_with_links_regex(self, get):
|
||||
get.return_value = Mock(text="""
|
||||
get.return_value = Mock(
|
||||
text="""
|
||||
<body>
|
||||
<a href="thing/">thing</a>
|
||||
<a href="thing2/">thing2</a>
|
||||
</body>
|
||||
""")
|
||||
self.assertEqual(
|
||||
network.url_links('', regex="thing2.*"),
|
||||
['thing2/'])
|
||||
"""
|
||||
)
|
||||
self.assertEqual(network.url_links("", regex="thing2.*"), ["thing2/"])
|
||||
|
||||
@patch('requests.get')
|
||||
@patch("requests.get")
|
||||
def test_url_with_absolute_links(self, get):
|
||||
get.return_value = Mock(text="""
|
||||
get.return_value = Mock(
|
||||
text="""
|
||||
<body>
|
||||
<a href="/useless/thing/">thing</a>
|
||||
<a href="/useless/thing2">thing2</a>
|
||||
</body>
|
||||
""")
|
||||
self.assertEqual(network.url_links(''),
|
||||
['thing/', 'thing2'])
|
||||
"""
|
||||
)
|
||||
self.assertEqual(network.url_links(""), ["thing/", "thing2"])
|
||||
|
||||
|
||||
def test_set_http_session():
|
||||
try:
|
||||
with patch('requests.Session') as Session:
|
||||
with patch("requests.Session") as Session:
|
||||
session = Session.return_value = Mock()
|
||||
session_get = session.get
|
||||
|
||||
network.set_http_session(get_defaults={'timeout': 5})
|
||||
network.set_http_session(get_defaults={"timeout": 5})
|
||||
|
||||
assert session == network.get_http_session()
|
||||
# timeout = 5 will be passed to the original get method as a default
|
||||
session.get('http://my-ul')
|
||||
session_get.assert_called_with('http://my-ul', timeout=5)
|
||||
session.get("http://my-ul")
|
||||
session_get.assert_called_with("http://my-ul", timeout=5)
|
||||
# if timeout is defined, it will override the default
|
||||
session.get('http://my-ul', timeout=10)
|
||||
session_get.assert_called_with('http://my-ul', timeout=10)
|
||||
session.get("http://my-ul", timeout=10)
|
||||
session_get.assert_called_with("http://my-ul", timeout=10)
|
||||
|
||||
finally:
|
||||
# remove the global session to not impact other tests
|
||||
network.SESSION = None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
from __future__ import absolute_import
|
||||
import pytest
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
import mozfile
|
||||
import time
|
||||
|
||||
import mozfile
|
||||
import pytest
|
||||
|
||||
from mozregression.persist_limit import PersistLimit
|
||||
|
||||
|
||||
|
@ -17,8 +19,8 @@ class TempCreator(object):
|
|||
|
||||
def create_file(self, name, size, delay):
|
||||
fname = os.path.join(self.tempdir, name)
|
||||
with open(fname, 'w') as f:
|
||||
f.write('a' * size)
|
||||
with open(fname, "w") as f:
|
||||
f.write("a" * size)
|
||||
# equivalent to touch, but we apply a delay for the test
|
||||
atime = time.time() + delay
|
||||
os.utime(fname, (atime, atime))
|
||||
|
@ -31,7 +33,9 @@ def temp():
|
|||
mozfile.remove(tmp.tempdir)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("size_limit,file_limit,files", [
|
||||
@pytest.mark.parametrize(
|
||||
"size_limit,file_limit,files",
|
||||
[
|
||||
# limit_file is always respected
|
||||
(10, 5, "bcdef"),
|
||||
(10, 3, "def"),
|
||||
|
@ -40,7 +44,8 @@ def temp():
|
|||
(5, 0, "abcdef"),
|
||||
# limit_size works
|
||||
(35, 1, "def"),
|
||||
])
|
||||
],
|
||||
)
|
||||
def test_persist_limit(temp, size_limit, file_limit, files):
|
||||
temp.create_file("a", 10, -6)
|
||||
temp.create_file("b", 10, -5)
|
||||
|
@ -53,4 +58,4 @@ def test_persist_limit(temp, size_limit, file_limit, files):
|
|||
persist_limit.register_dir_content(temp.tempdir)
|
||||
persist_limit.remove_old_files()
|
||||
|
||||
assert ''.join(sorted(temp.list())) == ''.join(sorted(files))
|
||||
assert "".join(sorted(temp.list())) == "".join(sorted(files))
|
||||
|
|
|
@ -1,10 +1,15 @@
|
|||
from __future__ import absolute_import
|
||||
|
||||
import unittest
|
||||
|
||||
from mozregression import errors
|
||||
from mozregression.releases import (releases, formatted_valid_release_dates,
|
||||
date_of_release, tag_of_release,
|
||||
tag_of_beta)
|
||||
from mozregression.releases import (
|
||||
date_of_release,
|
||||
formatted_valid_release_dates,
|
||||
releases,
|
||||
tag_of_beta,
|
||||
tag_of_release,
|
||||
)
|
||||
|
||||
|
||||
class TestRelease(unittest.TestCase):
|
||||
|
@ -15,7 +20,7 @@ class TestRelease(unittest.TestCase):
|
|||
self.assertEqual(date, "2012-06-05")
|
||||
date = date_of_release(34)
|
||||
self.assertEqual(date, "2014-09-02")
|
||||
date = date_of_release('33')
|
||||
date = date_of_release("33")
|
||||
self.assertEqual(date, "2014-07-21")
|
||||
|
||||
def test_valid_formatted_release_dates(self):
|
||||
|
@ -39,36 +44,36 @@ class TestRelease(unittest.TestCase):
|
|||
with self.assertRaises(errors.UnavailableRelease):
|
||||
date_of_release(441)
|
||||
with self.assertRaises(errors.UnavailableRelease):
|
||||
date_of_release('ew21rtw112')
|
||||
date_of_release("ew21rtw112")
|
||||
|
||||
def test_valid_release_tags(self):
|
||||
tag = tag_of_release('57.0')
|
||||
tag = tag_of_release("57.0")
|
||||
self.assertEqual(tag, "FIREFOX_57_0_RELEASE")
|
||||
tag = tag_of_release('60')
|
||||
tag = tag_of_release("60")
|
||||
self.assertEqual(tag, "FIREFOX_60_0_RELEASE")
|
||||
tag = tag_of_release('65.0.1')
|
||||
tag = tag_of_release("65.0.1")
|
||||
self.assertEqual(tag, "FIREFOX_65_0_1_RELEASE")
|
||||
|
||||
def test_invalid_release_tags(self):
|
||||
with self.assertRaises(errors.UnavailableRelease):
|
||||
tag_of_release('55.0.1.1')
|
||||
tag_of_release("55.0.1.1")
|
||||
with self.assertRaises(errors.UnavailableRelease):
|
||||
tag_of_release('57.0b4')
|
||||
tag_of_release("57.0b4")
|
||||
with self.assertRaises(errors.UnavailableRelease):
|
||||
tag_of_release('abc')
|
||||
tag_of_release("abc")
|
||||
|
||||
def test_valid_beta_tags(self):
|
||||
tag = tag_of_beta('57.0b9')
|
||||
tag = tag_of_beta("57.0b9")
|
||||
self.assertEqual(tag, "FIREFOX_57_0b9_RELEASE")
|
||||
tag = tag_of_beta('60.0b12')
|
||||
tag = tag_of_beta("60.0b12")
|
||||
self.assertEqual(tag, "FIREFOX_60_0b12_RELEASE")
|
||||
tag = tag_of_beta('65')
|
||||
tag = tag_of_beta("65")
|
||||
self.assertEqual(tag, "FIREFOX_RELEASE_65_BASE")
|
||||
tag = tag_of_beta('66.0')
|
||||
tag = tag_of_beta("66.0")
|
||||
self.assertEqual(tag, "FIREFOX_RELEASE_66_BASE")
|
||||
|
||||
def test_invalid_beta_tags(self):
|
||||
with self.assertRaises(errors.UnavailableRelease):
|
||||
tag_of_beta('57.0.1')
|
||||
tag_of_beta("57.0.1")
|
||||
with self.assertRaises(errors.UnavailableRelease):
|
||||
tag_of_beta('xyz')
|
||||
tag_of_beta("xyz")
|
||||
|
|
|
@ -3,14 +3,16 @@
|
|||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
from __future__ import absolute_import
|
||||
import pytest
|
||||
import unittest
|
||||
import datetime
|
||||
from mock import patch, Mock
|
||||
|
||||
from mozregression import test_runner, errors, build_info
|
||||
import datetime
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
from mock import Mock, patch
|
||||
from six.moves import range
|
||||
|
||||
from mozregression import build_info, errors, test_runner
|
||||
|
||||
|
||||
def mockinfo(**kwargs):
|
||||
return Mock(spec=build_info.BuildInfo, **kwargs)
|
||||
|
@ -32,81 +34,69 @@ class TestManualTestRunner(unittest.TestCase):
|
|||
def setUp(self):
|
||||
self.runner = test_runner.ManualTestRunner()
|
||||
|
||||
@patch('mozregression.test_runner.mozlauncher')
|
||||
@patch("mozregression.test_runner.mozlauncher")
|
||||
def test_nightly_create_launcher(self, create_launcher):
|
||||
launcher = Mock()
|
||||
create_launcher.return_value = launcher
|
||||
info = mockinfo(
|
||||
build_type='nightly',
|
||||
app_name="firefox",
|
||||
build_file="/path/to"
|
||||
)
|
||||
info = mockinfo(build_type="nightly", app_name="firefox", build_file="/path/to")
|
||||
result_launcher = test_runner.create_launcher(info)
|
||||
create_launcher.\
|
||||
assert_called_with(info)
|
||||
create_launcher.assert_called_with(info)
|
||||
|
||||
self.assertEqual(result_launcher, launcher)
|
||||
|
||||
@patch('mozregression.test_runner.mozlauncher')
|
||||
@patch('mozregression.test_runner.LOG')
|
||||
@patch("mozregression.test_runner.mozlauncher")
|
||||
@patch("mozregression.test_runner.LOG")
|
||||
def test_nightly_create_launcher_buildid(self, log, mozlauncher):
|
||||
launcher = Mock()
|
||||
mozlauncher.return_value = launcher
|
||||
info = mockinfo(
|
||||
build_type='nightly',
|
||||
build_type="nightly",
|
||||
app_name="firefox",
|
||||
build_file="/path/to",
|
||||
build_date=datetime.datetime(2015, 11, 6, 5, 4, 3),
|
||||
repo_name='mozilla-central',
|
||||
repo_name="mozilla-central",
|
||||
)
|
||||
result_launcher = test_runner.create_launcher(info)
|
||||
mozlauncher.\
|
||||
assert_called_with(info)
|
||||
log.info.assert_called_with(
|
||||
'Running mozilla-central build for buildid 20151106050403')
|
||||
mozlauncher.assert_called_with(info)
|
||||
log.info.assert_called_with("Running mozilla-central build for buildid 20151106050403")
|
||||
|
||||
self.assertEqual(result_launcher, launcher)
|
||||
|
||||
@patch('mozregression.download_manager.DownloadManager.download')
|
||||
@patch('mozregression.test_runner.mozlauncher')
|
||||
@patch("mozregression.download_manager.DownloadManager.download")
|
||||
@patch("mozregression.test_runner.mozlauncher")
|
||||
def test_inbound_create_launcher(self, mozlauncher, download):
|
||||
launcher = Mock()
|
||||
mozlauncher.return_value = launcher
|
||||
info = mockinfo(
|
||||
build_type='inbound',
|
||||
app_name="firefox",
|
||||
build_file="/path/to"
|
||||
)
|
||||
info = mockinfo(build_type="inbound", app_name="firefox", build_file="/path/to")
|
||||
result_launcher = test_runner.create_launcher(info)
|
||||
mozlauncher.assert_called_with(info)
|
||||
self.assertEqual(result_launcher, launcher)
|
||||
|
||||
@patch('mozregression.test_runner.input')
|
||||
@patch("mozregression.test_runner.input")
|
||||
def test_get_verdict(self, input):
|
||||
input.return_value = 'g'
|
||||
verdict = self.runner.get_verdict(mockinfo(build_type='inbound'),
|
||||
False)
|
||||
self.assertEqual(verdict, 'g')
|
||||
input.return_value = "g"
|
||||
verdict = self.runner.get_verdict(mockinfo(build_type="inbound"), False)
|
||||
self.assertEqual(verdict, "g")
|
||||
|
||||
output = input.call_args[0][0]
|
||||
# bad is proposed
|
||||
self.assertIn('bad', output)
|
||||
self.assertIn("bad", output)
|
||||
# back is not
|
||||
self.assertNotIn('back', output)
|
||||
self.assertNotIn("back", output)
|
||||
|
||||
@patch('mozregression.test_runner.input')
|
||||
@patch("mozregression.test_runner.input")
|
||||
def test_get_verdict_allow_back(self, input):
|
||||
input.return_value = 'back'
|
||||
verdict = self.runner.get_verdict(mockinfo(build_type='inbound'), True)
|
||||
input.return_value = "back"
|
||||
verdict = self.runner.get_verdict(mockinfo(build_type="inbound"), True)
|
||||
output = input.call_args[0][0]
|
||||
# back is now proposed
|
||||
self.assertIn('back', output)
|
||||
self.assertEqual(verdict, 'back')
|
||||
self.assertIn("back", output)
|
||||
self.assertEqual(verdict, "back")
|
||||
|
||||
@patch('mozregression.test_runner.create_launcher')
|
||||
@patch('mozregression.test_runner.ManualTestRunner.get_verdict')
|
||||
@patch("mozregression.test_runner.create_launcher")
|
||||
@patch("mozregression.test_runner.ManualTestRunner.get_verdict")
|
||||
def test_evaluate(self, get_verdict, create_launcher):
|
||||
get_verdict.return_value = 'g'
|
||||
get_verdict.return_value = "g"
|
||||
launcher = Mock()
|
||||
create_launcher.return_value = Launcher(launcher)
|
||||
build_infos = mockinfo()
|
||||
|
@ -117,13 +107,12 @@ class TestManualTestRunner(unittest.TestCase):
|
|||
launcher.start.assert_called_with()
|
||||
get_verdict.assert_called_with(build_infos, False)
|
||||
launcher.stop.assert_called_with()
|
||||
self.assertEqual(result[0], 'g')
|
||||
self.assertEqual(result[0], "g")
|
||||
|
||||
@patch('mozregression.test_runner.create_launcher')
|
||||
@patch('mozregression.test_runner.ManualTestRunner.get_verdict')
|
||||
def test_evaluate_with_launcher_error_on_stop(self, get_verdict,
|
||||
create_launcher):
|
||||
get_verdict.return_value = 'g'
|
||||
@patch("mozregression.test_runner.create_launcher")
|
||||
@patch("mozregression.test_runner.ManualTestRunner.get_verdict")
|
||||
def test_evaluate_with_launcher_error_on_stop(self, get_verdict, create_launcher):
|
||||
get_verdict.return_value = "g"
|
||||
launcher = Mock(stop=Mock(side_effect=errors.LauncherError))
|
||||
create_launcher.return_value = Launcher(launcher)
|
||||
build_infos = mockinfo()
|
||||
|
@ -131,9 +120,9 @@ class TestManualTestRunner(unittest.TestCase):
|
|||
|
||||
# the LauncherError is silently ignore here
|
||||
launcher.stop.assert_called_with()
|
||||
self.assertEqual(result[0], 'g')
|
||||
self.assertEqual(result[0], "g")
|
||||
|
||||
@patch('mozregression.test_runner.create_launcher')
|
||||
@patch("mozregression.test_runner.create_launcher")
|
||||
def test_run_once(self, create_launcher):
|
||||
launcher = Mock(wait=Mock(return_value=0))
|
||||
create_launcher.return_value = Launcher(launcher)
|
||||
|
@ -144,7 +133,7 @@ class TestManualTestRunner(unittest.TestCase):
|
|||
launcher.start.assert_called_with()
|
||||
launcher.wait.assert_called_with()
|
||||
|
||||
@patch('mozregression.test_runner.create_launcher')
|
||||
@patch("mozregression.test_runner.create_launcher")
|
||||
def test_run_once_ctrlc(self, create_launcher):
|
||||
launcher = Mock(wait=Mock(side_effect=KeyboardInterrupt))
|
||||
create_launcher.return_value = Launcher(launcher)
|
||||
|
@ -159,115 +148,111 @@ class TestManualTestRunner(unittest.TestCase):
|
|||
|
||||
class TestCommandTestRunner(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.runner = test_runner.CommandTestRunner('my command')
|
||||
self.runner = test_runner.CommandTestRunner("my command")
|
||||
self.launcher = Mock()
|
||||
del self.launcher.binary # block the auto attr binary on the mock
|
||||
|
||||
if not hasattr(self, 'assertRaisesRegex'):
|
||||
if not hasattr(self, "assertRaisesRegex"):
|
||||
self.assertRaisesRegex = self.assertRaisesRegexp
|
||||
|
||||
def test_create(self):
|
||||
self.assertEqual(self.runner.command, 'my command')
|
||||
self.assertEqual(self.runner.command, "my command")
|
||||
|
||||
@patch('mozregression.test_runner.create_launcher')
|
||||
@patch('subprocess.call')
|
||||
def evaluate(self, call, create_launcher, build_info={},
|
||||
retcode=0, subprocess_call_effect=None):
|
||||
build_info['app_name'] = 'myapp'
|
||||
@patch("mozregression.test_runner.create_launcher")
|
||||
@patch("subprocess.call")
|
||||
def evaluate(
|
||||
self, call, create_launcher, build_info={}, retcode=0, subprocess_call_effect=None,
|
||||
):
|
||||
build_info["app_name"] = "myapp"
|
||||
call.return_value = retcode
|
||||
if subprocess_call_effect:
|
||||
call.side_effect = subprocess_call_effect
|
||||
self.subprocess_call = call
|
||||
create_launcher.return_value = Launcher(self.launcher)
|
||||
return self.runner.evaluate(
|
||||
mockinfo(to_dict=lambda: build_info)
|
||||
)[0]
|
||||
return self.runner.evaluate(mockinfo(to_dict=lambda: build_info))[0]
|
||||
|
||||
def test_evaluate_retcode(self):
|
||||
self.assertEqual('g', self.evaluate(retcode=0))
|
||||
self.assertEqual('b', self.evaluate(retcode=1))
|
||||
self.assertEqual("g", self.evaluate(retcode=0))
|
||||
self.assertEqual("b", self.evaluate(retcode=1))
|
||||
|
||||
def test_supbrocess_call(self):
|
||||
self.evaluate()
|
||||
command = self.subprocess_call.mock_calls[0][1][0]
|
||||
kwargs = self.subprocess_call.mock_calls[0][2]
|
||||
self.assertEqual(command, ['my', 'command'])
|
||||
self.assertIn('env', kwargs)
|
||||
self.assertEqual(command, ["my", "command"])
|
||||
self.assertIn("env", kwargs)
|
||||
|
||||
def test_env_vars(self):
|
||||
self.evaluate(build_info={'my': 'var', 'int': 15})
|
||||
self.evaluate(build_info={"my": "var", "int": 15})
|
||||
expected = {
|
||||
'MOZREGRESSION_MY': 'var',
|
||||
'MOZREGRESSION_INT': '15',
|
||||
'MOZREGRESSION_APP_NAME': 'myapp',
|
||||
"MOZREGRESSION_MY": "var",
|
||||
"MOZREGRESSION_INT": "15",
|
||||
"MOZREGRESSION_APP_NAME": "myapp",
|
||||
}
|
||||
passed_env = self.subprocess_call.mock_calls[0][2]['env']
|
||||
passed_env = self.subprocess_call.mock_calls[0][2]["env"]
|
||||
self.assertTrue(set(expected).issubset(set(passed_env)))
|
||||
|
||||
def test_command_placeholder_replaced(self):
|
||||
self.runner.command = 'run {app_name} "1"'
|
||||
self.evaluate()
|
||||
command = self.subprocess_call.mock_calls[0][1][0]
|
||||
self.assertEqual(command, ['run', 'myapp', '1'])
|
||||
self.assertEqual(command, ["run", "myapp", "1"])
|
||||
|
||||
self.runner.command = 'run \'{binary}\' "{foo}"'
|
||||
self.launcher.binary = 'mybinary'
|
||||
self.evaluate(build_info={'foo': 12})
|
||||
self.runner.command = "run '{binary}' \"{foo}\""
|
||||
self.launcher.binary = "mybinary"
|
||||
self.evaluate(build_info={"foo": 12})
|
||||
command = self.subprocess_call.mock_calls[0][1][0]
|
||||
self.assertEqual(command, ['run', 'mybinary', '12'])
|
||||
self.assertEqual(command, ["run", "mybinary", "12"])
|
||||
|
||||
def test_command_placeholder_error(self):
|
||||
self.runner.command = 'run {app_nam} "1"'
|
||||
self.assertRaisesRegex(errors.TestCommandError,
|
||||
'formatting',
|
||||
self.evaluate)
|
||||
self.assertRaisesRegex(errors.TestCommandError, "formatting", self.evaluate)
|
||||
|
||||
def test_command_empty_error(self):
|
||||
# in case the command line is empty,
|
||||
# subprocess.call will raise IndexError
|
||||
self.assertRaisesRegex(errors.TestCommandError,
|
||||
'Empty', self.evaluate,
|
||||
subprocess_call_effect=IndexError)
|
||||
self.assertRaisesRegex(
|
||||
errors.TestCommandError, "Empty", self.evaluate, subprocess_call_effect=IndexError,
|
||||
)
|
||||
|
||||
def test_command_missing_error(self):
|
||||
# in case the command is missing or not executable,
|
||||
# subprocess.call will raise IOError
|
||||
self.assertRaisesRegex(errors.TestCommandError,
|
||||
'not found', self.evaluate,
|
||||
subprocess_call_effect=OSError)
|
||||
self.assertRaisesRegex(
|
||||
errors.TestCommandError, "not found", self.evaluate, subprocess_call_effect=OSError,
|
||||
)
|
||||
|
||||
def test_run_once(self):
|
||||
self.runner.evaluate = Mock(return_value='g')
|
||||
self.runner.evaluate = Mock(return_value="g")
|
||||
build_info = Mock()
|
||||
self.assertEqual(self.runner.run_once(build_info), 0)
|
||||
self.runner.evaluate.assert_called_once_with(build_info)
|
||||
|
||||
|
||||
# useful fixture
|
||||
from .test_build_range import range_creator # noqa
|
||||
|
||||
|
||||
@pytest.mark.parametrize('brange,input,allowed_range,result', [ # noqa
|
||||
@pytest.mark.parametrize(
|
||||
"brange,input,allowed_range,result",
|
||||
[ # noqa
|
||||
# [0, 1, 2, 3, 4, 5] (6 elements, mid is '3')
|
||||
(list(range(6)), ['-2'], '[-2, 1]', 1),
|
||||
(list(range(6)), ["-2"], "[-2, 1]", 1),
|
||||
# [0, 1, 2, 3, 4] (5 elements, mid is '2')
|
||||
(list(range(5)), ['1'], '[-1, 1]', 3),
|
||||
(list(range(5)), ["1"], "[-1, 1]", 3),
|
||||
# user hit something bad, we loop
|
||||
(list(range(5)), ['aa', '', '1'], '[-1, 1]', 3),
|
||||
(list(range(5)), ["aa", "", "1"], "[-1, 1]", 3),
|
||||
# small range, no input
|
||||
(list(range(3)), Exception('input called, it should not happen'), None, 1)
|
||||
])
|
||||
def test_index_to_try_after_skip(mocker, range_creator, brange,
|
||||
input, allowed_range, result):
|
||||
(list(range(3)), Exception("input called, it should not happen"), None, 1),
|
||||
],
|
||||
)
|
||||
def test_index_to_try_after_skip(mocker, range_creator, brange, input, allowed_range, result):
|
||||
build_range = range_creator.create(brange)
|
||||
mocked_input = mocker.patch("mozregression.test_runner.input")
|
||||
mocked_input.side_effect = input
|
||||
output = []
|
||||
mocked_stdout = mocker.patch('sys.stdout')
|
||||
mocked_stdout = mocker.patch("sys.stdout")
|
||||
mocked_stdout.write = output.append
|
||||
|
||||
runner = test_runner.ManualTestRunner()
|
||||
assert runner.index_to_try_after_skip(build_range) == result
|
||||
if allowed_range is not None:
|
||||
assert ("You can choose a build index between %s:"
|
||||
% allowed_range) in [o.strip() for o in output]
|
||||
assert ("You can choose a build index between %s:" % allowed_range) in [
|
||||
o.strip() for o in output
|
||||
]
|
||||
|
|
Загрузка…
Ссылка в новой задаче