2015-03-20 15:38:09 +03:00
|
|
|
import sys
|
2016-01-22 13:57:08 +03:00
|
|
|
import threading
|
2020-03-27 23:17:37 +03:00
|
|
|
|
2022-08-22 23:28:27 +03:00
|
|
|
from PySide6.QtCore import QObject, QTimer, Signal, Slot
|
|
|
|
from PySide6.QtWidgets import QMessageBox
|
2015-03-06 17:29:01 +03:00
|
|
|
|
2016-01-19 02:46:35 +03:00
|
|
|
from mozregression.approx_persist import ApproxPersistChooser
|
2020-03-27 23:17:37 +03:00
|
|
|
from mozregression.bisector import (
|
|
|
|
Bisection,
|
|
|
|
Bisector,
|
|
|
|
IndexPromise,
|
|
|
|
IntegrationHandler,
|
|
|
|
NightlyHandler,
|
|
|
|
)
|
2016-02-14 02:18:19 +03:00
|
|
|
from mozregression.config import DEFAULT_EXPAND
|
2020-03-27 23:17:37 +03:00
|
|
|
from mozregression.dates import is_date_or_datetime
|
|
|
|
from mozregression.errors import MozRegressionError
|
2015-12-06 12:37:35 +03:00
|
|
|
from mozregui.build_runner import AbstractBuildRunner
|
2015-12-28 17:19:08 +03:00
|
|
|
from mozregui.log_report import log
|
2020-03-27 23:17:37 +03:00
|
|
|
from mozregui.skip_chooser import SkipDialog
|
2015-03-22 14:31:56 +03:00
|
|
|
|
2015-03-20 15:38:09 +03:00
|
|
|
Bisection.EXCEPTION = -1 # new possible value of bisection end
|
2015-03-06 17:29:01 +03:00
|
|
|
|
|
|
|
|
|
|
|
class GuiBisector(QObject, Bisector):
|
2015-03-20 20:04:16 +03:00
|
|
|
started = Signal()
|
2015-03-07 15:16:01 +03:00
|
|
|
finished = Signal(object, int)
|
2015-09-12 09:04:44 +03:00
|
|
|
choose_next_build = Signal()
|
2015-03-21 01:50:40 +03:00
|
|
|
step_started = Signal(object)
|
|
|
|
step_build_found = Signal(object, object)
|
2015-03-28 19:14:19 +03:00
|
|
|
step_testing = Signal(object, object)
|
2015-03-21 01:50:40 +03:00
|
|
|
step_finished = Signal(object, str)
|
2015-11-14 16:06:28 +03:00
|
|
|
handle_merge = Signal(object, str, str, str)
|
2015-03-06 17:29:01 +03:00
|
|
|
|
2020-03-27 23:17:37 +03:00
|
|
|
def __init__(self, fetch_config, test_runner, download_manager, download_in_background=True):
|
2023-06-01 17:56:23 +03:00
|
|
|
super().__init__(
|
|
|
|
fetch_config=fetch_config,
|
|
|
|
test_runner=test_runner,
|
|
|
|
download_manager=download_manager,
|
|
|
|
dl_in_background=download_in_background,
|
|
|
|
)
|
2015-03-06 17:29:01 +03:00
|
|
|
self.bisection = None
|
|
|
|
self.mid = None
|
|
|
|
self.build_infos = None
|
2015-03-18 15:31:03 +03:00
|
|
|
self._bisect_args = None
|
2015-03-20 15:38:09 +03:00
|
|
|
self.error = None
|
2015-09-12 09:04:44 +03:00
|
|
|
self._next_build_index = None
|
2015-12-15 21:54:16 +03:00
|
|
|
self.download_in_background = download_in_background
|
|
|
|
self.index_promise = None
|
2016-01-19 02:46:35 +03:00
|
|
|
self._persist_files = ()
|
2016-01-22 13:57:08 +03:00
|
|
|
self.should_stop = threading.Event()
|
2015-03-06 17:29:01 +03:00
|
|
|
|
2020-03-27 23:17:37 +03:00
|
|
|
self.download_manager.download_finished.connect(self._build_dl_finished)
|
2015-03-06 17:29:01 +03:00
|
|
|
self.test_runner.evaluate_finished.connect(self._evaluate_finished)
|
|
|
|
|
2015-03-20 15:38:09 +03:00
|
|
|
def _finish_on_exception(self, bisection):
|
|
|
|
self.error = sys.exc_info()
|
|
|
|
self.finished.emit(bisection, Bisection.EXCEPTION)
|
|
|
|
|
2015-03-18 15:31:03 +03:00
|
|
|
@Slot()
|
|
|
|
def bisect(self):
|
|
|
|
# this is a slot so it will be called in the thread
|
2015-03-20 20:04:16 +03:00
|
|
|
self.started.emit()
|
2015-03-20 15:38:09 +03:00
|
|
|
try:
|
|
|
|
Bisector.bisect(self, *self._bisect_args)
|
|
|
|
except MozRegressionError:
|
|
|
|
self._finish_on_exception(None)
|
2015-03-18 15:31:03 +03:00
|
|
|
|
2015-03-20 21:11:15 +03:00
|
|
|
@Slot()
|
2015-11-14 16:06:28 +03:00
|
|
|
def bisect_further(self):
|
2015-03-20 21:11:15 +03:00
|
|
|
assert self.bisection
|
|
|
|
self.started.emit()
|
2015-11-14 16:06:28 +03:00
|
|
|
handler = self.bisection.handler
|
2015-03-20 21:11:15 +03:00
|
|
|
try:
|
2020-02-13 22:01:46 +03:00
|
|
|
nhandler = IntegrationHandler(find_fix=self.bisection.handler.find_fix)
|
2020-03-27 23:17:37 +03:00
|
|
|
Bisector.bisect(
|
|
|
|
self,
|
|
|
|
nhandler,
|
|
|
|
handler.good_revision,
|
|
|
|
handler.bad_revision,
|
|
|
|
expand=DEFAULT_EXPAND,
|
|
|
|
interrupt=self.should_stop.is_set,
|
|
|
|
)
|
2015-03-20 21:11:15 +03:00
|
|
|
except MozRegressionError:
|
|
|
|
self._finish_on_exception(None)
|
2016-02-14 02:18:19 +03:00
|
|
|
except StopIteration:
|
|
|
|
self.finished.emit(None, Bisection.USER_EXIT)
|
2015-03-20 21:11:15 +03:00
|
|
|
|
2015-11-14 16:06:28 +03:00
|
|
|
@Slot()
|
|
|
|
def check_merge(self):
|
|
|
|
handler = self.bisection.handler
|
|
|
|
try:
|
|
|
|
result = handler.handle_merge()
|
|
|
|
except MozRegressionError:
|
|
|
|
self._finish_on_exception(None)
|
|
|
|
return
|
|
|
|
if result is None:
|
|
|
|
self.bisection.no_more_merge = True
|
2016-02-11 11:34:47 +03:00
|
|
|
self.finished.emit(self.bisection, Bisection.FINISHED)
|
2015-11-14 16:06:28 +03:00
|
|
|
else:
|
|
|
|
self.handle_merge.emit(self.bisection, *result)
|
|
|
|
|
2015-08-31 11:55:14 +03:00
|
|
|
def _bisect(self, handler, build_range):
|
2020-03-27 23:17:37 +03:00
|
|
|
self.bisection = Bisection(
|
|
|
|
handler,
|
|
|
|
build_range,
|
|
|
|
self.download_manager,
|
|
|
|
self.test_runner,
|
|
|
|
dl_in_background=False,
|
|
|
|
approx_chooser=self.approx_chooser,
|
|
|
|
)
|
2015-03-20 20:04:16 +03:00
|
|
|
self._bisect_next()
|
2015-03-06 17:29:01 +03:00
|
|
|
|
|
|
|
@Slot()
|
|
|
|
def _bisect_next(self):
|
2015-03-18 15:31:03 +03:00
|
|
|
# this is executed in the working thread
|
2020-03-27 23:17:37 +03:00
|
|
|
if self.test_runner.verdict != "r":
|
2016-02-03 14:56:10 +03:00
|
|
|
try:
|
2020-03-27 23:17:37 +03:00
|
|
|
self.mid = self.bisection.search_mid_point(interrupt=self.should_stop.is_set)
|
2016-02-03 14:56:10 +03:00
|
|
|
except MozRegressionError:
|
|
|
|
self._finish_on_exception(self.bisection)
|
|
|
|
return
|
|
|
|
except StopIteration:
|
|
|
|
return
|
2015-09-12 09:04:44 +03:00
|
|
|
|
|
|
|
# if our last answer was skip, and that the next build
|
|
|
|
# to use is not chosen yet, ask to choose it.
|
2020-03-27 23:17:37 +03:00
|
|
|
if (
|
|
|
|
self._next_build_index is None
|
|
|
|
and self.test_runner.verdict == "s"
|
|
|
|
and len(self.bisection.build_range) > 3
|
|
|
|
):
|
2015-09-12 09:04:44 +03:00
|
|
|
self.choose_next_build.emit()
|
|
|
|
return
|
|
|
|
|
|
|
|
if self._next_build_index is not None:
|
|
|
|
# here user asked for specific build (eg from choose_next_build)
|
2016-02-03 14:56:10 +03:00
|
|
|
self.mid = self._next_build_index
|
2015-09-12 09:04:44 +03:00
|
|
|
# this will download build infos if required
|
2016-02-03 14:56:10 +03:00
|
|
|
if self.bisection.build_range[self.mid] is False:
|
2015-09-12 09:04:44 +03:00
|
|
|
# in case no build info is found, ask to choose again
|
|
|
|
self.choose_next_build.emit()
|
|
|
|
return
|
|
|
|
self._next_build_index = None
|
|
|
|
|
|
|
|
self.step_started.emit(self.bisection)
|
2016-02-03 14:56:10 +03:00
|
|
|
result = self.bisection.init_handler(self.mid)
|
2015-03-06 17:29:01 +03:00
|
|
|
if result != Bisection.RUNNING:
|
2015-03-07 15:16:01 +03:00
|
|
|
self.finished.emit(self.bisection, result)
|
2015-03-06 17:29:01 +03:00
|
|
|
else:
|
2016-02-03 14:56:10 +03:00
|
|
|
self.build_infos = self.bisection.handler.build_range[self.mid]
|
2020-03-27 23:17:37 +03:00
|
|
|
(
|
|
|
|
found,
|
|
|
|
self.mid,
|
|
|
|
self.build_infos,
|
|
|
|
self._persist_files,
|
|
|
|
) = self.bisection._find_approx_build(self.mid, self.build_infos)
|
2016-01-19 02:46:35 +03:00
|
|
|
if not found:
|
|
|
|
self.download_manager.focus_download(self.build_infos)
|
2015-03-21 01:50:40 +03:00
|
|
|
self.step_build_found.emit(self.bisection, self.build_infos)
|
2016-01-19 02:46:35 +03:00
|
|
|
if found:
|
|
|
|
# to continue the bisection, act as if it was downloaded
|
|
|
|
self._build_dl_finished(None, self.build_infos.build_file)
|
2015-03-06 17:29:01 +03:00
|
|
|
|
2015-03-18 15:31:03 +03:00
|
|
|
@Slot()
|
|
|
|
def _evaluate(self):
|
|
|
|
# this is called in the working thread, so installation does not
|
|
|
|
# block the ui.
|
2015-12-15 21:54:16 +03:00
|
|
|
|
|
|
|
# download in background, if desired and that last verdict was not
|
|
|
|
# a skip.
|
2020-03-27 23:17:37 +03:00
|
|
|
if self.download_in_background and self.test_runner.verdict != "s":
|
2015-12-15 21:54:16 +03:00
|
|
|
self.index_promise = IndexPromise(
|
2021-03-25 17:38:39 +03:00
|
|
|
self.mid,
|
|
|
|
self.bisection._download_next_builds,
|
|
|
|
args=(self._persist_files,),
|
2015-12-15 21:54:16 +03:00
|
|
|
)
|
|
|
|
# run the build evaluation
|
2015-03-18 15:31:03 +03:00
|
|
|
self.bisection.evaluate(self.build_infos)
|
2015-12-15 21:54:16 +03:00
|
|
|
# wait for the next index in the thread if any
|
|
|
|
if self.index_promise:
|
|
|
|
self.index_promise()
|
|
|
|
# if there was an error, stop the possible downloads
|
|
|
|
if self.test_runner.run_error:
|
|
|
|
self.download_manager.cancel()
|
|
|
|
self.download_manager.wait(raise_if_error=False)
|
2015-12-28 16:17:04 +03:00
|
|
|
if not self.test_runner.run_error:
|
|
|
|
self.step_testing.emit(self.bisection, self.build_infos)
|
2015-03-18 15:31:03 +03:00
|
|
|
|
2015-03-22 14:47:21 +03:00
|
|
|
@Slot(object, str)
|
|
|
|
def _build_dl_finished(self, dl, dest):
|
2015-03-18 15:31:03 +03:00
|
|
|
# here we are not in the working thread, since the connection was
|
|
|
|
# done in the constructor
|
2015-08-24 12:11:15 +03:00
|
|
|
if not dest == self.build_infos.build_file:
|
2015-03-06 17:29:01 +03:00
|
|
|
return
|
2015-03-22 14:47:21 +03:00
|
|
|
if dl is not None and (dl.is_canceled() or dl.error()):
|
2015-03-06 17:29:01 +03:00
|
|
|
# todo handle this
|
|
|
|
return
|
2015-03-18 15:31:03 +03:00
|
|
|
# call this in the thread
|
|
|
|
QTimer.singleShot(0, self._evaluate)
|
2015-03-06 17:29:01 +03:00
|
|
|
|
|
|
|
@Slot()
|
|
|
|
def _evaluate_finished(self):
|
2015-03-18 15:31:03 +03:00
|
|
|
# here we are not in the working thread, since the connection was
|
|
|
|
# done in the constructor
|
2015-12-15 21:54:16 +03:00
|
|
|
if self.index_promise:
|
|
|
|
self.mid = self.index_promise()
|
|
|
|
self.index_promise = None
|
|
|
|
|
2015-03-21 01:50:40 +03:00
|
|
|
self.step_finished.emit(self.bisection, self.test_runner.verdict)
|
2020-03-27 23:17:37 +03:00
|
|
|
result = self.bisection.handle_verdict(self.mid, self.test_runner.verdict)
|
2015-03-06 17:29:01 +03:00
|
|
|
if result != Bisection.RUNNING:
|
2015-03-07 15:16:01 +03:00
|
|
|
self.finished.emit(self.bisection, result)
|
2015-03-06 17:29:01 +03:00
|
|
|
else:
|
2015-03-18 15:31:03 +03:00
|
|
|
# call this in the thread
|
|
|
|
QTimer.singleShot(0, self._bisect_next)
|
2015-03-06 17:29:01 +03:00
|
|
|
|
|
|
|
|
2015-12-06 12:37:35 +03:00
|
|
|
class BisectRunner(AbstractBuildRunner):
|
|
|
|
worker_class = GuiBisector
|
2015-03-07 15:16:01 +03:00
|
|
|
|
2015-12-06 12:37:35 +03:00
|
|
|
def init_worker(self, fetch_config, options):
|
|
|
|
AbstractBuildRunner.init_worker(self, fetch_config, options)
|
2015-03-30 13:15:55 +03:00
|
|
|
|
2015-12-06 12:37:35 +03:00
|
|
|
self.worker.test_runner.evaluate_started.connect(self.evaluate)
|
|
|
|
self.worker.finished.connect(self.bisection_finished)
|
|
|
|
self.worker.handle_merge.connect(self.handle_merge)
|
|
|
|
self.worker.choose_next_build.connect(self.choose_next_build)
|
2020-07-14 17:39:56 +03:00
|
|
|
good, bad = options.get("good"), options.get("bad")
|
2020-03-27 23:17:37 +03:00
|
|
|
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"])
|
2015-03-06 17:29:01 +03:00
|
|
|
else:
|
2020-03-27 23:17:37 +03:00
|
|
|
handler = IntegrationHandler(find_fix=options["find_fix"])
|
2015-03-27 00:55:46 +03:00
|
|
|
|
2015-12-06 12:37:35 +03:00
|
|
|
self.worker._bisect_args = (handler, good, bad)
|
2020-03-27 23:17:37 +03:00
|
|
|
self.worker.download_in_background = self.global_prefs["background_downloads"]
|
|
|
|
if self.global_prefs["approx_policy"]:
|
2016-01-19 02:46:35 +03:00
|
|
|
self.worker.approx_chooser = ApproxPersistChooser(7)
|
2015-12-06 12:37:35 +03:00
|
|
|
return self.worker.bisect
|
2015-03-29 12:54:01 +03:00
|
|
|
|
2016-01-22 13:57:08 +03:00
|
|
|
def stop(self, wait=True):
|
|
|
|
if self.worker:
|
|
|
|
self.worker.should_stop.set()
|
|
|
|
AbstractBuildRunner.stop(self, wait=wait)
|
|
|
|
|
2015-09-07 20:58:20 +03:00
|
|
|
@Slot(str)
|
|
|
|
def evaluate(self, err_message):
|
2015-12-18 12:21:46 +03:00
|
|
|
if self.stopped:
|
|
|
|
self.test_runner.finish(None)
|
|
|
|
return
|
2015-12-28 16:17:04 +03:00
|
|
|
if err_message:
|
2015-09-07 20:58:20 +03:00
|
|
|
QMessageBox.warning(
|
|
|
|
self.mainwindow,
|
|
|
|
"Launcher Error",
|
2020-03-27 23:17:37 +03:00
|
|
|
(
|
|
|
|
"An error occured while starting the process, so the build"
|
|
|
|
" will be skipped. Error message:<br><strong>%s</strong>" % err_message
|
|
|
|
),
|
2015-09-07 20:58:20 +03:00
|
|
|
)
|
2020-03-27 23:17:37 +03:00
|
|
|
self.worker.test_runner.finish("s")
|
2015-12-28 16:17:04 +03:00
|
|
|
|
|
|
|
@Slot(str)
|
|
|
|
def set_verdict(self, verdict):
|
2015-12-06 12:37:35 +03:00
|
|
|
self.worker.test_runner.finish(verdict)
|
2015-03-06 22:18:26 +03:00
|
|
|
|
2015-12-28 16:17:04 +03:00
|
|
|
def open_evaluate_editor(self, open, index):
|
|
|
|
if open:
|
|
|
|
self.mainwindow.ui.report_view.openPersistentEditor(index)
|
|
|
|
else:
|
|
|
|
self.mainwindow.ui.report_view.closePersistentEditor(index)
|
|
|
|
|
2015-09-12 09:04:44 +03:00
|
|
|
@Slot()
|
|
|
|
def choose_next_build(self):
|
2016-02-05 10:15:37 +03:00
|
|
|
dlg = SkipDialog(self.worker.bisection.build_range, self.mainwindow)
|
|
|
|
index = dlg.choose_next_build()
|
|
|
|
if index is None:
|
|
|
|
self.stop()
|
|
|
|
return
|
|
|
|
self.worker._next_build_index = index
|
2015-12-06 12:37:35 +03:00
|
|
|
QTimer.singleShot(0, self.worker._bisect_next)
|
2015-09-12 09:04:44 +03:00
|
|
|
|
2015-03-07 15:16:01 +03:00
|
|
|
@Slot(object, int)
|
|
|
|
def bisection_finished(self, bisection, resultcode):
|
2015-12-28 17:19:08 +03:00
|
|
|
dialog = None
|
2015-03-22 14:55:44 +03:00
|
|
|
if resultcode == Bisection.USER_EXIT:
|
|
|
|
msg = "Bisection stopped."
|
|
|
|
elif resultcode == Bisection.NO_DATA:
|
2015-03-06 22:18:26 +03:00
|
|
|
msg = "Unable to find enough data to bisect."
|
|
|
|
dialog = QMessageBox.warning
|
2015-03-20 15:38:09 +03:00
|
|
|
elif resultcode == Bisection.EXCEPTION:
|
2015-12-06 12:37:35 +03:00
|
|
|
msg = "Error: %s" % self.worker.error[1]
|
2015-03-20 15:38:09 +03:00
|
|
|
dialog = QMessageBox.critical
|
2015-03-06 22:18:26 +03:00
|
|
|
else:
|
2015-12-06 12:37:35 +03:00
|
|
|
fetch_config = self.worker.fetch_config
|
2020-05-05 19:16:20 +03:00
|
|
|
if fetch_config.can_go_integration() and not getattr(bisection, "no_more_merge", False):
|
2015-11-14 16:06:28 +03:00
|
|
|
if isinstance(bisection.handler, NightlyHandler):
|
|
|
|
handler = bisection.handler
|
2020-03-27 23:17:37 +03:00
|
|
|
fetch_config.set_repo(fetch_config.get_nightly_repo(handler.bad_date))
|
2015-12-06 12:37:35 +03:00
|
|
|
QTimer.singleShot(0, self.worker.bisect_further)
|
2015-03-29 02:49:00 +03:00
|
|
|
else:
|
2015-11-14 16:06:28 +03:00
|
|
|
# check merge, try to bisect further
|
2015-12-06 12:37:35 +03:00
|
|
|
QTimer.singleShot(0, self.worker.check_merge)
|
2015-03-20 21:11:15 +03:00
|
|
|
return
|
2015-03-06 22:18:26 +03:00
|
|
|
msg = "The bisection is done."
|
2015-12-28 17:19:08 +03:00
|
|
|
if dialog:
|
|
|
|
dialog(self.mainwindow, "End of the bisection", msg)
|
|
|
|
else:
|
|
|
|
log(msg)
|
2015-03-20 15:38:09 +03:00
|
|
|
self.stop()
|
2015-11-14 16:06:28 +03:00
|
|
|
|
|
|
|
@Slot(object, str, str, str)
|
|
|
|
def handle_merge(self, bisection, branch, good_rev, bad_rev):
|
2015-12-28 17:54:48 +03:00
|
|
|
self.worker.fetch_config.set_repo(str(branch))
|
|
|
|
bisection.handler.good_revision = str(good_rev)
|
|
|
|
bisection.handler.bad_revision = str(bad_rev)
|
|
|
|
QTimer.singleShot(0, self.worker.bisect_further)
|