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
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
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
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
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