예제 #1
0
 def __init__(self,
              fetch_config,
              options,
              last_good_revision=None,
              first_bad_revision=None):
     self.last_good_revision = last_good_revision
     self.first_bad_revision = first_bad_revision
     self.fetch_config = fetch_config
     self.options = options
     self.launcher_kwargs = dict(
         addons=options.addons,
         profile=options.profile,
         cmdargs=options.cmdargs,
     )
     self.nightly_data = NightlyBuildData(parse_date(options.good_date),
                                          parse_date(options.bad_date),
                                          fetch_config)
     self._logger = get_default_logger('Bisector')
예제 #2
0
 def __init__(self, fetch_config, options,
              last_good_revision=None, first_bad_revision=None):
     self.last_good_revision = last_good_revision
     self.first_bad_revision = first_bad_revision
     self.fetch_config = fetch_config
     self.options = options
     self.launcher_kwargs = dict(
         addons=options.addons,
         profile=options.profile,
         cmdargs=options.cmdargs,
     )
     self.nightly_data = NightlyBuildData(parse_date(options.good_date),
                                          parse_date(options.bad_date),
                                          fetch_config)
     self._logger = get_default_logger('Bisector')
예제 #3
0
class Bisector(object):

    curr_date = ''
    found_repo = None

    def __init__(self, fetch_config, options,
                 last_good_revision=None, first_bad_revision=None):
        self.last_good_revision = last_good_revision
        self.first_bad_revision = first_bad_revision
        self.fetch_config = fetch_config
        self.options = options
        self.launcher_kwargs = dict(
            addons=options.addons,
            profile=options.profile,
            cmdargs=options.cmdargs,
        )
        self.nightly_data = NightlyBuildData(parse_date(options.good_date),
                                             parse_date(options.bad_date),
                                             fetch_config)
        self._logger = get_default_logger('Bisector')

    def find_regression_chset(self, last_good_revision, first_bad_revision):
        # Uses mozcommitbuilder to bisect on changesets
        # Only needed if they want to bisect, so we'll put the dependency here.
        from mozcommitbuilder import builder
        commit_builder = builder.Builder()

        self._logger.info(" Narrowed changeset range from %s to %s"
                          % (last_good_revision, first_bad_revision))

        self._logger.info("Time to do some bisecting and building!")
        commit_builder.bisect(last_good_revision, first_bad_revision)
        quit()

    def offer_build(self, last_good_revision, first_bad_revision):
        verdict = raw_input("do you want to bisect further by fetching"
                            " the repository and building? (y or n) ")
        if verdict != "y":
            sys.exit()

        if self.fetch_config.app_name == "firefox":
            self.find_regression_chset(last_good_revision, first_bad_revision)
        else:
            sys.exit("Bisection on anything other than firefox is not"
                     " currently supported.")

    def print_range(self, good_date=None, bad_date=None):
        def _print_date_revision(name, revision, date):
            if date and revision:
                self._logger.info("%s revision: %s (%s)"
                                  % (name, revision, date))
            elif revision:
                self._logger.info("%s revision: %s" % (name, revision))
            elif date:
                self._logger.info("%s build: %s" % (name, date))
        _print_date_revision("Last good", self.last_good_revision, good_date)
        _print_date_revision("First bad", self.first_bad_revision, bad_date)

        self._logger.info("Pushlog:\n%s\n"
                          % self.get_pushlog_url(good_date, bad_date))

    def _ensure_metadata(self):
        self._logger.info("Ensuring we have enough metadata to get a pushlog...")
        if not self.last_good_revision:
            url = self.nightly_data[0]['build_txt_url']
            infos = self.nightly_data.info_fetcher.find_build_info_txt(url)
            self.found_repo = infos['repository']
            self.last_good_revision = infos['changeset']

        if not self.first_bad_revision:
            url = self.nightly_data[-1]['build_txt_url']
            infos = self.nightly_data.info_fetcher.find_build_info_txt(url)
            self.found_repo = infos['repository']
            self.first_bad_revision = infos['changeset']

    def _get_verdict(self, build_type, offer_skip=True):
        verdict = ""
        options = ['good', 'g', 'bad', 'b', 'retry', 'r']
        if offer_skip:
            options += ['skip', 's']
        options += ['exit']
        while verdict not in options:
            verdict = raw_input("Was this %s build good, bad, or broken?"
                                " (type 'good', 'bad', 'skip', 'retry', or"
                                " 'exit' and press Enter): " % build_type)

        # shorten verdict to one character for processing...
        if len(verdict) > 1:
            return verdict[0]

        return verdict

    def print_inbound_regression_progress(self, revisions, revisions_left):
        self._logger.info("Narrowed inbound regression window from [%s, %s]"
                          " (%d revisions) to [%s, %s] (%d revisions)"
                          " (~%d steps left)"
                          % (revisions[0]['revision'],
                             revisions[-1]['revision'],
                             len(revisions),
                             revisions_left[0]['revision'],
                             revisions_left[-1]['revision'],
                             len(revisions_left),
                             compute_steps_left(len(revisions_left))))

    def bisect_inbound(self, inbound_revisions=None):
        self.found_repo = get_repo_url(inbound_branch=self.fetch_config.inbound_branch)

        if inbound_revisions is None:
            self._logger.info("Getting inbound builds between %s and %s"
                              % (self.last_good_revision,
                                 self.first_bad_revision))
            # anything within twelve hours is potentially within the range
            # (should be a tighter but some older builds have wrong timestamps,
            # see https://bugzilla.mozilla.org/show_bug.cgi?id=1018907 ...
            # we can change this at some point in the future, after those builds
            # expire)
            build_finder = BuildsFinder(self.fetch_config)
            inbound_revisions = build_finder.get_build_infos(self.last_good_revision,
                                                             self.first_bad_revision,
                                                             range=60*60*12)

        mid = inbound_revisions.mid_point()
        if mid == 0:
            self._logger.info("Oh noes, no (more) inbound revisions :(")
            self.print_range()
            self.offer_build(self.last_good_revision,
                             self.first_bad_revision)
            return
        # hardcode repo to mozilla-central (if we use inbound, we may be
        # missing some revisions that went into the nightlies which we may
        # also be comparing against...)

        self._logger.info("Testing inbound build with timestamp %s,"
                          " revision %s"
                          % (inbound_revisions[mid]['timestamp'],
                             inbound_revisions[mid]['revision']))
        build_url = inbound_revisions[mid]['build_url']
        persist_prefix='%s-%s-' % (inbound_revisions[mid]['timestamp'],
                                   self.fetch_config.inbound_branch)
        launcher = create_launcher(self.fetch_config.app_name,
                                   build_url,
                                   persist=self.options.persist,
                                   persist_prefix=persist_prefix)
        launcher.start()

        verdict = self._get_verdict('inbound', offer_skip=False)
        info = launcher.get_app_info()
        launcher.stop()
        if verdict == 'g':
            self.last_good_revision = info['application_changeset']
        elif verdict == 'b':
            self.first_bad_revision = info['application_changeset']
        elif verdict == 'r':
            # do the same thing over again
            self.bisect_inbound(inbound_revisions=inbound_revisions)
            return
        elif verdict == 'e':
            self._logger.info('Newest known good inbound revision: %s'
                              % self.last_good_revision)
            self._logger.info('Oldest known bad inbound revision: %s'
                              % self.first_bad_revision)

            self._logger.info('To resume, run:')
            self.print_inbound_resume_info(self.last_good_revision,
                                           self.first_bad_revision)
            return

        if len(inbound_revisions) > 1 and verdict in ('g', 'b'):
            if verdict == 'g':
                revisions_left = inbound_revisions[mid:]
            else:
                revisions_left = inbound_revisions[:mid]
            revisions_left.ensure_limits()
            if len(revisions_left) > 0:
                self.print_inbound_regression_progress(inbound_revisions,
                                                       revisions_left)
            self.bisect_inbound(revisions_left)
        else:
            # no more inbounds to be bisect, we must build
            self._logger.info("No more inbounds to bisect")
            self.print_range()
            self.offer_build(self.last_good_revision, self.first_bad_revision)

    def print_nightly_regression_progress(self, good_date, bad_date,
                                          next_good_date, next_bad_date):
        next_days_range = (next_bad_date - next_good_date).days
        self._logger.info("Narrowed nightly regression window from"
                          " [%s, %s] (%d days) to [%s, %s] (%d days)"
                          " (~%d steps left)"
                          % (format_date(good_date),
                             format_date(bad_date),
                             (bad_date - good_date).days,
                             format_date(next_good_date),
                             format_date(next_bad_date),
                             next_days_range,
                             compute_steps_left(next_days_range)))

    def bisect_nightlies(self):
        mid = self.nightly_data.mid_point()

        if len(self.nightly_data) == 0:
            sys.exit("Unable to get valid builds within the given"
                     " range. You should try to launch mozregression"
                     " again with a larger date range.")

        good_date = self.nightly_data.get_date_for_index(0)
        bad_date = self.nightly_data.get_date_for_index(-1)
        mid_date = self.nightly_data.get_date_for_index(mid)

        if mid_date == bad_date or mid_date == good_date:
            self._logger.info("Got as far as we can go bisecting nightlies...")
            self._ensure_metadata()
            self.print_range(good_date, bad_date)
            if self.fetch_config.can_go_inbound():
                self._logger.info("... attempting to bisect inbound builds"
                                  " (starting from previous week, to make"
                                  " sure no inbound revision is missed)")
                infos = {}
                days = 6
                while not 'changeset' in infos:
                    days += 1
                    prev_date = good_date - datetime.timedelta(days=days)
                    infos = self.nightly_data.get_build_infos_for_date(prev_date)
                if days > 7:
                    self._logger.info("At least one build folder was"
                                      " invalid, we have to start from"
                                      " %d days ago." % days)
                self.last_good_revision = infos['changeset']
                self.bisect_inbound()
                return
            else:
                message = ("Can not bissect inbound for application `%s`"
                           % self.fetch_config.app_name)
                if self.fetch_config.is_inbound():
                    # the config is able to bissect inbound but not
                    # for this repo.
                    message += (" because the repo `%s` was specified"
                                % self.options.repo)
                self._logger.info(message + '.')
                sys.exit()

        build_url = self.nightly_data[mid]['build_url']
        persist_prefix = ('%s-%s-'
                          % (mid_date,
                             self.fetch_config.get_nightly_repo(mid_date)))
        self._logger.info("Running nightly for %s" % mid_date)
        launcher = create_launcher(self.fetch_config.app_name,
                                   build_url,
                                   persist=self.options.persist,
                                   persist_prefix=persist_prefix)
        launcher.start(**self.launcher_kwargs)
        info = launcher.get_app_info()
        self.found_repo = info['application_repository']

        self.prev_date = self.curr_date
        self.curr_date = mid_date

        verdict = self._get_verdict('nightly')
        launcher.stop()
        if verdict == 'g':
            self.last_good_revision = info['application_changeset']
            self.print_nightly_regression_progress(good_date, bad_date,
                                                   mid_date, bad_date)
            self.nightly_data = self.nightly_data[mid:]
        elif verdict == 'b':
            self.first_bad_revision = info['application_changeset']
            self.print_nightly_regression_progress(good_date, bad_date,
                                                   good_date, mid_date)
            self.nightly_data = self.nightly_data[:mid]
        elif verdict == 's':
            del self.nightly_data[mid]
        elif verdict == 'e':
            good_date_string = '%04d-%02d-%02d' % (good_date.year,
                                                   good_date.month,
                                                   good_date.day)
            bad_date_string = '%04d-%02d-%02d' % (bad_date.year,
                                                  bad_date.month,
                                                  bad_date.day)
            self._logger.info('Newest known good nightly: %s'
                              % good_date_string)
            self._logger.info('Oldest known bad nightly: %s' % bad_date_string)
            self._logger.info('To resume, run:')
            self.print_nightly_resume_info(good_date_string,
                                           bad_date_string)
            return
        self.bisect_nightlies()

    def get_pushlog_url(self, good_date, bad_date):
        # if we don't have precise revisions, we need to resort to just
        # using handwavey dates
        if not self.last_good_revision or not self.first_bad_revision:
            # pushlogs are typically done with the oldest date first
            if good_date < bad_date:
                start = good_date
                end = bad_date
            else:
                start = bad_date
                end = good_date
            return "%s/pushloghtml?startdate=%s&enddate=%s" % (self.found_repo,
                                                               start, end)

        return "%s/pushloghtml?fromchange=%s&tochange=%s" % (
            self.found_repo, self.last_good_revision, self.first_bad_revision)

    def get_resume_options(self):
        info = ""
        options = self.options
        info += ' --app=%s' % options.app
        if len(options.addons) > 0:
            info += ' --addons=%s' % ",".join(options.addons)
        if options.profile is not None:
            info += ' --profile=%s' % options.profile
        if options.inbound_branch is not None:
            info += ' --inbound-branch=%s' % options.inbound_branch
        info += ' --bits=%s' % options.bits
        if options.persist is not None:
            info += ' --persist=%s' % options.persist
        return info

    def print_nightly_resume_info(self, good_date_string, bad_date_string):
        self._logger.info('mozregression --good=%s --bad=%s%s'
                          % (good_date_string,
                             bad_date_string,
                             self.get_resume_options()))

    def print_inbound_resume_info(self, last_good_revision, first_bad_revision):
        self._logger.info('mozregression --good-rev=%s --bad-rev=%s%s'
                          % (last_good_revision,
                             first_bad_revision,
                             self.get_resume_options()))
예제 #4
0
class Bisector(object):

    curr_date = ''
    found_repo = None

    def __init__(self,
                 fetch_config,
                 options,
                 last_good_revision=None,
                 first_bad_revision=None):
        self.last_good_revision = last_good_revision
        self.first_bad_revision = first_bad_revision
        self.fetch_config = fetch_config
        self.options = options
        self.launcher_kwargs = dict(
            addons=options.addons,
            profile=options.profile,
            cmdargs=options.cmdargs,
        )
        self.nightly_data = NightlyBuildData(parse_date(options.good_date),
                                             parse_date(options.bad_date),
                                             fetch_config)
        self._logger = get_default_logger('Bisector')

    def find_regression_chset(self, last_good_revision, first_bad_revision):
        # Uses mozcommitbuilder to bisect on changesets
        # Only needed if they want to bisect, so we'll put the dependency here.
        from mozcommitbuilder import builder
        commit_builder = builder.Builder()

        self._logger.info(" Narrowed changeset range from %s to %s" %
                          (last_good_revision, first_bad_revision))

        self._logger.info("Time to do some bisecting and building!")
        commit_builder.bisect(last_good_revision, first_bad_revision)
        quit()

    def offer_build(self, last_good_revision, first_bad_revision):
        verdict = raw_input("do you want to bisect further by fetching"
                            " the repository and building? (y or n) ")
        if verdict != "y":
            sys.exit()

        if self.fetch_config.app_name == "firefox":
            self.find_regression_chset(last_good_revision, first_bad_revision)
        else:
            sys.exit("Bisection on anything other than firefox is not"
                     " currently supported.")

    def print_range(self, good_date=None, bad_date=None):
        def _print_date_revision(name, revision, date):
            if date and revision:
                self._logger.info("%s revision: %s (%s)" %
                                  (name, revision, date))
            elif revision:
                self._logger.info("%s revision: %s" % (name, revision))
            elif date:
                self._logger.info("%s build: %s" % (name, date))

        _print_date_revision("Last good", self.last_good_revision, good_date)
        _print_date_revision("First bad", self.first_bad_revision, bad_date)

        self._logger.info("Pushlog:\n%s\n" %
                          self.get_pushlog_url(good_date, bad_date))

    def _ensure_metadata(self):
        self._logger.info(
            "Ensuring we have enough metadata to get a pushlog...")
        if not self.last_good_revision:
            url = self.nightly_data[0]['build_txt_url']
            infos = self.nightly_data.info_fetcher.find_build_info_txt(url)
            self.found_repo = infos['repository']
            self.last_good_revision = infos['changeset']

        if not self.first_bad_revision:
            url = self.nightly_data[-1]['build_txt_url']
            infos = self.nightly_data.info_fetcher.find_build_info_txt(url)
            self.found_repo = infos['repository']
            self.first_bad_revision = infos['changeset']

    def _get_verdict(self, build_type, offer_skip=True):
        verdict = ""
        options = ['good', 'g', 'bad', 'b', 'retry', 'r']
        if offer_skip:
            options += ['skip', 's']
        options += ['exit']
        while verdict not in options:
            verdict = raw_input("Was this %s build good, bad, or broken?"
                                " (type 'good', 'bad', 'skip', 'retry', or"
                                " 'exit' and press Enter): " % build_type)

        # shorten verdict to one character for processing...
        if len(verdict) > 1:
            return verdict[0]

        return verdict

    def print_inbound_regression_progress(self, revisions, revisions_left):
        self._logger.info("Narrowed inbound regression window from [%s, %s]"
                          " (%d revisions) to [%s, %s] (%d revisions)"
                          " (~%d steps left)" %
                          (revisions[0]['revision'], revisions[-1]['revision'],
                           len(revisions), revisions_left[0]['revision'],
                           revisions_left[-1]['revision'], len(revisions_left),
                           compute_steps_left(len(revisions_left))))

    def bisect_inbound(self, inbound_revisions=None):
        self.found_repo = get_repo_url(
            inbound_branch=self.fetch_config.inbound_branch)

        if inbound_revisions is None:
            self._logger.info(
                "Getting inbound builds between %s and %s" %
                (self.last_good_revision, self.first_bad_revision))
            # anything within twelve hours is potentially within the range
            # (should be a tighter but some older builds have wrong timestamps,
            # see https://bugzilla.mozilla.org/show_bug.cgi?id=1018907 ...
            # we can change this at some point in the future, after those builds
            # expire)
            build_finder = BuildsFinder(self.fetch_config)
            inbound_revisions = build_finder.get_build_infos(
                self.last_good_revision,
                self.first_bad_revision,
                range=60 * 60 * 12)

        mid = inbound_revisions.mid_point()
        if mid == 0:
            self._logger.info("Oh noes, no (more) inbound revisions :(")
            self.print_range()
            self.offer_build(self.last_good_revision, self.first_bad_revision)
            return
        # hardcode repo to mozilla-central (if we use inbound, we may be
        # missing some revisions that went into the nightlies which we may
        # also be comparing against...)

        self._logger.info("Testing inbound build with timestamp %s,"
                          " revision %s" %
                          (inbound_revisions[mid]['timestamp'],
                           inbound_revisions[mid]['revision']))
        build_url = inbound_revisions[mid]['build_url']
        persist_prefix = '%s-%s-' % (inbound_revisions[mid]['timestamp'],
                                     self.fetch_config.inbound_branch)
        launcher = create_launcher(self.fetch_config.app_name,
                                   build_url,
                                   persist=self.options.persist,
                                   persist_prefix=persist_prefix)
        launcher.start()

        verdict = self._get_verdict('inbound', offer_skip=False)
        info = launcher.get_app_info()
        launcher.stop()
        if verdict == 'g':
            self.last_good_revision = info['application_changeset']
        elif verdict == 'b':
            self.first_bad_revision = info['application_changeset']
        elif verdict == 'r':
            # do the same thing over again
            self.bisect_inbound(inbound_revisions=inbound_revisions)
            return
        elif verdict == 'e':
            self._logger.info('Newest known good inbound revision: %s' %
                              self.last_good_revision)
            self._logger.info('Oldest known bad inbound revision: %s' %
                              self.first_bad_revision)

            self._logger.info('To resume, run:')
            self.print_inbound_resume_info(self.last_good_revision,
                                           self.first_bad_revision)
            return

        if len(inbound_revisions) > 1 and verdict in ('g', 'b'):
            if verdict == 'g':
                revisions_left = inbound_revisions[mid:]
            else:
                revisions_left = inbound_revisions[:mid]
            revisions_left.ensure_limits()
            if len(revisions_left) > 0:
                self.print_inbound_regression_progress(inbound_revisions,
                                                       revisions_left)
            self.bisect_inbound(revisions_left)
        else:
            # no more inbounds to be bisect, we must build
            self._logger.info("No more inbounds to bisect")
            self.print_range()
            self.offer_build(self.last_good_revision, self.first_bad_revision)

    def print_nightly_regression_progress(self, good_date, bad_date,
                                          next_good_date, next_bad_date):
        next_days_range = (next_bad_date - next_good_date).days
        self._logger.info(
            "Narrowed nightly regression window from"
            " [%s, %s] (%d days) to [%s, %s] (%d days)"
            " (~%d steps left)" %
            (format_date(good_date), format_date(bad_date),
             (bad_date - good_date).days, format_date(next_good_date),
             format_date(next_bad_date), next_days_range,
             compute_steps_left(next_days_range)))

    def bisect_nightlies(self):
        mid = self.nightly_data.mid_point()

        if len(self.nightly_data) == 0:
            sys.exit("Unable to get valid builds within the given"
                     " range. You should try to launch mozregression"
                     " again with a larger date range.")

        good_date = self.nightly_data.get_date_for_index(0)
        bad_date = self.nightly_data.get_date_for_index(-1)
        mid_date = self.nightly_data.get_date_for_index(mid)

        if mid_date == bad_date or mid_date == good_date:
            self._logger.info("Got as far as we can go bisecting nightlies...")
            self._ensure_metadata()
            self.print_range(good_date, bad_date)
            if self.fetch_config.can_go_inbound():
                self._logger.info("... attempting to bisect inbound builds"
                                  " (starting from previous week, to make"
                                  " sure no inbound revision is missed)")
                infos = {}
                days = 6
                while not 'changeset' in infos:
                    days += 1
                    prev_date = good_date - datetime.timedelta(days=days)
                    infos = self.nightly_data.get_build_infos_for_date(
                        prev_date)
                if days > 7:
                    self._logger.info("At least one build folder was"
                                      " invalid, we have to start from"
                                      " %d days ago." % days)
                self.last_good_revision = infos['changeset']
                self.bisect_inbound()
                return
            else:
                message = ("Can not bissect inbound for application `%s`" %
                           self.fetch_config.app_name)
                if self.fetch_config.is_inbound():
                    # the config is able to bissect inbound but not
                    # for this repo.
                    message += (" because the repo `%s` was specified" %
                                self.options.repo)
                self._logger.info(message + '.')
                sys.exit()

        build_url = self.nightly_data[mid]['build_url']
        persist_prefix = (
            '%s-%s-' %
            (mid_date, self.fetch_config.get_nightly_repo(mid_date)))
        self._logger.info("Running nightly for %s" % mid_date)
        launcher = create_launcher(self.fetch_config.app_name,
                                   build_url,
                                   persist=self.options.persist,
                                   persist_prefix=persist_prefix)
        launcher.start(**self.launcher_kwargs)
        info = launcher.get_app_info()
        self.found_repo = info['application_repository']

        self.prev_date = self.curr_date
        self.curr_date = mid_date

        verdict = self._get_verdict('nightly')
        launcher.stop()
        if verdict == 'g':
            self.last_good_revision = info['application_changeset']
            self.print_nightly_regression_progress(good_date, bad_date,
                                                   mid_date, bad_date)
            self.nightly_data = self.nightly_data[mid:]
        elif verdict == 'b':
            self.first_bad_revision = info['application_changeset']
            self.print_nightly_regression_progress(good_date, bad_date,
                                                   good_date, mid_date)
            self.nightly_data = self.nightly_data[:mid]
        elif verdict == 's':
            del self.nightly_data[mid]
        elif verdict == 'e':
            good_date_string = '%04d-%02d-%02d' % (
                good_date.year, good_date.month, good_date.day)
            bad_date_string = '%04d-%02d-%02d' % (bad_date.year,
                                                  bad_date.month, bad_date.day)
            self._logger.info('Newest known good nightly: %s' %
                              good_date_string)
            self._logger.info('Oldest known bad nightly: %s' % bad_date_string)
            self._logger.info('To resume, run:')
            self.print_nightly_resume_info(good_date_string, bad_date_string)
            return
        self.bisect_nightlies()

    def get_pushlog_url(self, good_date, bad_date):
        # if we don't have precise revisions, we need to resort to just
        # using handwavey dates
        if not self.last_good_revision or not self.first_bad_revision:
            # pushlogs are typically done with the oldest date first
            if good_date < bad_date:
                start = good_date
                end = bad_date
            else:
                start = bad_date
                end = good_date
            return "%s/pushloghtml?startdate=%s&enddate=%s" % (self.found_repo,
                                                               start, end)

        return "%s/pushloghtml?fromchange=%s&tochange=%s" % (
            self.found_repo, self.last_good_revision, self.first_bad_revision)

    def get_resume_options(self):
        info = ""
        options = self.options
        info += ' --app=%s' % options.app
        if len(options.addons) > 0:
            info += ' --addons=%s' % ",".join(options.addons)
        if options.profile is not None:
            info += ' --profile=%s' % options.profile
        if options.inbound_branch is not None:
            info += ' --inbound-branch=%s' % options.inbound_branch
        info += ' --bits=%s' % options.bits
        if options.persist is not None:
            info += ' --persist=%s' % options.persist
        return info

    def print_nightly_resume_info(self, good_date_string, bad_date_string):
        self._logger.info(
            'mozregression --good=%s --bad=%s%s' %
            (good_date_string, bad_date_string, self.get_resume_options()))

    def print_inbound_resume_info(self, last_good_revision,
                                  first_bad_revision):
        self._logger.info('mozregression --good-rev=%s --bad-rev=%s%s' %
                          (last_good_revision, first_bad_revision,
                           self.get_resume_options()))