Example #1
0
 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
     self.test_runner = test_runner
     self.dl_in_background = dl_in_background
     self.history = BisectionHistory()
     self.approx_chooser = approx_chooser
Example #2
0
 def __init__(self, handler, build_range, download_manager, test_runner,
              dl_in_background=True):
     self.handler = handler
     self.build_range = build_range
     self.download_manager = download_manager
     self.test_runner = test_runner
     self.dl_in_background = dl_in_background
     self.history = BisectionHistory()
Example #3
0
class Bisection(object):
    RUNNING = 0
    NO_DATA = 1
    FINISHED = 2
    USER_EXIT = 3

    def __init__(self, handler, build_range, download_manager, test_runner,
                 dl_in_background=True):
        self.handler = handler
        self.build_range = build_range
        self.download_manager = download_manager
        self.test_runner = test_runner
        self.dl_in_background = dl_in_background
        self.history = BisectionHistory()

    def search_mid_point(self):
        self.handler.set_build_range(self.build_range)
        return self._search_mid_point()

    def _search_mid_point(self):
        return self.build_range.mid_point()

    def init_handler(self, mid_point):
        if len(self.build_range) == 0:
            self.handler.no_data()
            return self.NO_DATA

        self.handler.initialize()

        if mid_point == 0:
            self.handler.finished()
            return self.FINISHED
        return self.RUNNING

    def download_build(self, mid_point, allow_bg_download=True):
        """
        Download the build for the given mid_point.

        This call may start the download of next builds in background (if
        dl_in_background evaluates to True). Note that the mid point may
        change in this case.

        Returns a couple (new_mid_point, build_infos) where build_infos
        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)

    def _download_build(self, mid_point, build_infos, allow_bg_download=True):
        self.download_manager.focus_download(build_infos)
        if self.dl_in_background and allow_bg_download:
            mid_point = self._download_next_builds(mid_point)
        return mid_point, build_infos

    def _download_next_builds(self, mid_point):
        # start downloading the next builds.
        # note that we don't have to worry if builds are already
        # downloaded, or if our build infos are the same because
        # this will be handled by the downloadmanager.
        def start_dl(r):
            # first get the next mid point
            # this will trigger some blocking downloads
            # (we need to find the build info)
            m = r.mid_point()
            if len(r) != 0:
                # non-blocking download of the build
                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:])
        # download right next mid point
        start_dl(self.build_range[:mid_point+1])
        # since we called mid_point() on copy of self.build_range instance,
        # the underlying cache may have changed and we need to find the new
        # mid point.
        self.build_range.filter_invalid_builds()
        return self.build_range.index(bdata)

    def evaluate(self, build_infos):
        return self.test_runner.evaluate(build_infos,
                                         allow_back=bool(self.history))

    def handle_verdict(self, mid_point, verdict):
        if verdict == 'g':
            # if build is good and we are looking for a regression, we
            # have to split from
            # [G, ?, ?, G, ?, B]
            # to
            #          [G, ?, B]
            self.history.add(self.build_range, mid_point, verdict)
            if not self.handler.find_fix:
                self.build_range = self.build_range[mid_point:]
            else:
                self.build_range = self.build_range[:mid_point+1]
            self.handler.build_good(mid_point, self.build_range)
        elif verdict == 'b':
            # if build is bad and we are looking for a regression, we
            # have to split from
            # [G, ?, ?, B, ?, B]
            # to
            # [G, ?, ?, B]
            self.history.add(self.build_range, mid_point, verdict)
            if not self.handler.find_fix:
                self.build_range = self.build_range[:mid_point+1]
            else:
                self.build_range = self.build_range[mid_point:]
            self.handler.build_bad(mid_point, self.build_range)
        elif verdict == 'r':
            self.handler.build_retry(mid_point)
        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':
            self.build_range = self.history[-1].build_range
        else:
            # user exit
            self.handler.user_exit(mid_point)
            return self.USER_EXIT
        return self.RUNNING
Example #4
0
class Bisection(object):
    RUNNING = 0
    NO_DATA = 1
    FINISHED = 2
    USER_EXIT = 3

    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
        self.test_runner = test_runner
        self.dl_in_background = dl_in_background
        self.history = BisectionHistory()
        self.approx_chooser = approx_chooser

    def search_mid_point(self, interrupt=None):
        self.handler.set_build_range(self.build_range)
        return self._search_mid_point(interrupt=interrupt)

    def _search_mid_point(self, interrupt=None):
        return self.build_range.mid_point(interrupt=interrupt)

    def init_handler(self, mid_point):
        if len(self.build_range) == 0:
            self.handler.no_data()
            return self.NO_DATA

        self.handler.initialize()

        if mid_point == 0:
            self.handler.finished()
            return self.FINISHED
        return self.RUNNING

    def download_build(self, mid_point, allow_bg_download=True):
        """
        Download the build for the given mid_point.

        This call may start the download of next builds in background (if
        dl_in_background evaluates to True). Note that the mid point may
        change in this case.

        Returns a couple (index_promise, build_infos) where build_infos
        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)

    def _find_approx_build(self, mid_point, build_infos):
        approx_index, persist_files = None, ()
        if self.approx_chooser:
            # try to find an approx build
            persist_files = os.listdir(self.download_manager.destdir)
            # first test if we have the exact file - if we do,
            # 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
                )
        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':
                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))
            build_infos.build_file = fname
        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(
            mid_point, build_infos
        )
        if not found and self.download_manager:
            # else, do the download. Note that nothing will
            # be downloaded if the exact build file is already present.
            self.download_manager.focus_download(build_infos)
        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)

    def _download_next_builds(self, mid_point, persist_files=()):
        # start downloading the next builds.
        # note that we don't have to worry if builds are already
        # downloaded, or if our build infos are the same because
        # this will be handled by the downloadmanager.
        def start_dl(r):
            # first get the next mid point
            # this will trigger some blocking downloads
            # (we need to find the build info)
            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:
                    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:])
        # download right next mid point
        start_dl(self.build_range[:mid_point + 1])
        # since we called mid_point() on copy of self.build_range instance,
        # the underlying cache may have changed and we need to find the new
        # mid point.
        self.build_range.filter_invalid_builds()
        return self.build_range.index(bdata)

    def evaluate(self, build_infos):
        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:
            self.handler.found_repo = build_infos.repo_url
        return verdict

    def ensure_good_and_bad(self):
        good, bad = self.build_range[0], self.build_range[-1]
        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...")
        self.download_manager.focus_download(good)
        if self.dl_in_background:
            self.download_manager.download_in_background(bad)

        def _evaluate(build_info, expected):
            while 1:
                res = self.test_runner.evaluate(build_info)
                if res == expected[0]:
                    return True
                elif res == 's':
                    LOG.info("You can not skip this build.")
                elif res == 'e':
                    return
                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'):
            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')

    def handle_verdict(self, mid_point, verdict):
        if verdict == 'g':
            # if build is good and we are looking for a regression, we
            # have to split from
            # [G, ?, ?, G, ?, B]
            # to
            #          [G, ?, B]
            self.history.add(self.build_range, mid_point, verdict)
            if not self.handler.find_fix:
                self.build_range = self.build_range[mid_point:]
            else:
                self.build_range = self.build_range[:mid_point + 1]
            self.handler.build_good(mid_point, self.build_range)
        elif verdict == 'b':
            # if build is bad and we are looking for a regression, we
            # have to split from
            # [G, ?, ?, B, ?, B]
            # to
            # [G, ?, ?, B]
            self.history.add(self.build_range, mid_point, verdict)
            if not self.handler.find_fix:
                self.build_range = self.build_range[:mid_point + 1]
            else:
                self.build_range = self.build_range[mid_point:]
            self.handler.build_bad(mid_point, self.build_range)
        elif verdict == 'r':
            self.handler.build_retry(mid_point)
        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':
            self.build_range = self.history[-1].build_range
        else:
            # user exit
            self.handler.user_exit(mid_point)
            return self.USER_EXIT
        return self.RUNNING
Example #5
0
class Bisection(object):
    RUNNING = 0
    NO_DATA = 1
    FINISHED = 2
    USER_EXIT = 3

    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
        self.test_runner = test_runner
        self.dl_in_background = dl_in_background
        self.history = BisectionHistory()
        self.approx_chooser = approx_chooser

    def search_mid_point(self, interrupt=None):
        self.handler.set_build_range(self.build_range)
        return self._search_mid_point(interrupt=interrupt)

    def _search_mid_point(self, interrupt=None):
        return self.build_range.mid_point(interrupt=interrupt)

    def init_handler(self, mid_point):
        if len(self.build_range) == 0:
            self.handler.no_data()
            return self.NO_DATA

        self.handler.initialize()

        if mid_point == 0:
            self.handler.finished()
            return self.FINISHED
        return self.RUNNING

    def download_build(self, mid_point, allow_bg_download=True):
        """
        Download the build for the given mid_point.

        This call may start the download of next builds in background (if
        dl_in_background evaluates to True). Note that the mid point may
        change in this case.

        Returns a couple (index_promise, build_infos) where build_infos
        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)

    def _find_approx_build(self, mid_point, build_infos):
        approx_index, persist_files = None, ()
        if self.approx_chooser:
            # try to find an approx build
            persist_files = os.listdir(self.download_manager.destdir)
            # first test if we have the exact file - if we do,
            # 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)
        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':
                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))
            build_infos.build_file = fname
        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(
            mid_point, build_infos)
        if not found and self.download_manager:
            # else, do the download. Note that nothing will
            # be downloaded if the exact build file is already present.
            self.download_manager.focus_download(build_infos)
        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)

    def _download_next_builds(self, mid_point, persist_files=()):
        # start downloading the next builds.
        # note that we don't have to worry if builds are already
        # downloaded, or if our build infos are the same because
        # this will be handled by the downloadmanager.
        def start_dl(r):
            # first get the next mid point
            # this will trigger some blocking downloads
            # (we need to find the build info)
            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:
                    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:])
        # download right next mid point
        start_dl(self.build_range[:mid_point + 1])
        # since we called mid_point() on copy of self.build_range instance,
        # the underlying cache may have changed and we need to find the new
        # mid point.
        self.build_range.filter_invalid_builds()
        return self.build_range.index(bdata)

    def evaluate(self, build_infos):
        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:
            self.handler.found_repo = build_infos.repo_url
        return verdict

    def ensure_good_and_bad(self):
        good, bad = self.build_range[0], self.build_range[-1]
        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...")
        self.download_manager.focus_download(good)
        if self.dl_in_background:
            self.download_manager.download_in_background(bad)

        def _evaluate(build_info, expected):
            while 1:
                res = self.test_runner.evaluate(build_info)
                if res == expected[0]:
                    return True
                elif res == 's':
                    LOG.info("You can not skip this build.")
                elif res == 'e':
                    return
                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'):
            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')

    def handle_verdict(self, mid_point, verdict):
        if verdict == 'g':
            # if build is good and we are looking for a regression, we
            # have to split from
            # [G, ?, ?, G, ?, B]
            # to
            #          [G, ?, B]
            self.history.add(self.build_range, mid_point, verdict)
            if not self.handler.find_fix:
                self.build_range = self.build_range[mid_point:]
            else:
                self.build_range = self.build_range[:mid_point + 1]
            self.handler.build_good(mid_point, self.build_range)
        elif verdict == 'b':
            # if build is bad and we are looking for a regression, we
            # have to split from
            # [G, ?, ?, B, ?, B]
            # to
            # [G, ?, ?, B]
            self.history.add(self.build_range, mid_point, verdict)
            if not self.handler.find_fix:
                self.build_range = self.build_range[:mid_point + 1]
            else:
                self.build_range = self.build_range[mid_point:]
            self.handler.build_bad(mid_point, self.build_range)
        elif verdict == 'r':
            self.handler.build_retry(mid_point)
        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':
            self.build_range = self.history[-1].build_range
        else:
            # user exit
            self.handler.user_exit(mid_point)
            return self.USER_EXIT
        return self.RUNNING
Example #6
0
class Bisection(object):
    RUNNING = 0
    NO_DATA = 1
    FINISHED = 2
    USER_EXIT = 3

    def __init__(self,
                 handler,
                 build_range,
                 download_manager,
                 test_runner,
                 dl_in_background=True):
        self.handler = handler
        self.build_range = build_range
        self.download_manager = download_manager
        self.test_runner = test_runner
        self.dl_in_background = dl_in_background
        self.history = BisectionHistory()

    def search_mid_point(self):
        self.handler.set_build_range(self.build_range)
        return self._search_mid_point()

    def _search_mid_point(self):
        return self.build_range.mid_point()

    def init_handler(self, mid_point):
        if len(self.build_range) == 0:
            self.handler.no_data()
            return self.NO_DATA

        self.handler.initialize()

        if mid_point == 0:
            self.handler.finished()
            return self.FINISHED
        return self.RUNNING

    def download_build(self, mid_point, allow_bg_download=True):
        """
        Download the build for the given mid_point.

        This call may start the download of next builds in background (if
        dl_in_background evaluates to True). Note that the mid point may
        change in this case.

        Returns a couple (index_promise, build_infos) where build_infos
        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)

    def _download_build(self, mid_point, build_infos, allow_bg_download=True):
        self.download_manager.focus_download(build_infos)
        callback = None
        if self.dl_in_background and allow_bg_download:
            callback = self._download_next_builds
        return IndexPromise(mid_point, callback), build_infos

    def _download_next_builds(self, mid_point):
        # start downloading the next builds.
        # note that we don't have to worry if builds are already
        # downloaded, or if our build infos are the same because
        # this will be handled by the downloadmanager.
        def start_dl(r):
            # first get the next mid point
            # this will trigger some blocking downloads
            # (we need to find the build info)
            m = r.mid_point()
            if len(r) != 0:
                # non-blocking download of the build
                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:])
        # download right next mid point
        start_dl(self.build_range[:mid_point + 1])
        # since we called mid_point() on copy of self.build_range instance,
        # the underlying cache may have changed and we need to find the new
        # mid point.
        self.build_range.filter_invalid_builds()
        return self.build_range.index(bdata)

    def evaluate(self, build_infos):
        return self.test_runner.evaluate(build_infos,
                                         allow_back=bool(self.history))

    def ensure_good_and_bad(self):
        good, bad = self.build_range[0], self.build_range[-1]
        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...")
        self.download_manager.focus_download(good)
        if self.dl_in_background:
            self.download_manager.download_in_background(bad)

        def _evaluate(build_info, expected):
            while 1:
                res = self.test_runner.evaluate(build_info)
                if res == expected[0]:
                    return True
                elif res == 's':
                    LOG.info("You can not skip this build.")
                elif res == 'e':
                    return
                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'):
            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')

    def handle_verdict(self, mid_point, verdict):
        if verdict == 'g':
            # if build is good and we are looking for a regression, we
            # have to split from
            # [G, ?, ?, G, ?, B]
            # to
            #          [G, ?, B]
            self.history.add(self.build_range, mid_point, verdict)
            if not self.handler.find_fix:
                self.build_range = self.build_range[mid_point:]
            else:
                self.build_range = self.build_range[:mid_point + 1]
            self.handler.build_good(mid_point, self.build_range)
        elif verdict == 'b':
            # if build is bad and we are looking for a regression, we
            # have to split from
            # [G, ?, ?, B, ?, B]
            # to
            # [G, ?, ?, B]
            self.history.add(self.build_range, mid_point, verdict)
            if not self.handler.find_fix:
                self.build_range = self.build_range[:mid_point + 1]
            else:
                self.build_range = self.build_range[mid_point:]
            self.handler.build_bad(mid_point, self.build_range)
        elif verdict == 'r':
            self.handler.build_retry(mid_point)
        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':
            self.build_range = self.history[-1].build_range
        else:
            # user exit
            self.handler.user_exit(mid_point)
            return self.USER_EXIT
        return self.RUNNING
Example #7
0
class Bisection(object):
    RUNNING = 0
    NO_DATA = 1
    FINISHED = 2
    USER_EXIT = 3

    def __init__(self, handler, build_range, download_manager, test_runner,
                 dl_in_background=True):
        self.handler = handler
        self.build_range = build_range
        self.download_manager = download_manager
        self.test_runner = test_runner
        self.dl_in_background = dl_in_background
        self.history = BisectionHistory()

    def search_mid_point(self):
        self.handler.set_build_range(self.build_range)
        return self._search_mid_point()

    def _search_mid_point(self):
        return self.build_range.mid_point()

    def init_handler(self, mid_point):
        if len(self.build_range) == 0:
            self.handler.no_data()
            return self.NO_DATA

        self.handler.initialize()

        if mid_point == 0:
            self.handler.finished()
            return self.FINISHED
        return self.RUNNING

    def download_build(self, mid_point, allow_bg_download=True):
        """
        Download the build for the given mid_point.

        This call may start the download of next builds in background (if
        dl_in_background evaluates to True). Note that the mid point may
        change in this case.

        Returns a couple (index_promise, build_infos) where build_infos
        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)

    def _download_build(self, mid_point, build_infos, allow_bg_download=True):
        self.download_manager.focus_download(build_infos)
        callback = None
        if self.dl_in_background and allow_bg_download:
            callback = self._download_next_builds
        return IndexPromise(mid_point, callback), build_infos

    def _download_next_builds(self, mid_point):
        # start downloading the next builds.
        # note that we don't have to worry if builds are already
        # downloaded, or if our build infos are the same because
        # this will be handled by the downloadmanager.
        def start_dl(r):
            # first get the next mid point
            # this will trigger some blocking downloads
            # (we need to find the build info)
            m = r.mid_point()
            if len(r) != 0:
                # non-blocking download of the build
                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:])
        # download right next mid point
        start_dl(self.build_range[:mid_point+1])
        # since we called mid_point() on copy of self.build_range instance,
        # the underlying cache may have changed and we need to find the new
        # mid point.
        self.build_range.filter_invalid_builds()
        return self.build_range.index(bdata)

    def evaluate(self, build_infos):
        return self.test_runner.evaluate(build_infos,
                                         allow_back=bool(self.history))

    def ensure_good_and_bad(self):
        good, bad = self.build_range[0], self.build_range[-1]
        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...")
        self.download_manager.focus_download(good)
        if self.dl_in_background:
            self.download_manager.download_in_background(bad)

        def _evaluate(build_info, expected):
            while 1:
                res = self.test_runner.evaluate(build_info)
                if res == expected[0]:
                    return True
                elif res == 's':
                    LOG.info("You can not skip this build.")
                elif res == 'e':
                    return
                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'):
            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')

    def handle_verdict(self, mid_point, verdict):
        if verdict == 'g':
            # if build is good and we are looking for a regression, we
            # have to split from
            # [G, ?, ?, G, ?, B]
            # to
            #          [G, ?, B]
            self.history.add(self.build_range, mid_point, verdict)
            if not self.handler.find_fix:
                self.build_range = self.build_range[mid_point:]
            else:
                self.build_range = self.build_range[:mid_point+1]
            self.handler.build_good(mid_point, self.build_range)
        elif verdict == 'b':
            # if build is bad and we are looking for a regression, we
            # have to split from
            # [G, ?, ?, B, ?, B]
            # to
            # [G, ?, ?, B]
            self.history.add(self.build_range, mid_point, verdict)
            if not self.handler.find_fix:
                self.build_range = self.build_range[:mid_point+1]
            else:
                self.build_range = self.build_range[mid_point:]
            self.handler.build_bad(mid_point, self.build_range)
        elif verdict == 'r':
            self.handler.build_retry(mid_point)
        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':
            self.build_range = self.history[-1].build_range
        else:
            # user exit
            self.handler.user_exit(mid_point)
            return self.USER_EXIT
        return self.RUNNING