bisect-kit: bisector 'run --force'

This CL changed the behavior of bisector 'run' subcommand slightly.

Original behavior:
   If bisect failed (verify failed or skip too many times), the result
   is not saved and terminated.

   So users can resume by just calling 'run' directly. However, there is
   a problem when integrated with diagnose_cros_autotest.py. Assume the
   culprit is inside chromeos tree, so android bisector and chrome
   bisector would fail as expected. When diagnose_cros_autotest.py
   resume, it will waste time to run android and chrome bisector again.

New behavior:
   Bisect result will always be saved even the result is failure. If
   users want to resume failed session, they can do 'run --force', which
   switch and eval at least one more time even if last run failed.

BUG=None
TEST=When bisector failed, run again to resume.

Change-Id: I91036c8e22b4c8c037d0e0fb702c709762b28777
Reviewed-on: https://chromium-review.googlesource.com/1304233
Commit-Ready: Kuang-che Wu <kcwu@chromium.org>
Tested-by: Kuang-che Wu <kcwu@chromium.org>
Reviewed-by: Chung-yih Wang <cywang@chromium.org>
diff --git a/bisect_kit/cli.py b/bisect_kit/cli.py
index ef399ae..4a369c1 100644
--- a/bisect_kit/cli.py
+++ b/bisect_kit/cli.py
@@ -396,7 +396,7 @@
 
     return 'eval', status, values
 
-  def _next_idx_iter(self, opts):
+  def _next_idx_iter(self, opts, force):
     if opts.revs:
       for rev in opts.revs:
         idx = self.states.rev2idx(rev)
@@ -405,11 +405,12 @@
         if opts.once:
           break
     else:
-      while not self.strategy.is_done():
+      while force or not self.strategy.is_done():
         idx = self.strategy.next_idx()
         rev = self.states.idx2rev(idx)
         logger.info('try idx=%d rev=%s', idx, rev)
         yield idx, rev
+        force = False
         if opts.once:
           break
 
@@ -430,22 +431,23 @@
     self.strategy.rebuild()
 
     prev_rev = None
-    for idx, rev in self._next_idx_iter(opts):
-      # Bail out if bisection range is unlikely true in order to prevent
-      # wasting time. This is necessary because some configurations (say,
-      # confidence) may be changed before cmd_run() and thus the bisection
-      # range becomes not acceptable.
-      self.strategy.check_verification_range()
+    force = opts.force
+    for idx, rev in self._next_idx_iter(opts, force):
+      if not force:
+        # Bail out if bisection range is unlikely true in order to prevent
+        # wasting time. This is necessary because some configurations (say,
+        # confidence) may be changed before cmd_run() and thus the bisection
+        # range becomes not acceptable.
+        self.strategy.check_verification_range()
 
       step, status, values = self._switch_and_eval(rev, prev_rev=prev_rev)
       logger.info('rev=%s => status %s', rev, status)
+      force = False
 
       self._add_status(rev, status, values=values)
-      # Bail out if bisection range is unlikely true. Don't save.
-      # The last failing results are likely something wrong in bisection setup,
-      # bisector, and/or evaluator. The benefits of not saving the last failing
-      # results, are that users can just resume bisector directly without needs
-      # of erasing bad results after they fixed the problems.
+      self.states.save()
+
+      # Bail out if bisection range is unlikely true.
       self.strategy.check_verification_range()
 
       if status == 'skip':
@@ -455,8 +457,6 @@
             current_state['old'] + current_state['new'] + 1) * 5:
           raise core.ExecutionFatalError('too much "skip" for rev=%r' % rev)
 
-      self.states.save()
-
       self.strategy.show_summary()
 
       if step == 'switch' and status == 'skip':
@@ -731,6 +731,10 @@
     parser_run.add_argument(
         '-1', '--once', action='store_true', help='Only run one step')
     parser_run.add_argument(
+        '--force',
+        action='store_true',
+        help="Run at least once even it's already done")
+    parser_run.add_argument(
         'revs',
         nargs='*',
         type=self.domain_cls.intra_revtype,