Merge lp:~naesten/bzr-bisect/683822-bisect-start-range-argument into lp:bzr-bisect

Proposed by Samuel Bronson
Status: Needs review
Proposed branch: lp:~naesten/bzr-bisect/683822-bisect-start-range-argument
Merge into: lp:bzr-bisect
Diff against target: 252 lines (+99/-18)
2 files modified
cmds.py (+62/-18)
tests.py (+37/-0)
To merge this branch: bzr merge lp:~naesten/bzr-bisect/683822-bisect-start-range-argument
Reviewer Review Type Date Requested Status
Bazaar Developers Pending
Review via email: mp+124565@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Samuel Bronson (naesten) wrote :

Unfortunately, it turns out that this doesn't match the convention used by "run" for the common case of finding when a problem was introduced :-(.

87. By Samuel Bronson

Roll back my test changes; later, I'll add some tests just for this.

88. By Samuel Bronson

Merge lp:~jeff-licquia/bzr-bisect/yesno

This gives "yes" and "no" equal standing again.

89. By Samuel Bronson

Reverse polarity of "start -r" and "run -r".

Now it matches the "run" commands's return-value handling.

90. By Samuel Bronson

Add a test for "bzr bisect run -r".

91. By Samuel Bronson

Add some debug output (mostly log-only).

Revision history for this message
Samuel Bronson (naesten) wrote :

> Unfortunately, it turns out that this doesn't match the convention used by
> "run" for the common case of finding when a problem was introduced :-(.

Okay, as of revision 89, I've matched the polarities here; now, it's the bulk of the tests that don't match ;-).

Unmerged revisions

91. By Samuel Bronson

Add some debug output (mostly log-only).

90. By Samuel Bronson

Add a test for "bzr bisect run -r".

89. By Samuel Bronson

Reverse polarity of "start -r" and "run -r".

Now it matches the "run" commands's return-value handling.

88. By Samuel Bronson

Merge lp:~jeff-licquia/bzr-bisect/yesno

This gives "yes" and "no" equal standing again.

87. By Samuel Bronson

Roll back my test changes; later, I'll add some tests just for this.

86. By Samuel Bronson

Implement the "-r revA..revB" syntax for "start" and "run" suggested in bug 683822.

Quite likely this is buggy, and none of the scenareos in the test suite (some of which I converted to this syntax) are complicated enough to show this: they all seem to start by setting the bisect range to cover all revisions on the branch.

More tests would be quite useful!

85. By Samuel Bronson

Cleanup option handling preperatory to implementing bug 683822.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cmds.py'
--- cmds.py 2012-07-19 22:22:27 +0000
+++ cmds.py 2012-09-21 21:50:23 +0000
@@ -23,7 +23,7 @@
23from bzrlib.commands import Command23from bzrlib.commands import Command
24from bzrlib.errors import BzrCommandError24from bzrlib.errors import BzrCommandError
25from bzrlib.option import Option25from bzrlib.option import Option
26from bzrlib.trace import note26from bzrlib.trace import note, mutter
2727
28bisect_info_path = ".bzr/bisect"28bisect_info_path = ".bzr/bisect"
29bisect_rev_path = ".bzr/bisect_revid"29bisect_rev_path = ".bzr/bisect_revid"
@@ -75,6 +75,7 @@
75 """Write the current revision's log entry to a file."""75 """Write the current revision's log entry to a file."""
76 rev = self._bzrbranch.repository.get_revision(self._revid)76 rev = self._bzrbranch.repository.get_revision(self._revid)
77 revno = ".".join([str(x) for x in self.get_current_revno()])77 revno = ".".join([str(x) for x in self.get_current_revno()])
78 mutter("On revision %s (%s)", revno, rev.revision_id)
78 out.write("On revision %s (%s):\n%s\n" % (revno, rev.revision_id,79 out.write("On revision %s (%s):\n%s\n" % (revno, rev.revision_id,
79 rev.message))80 rev.message))
8081
@@ -111,6 +112,7 @@
111 self._low_revid = None112 self._low_revid = None
112 self._middle_revid = None113 self._middle_revid = None
113 self._filename = filename114 self._filename = filename
115 self._high_indicator = None
114 self.load()116 self.load()
115117
116 def _open_for_read(self):118 def _open_for_read(self):
@@ -160,10 +162,12 @@
160 continue162 continue
161 if len(matches) > 1:163 if len(matches) > 1:
162 raise RuntimeError("revision %s duplicated" % revision)164 raise RuntimeError("revision %s duplicated" % revision)
163 if matches[0] == "yes":165 if not self._high_indicator:
166 self._high_indicator = matches[0]
167 if matches[0] == self._high_indicator:
164 high_revid = revision168 high_revid = revision
165 between_revs = []169 between_revs = []
166 elif matches[0] == "no":170 else:
167 low_revid = revision171 low_revid = revision
168 del between_revs[0]172 del between_revs[0]
169 break173 break
@@ -201,6 +205,7 @@
201 def _set_status(self, revid, status):205 def _set_status(self, revid, status):
202 """Set the bisect status for the given revid."""206 """Set the bisect status for the given revid."""
203 if not self.is_done():207 if not self.is_done():
208 note("Marking %s as %s", revid, status)
204 if status != "done" and revid in [x[0] for x in self._items209 if status != "done" and revid in [x[0] for x in self._items
205 if x[1] in ['yes', 'no']]:210 if x[1] in ['yes', 'no']]:
206 raise RuntimeError("attempting to add revid %s twice" % revid)211 raise RuntimeError("attempting to add revid %s twice" % revid)
@@ -253,6 +258,7 @@
253258
254 def bisect(self, outf):259 def bisect(self, outf):
255 """Using the current revision's status, do a bisection."""260 """Using the current revision's status, do a bisection."""
261 mutter("Bisecting")
256 self._find_range_and_middle()262 self._find_range_and_middle()
257 # If we've found the "final" revision, check for a263 # If we've found the "final" revision, check for a
258 # merge point.264 # merge point.
@@ -297,6 +303,10 @@
297 The specified revision (or the current revision, if not given)303 The specified revision (or the current revision, if not given)
298 does not have the characteristic we're looking for,304 does not have the characteristic we're looking for,
299305
306 bzr bisect start -r revA..revB
307 Like: "bzr bisect start; bzr bisect yes revA; bzr bisect no revB".
308 If without revA, skip the "yes"; without revB, skip the "no".
309
300 bzr bisect move -r rev310 bzr bisect move -r rev
301 Switch to a different revision manually. Use if the bisect311 Switch to a different revision manually. Use if the bisect
302 algorithm chooses a revision that is not suitable. Try to312 algorithm chooses a revision that is not suitable. Try to
@@ -313,12 +323,14 @@
313 Replay a previously-saved bisect log, forgetting any bisection323 Replay a previously-saved bisect log, forgetting any bisection
314 that might be in progress.324 that might be in progress.
315325
316 bzr bisect run <script>326 bzr bisect run [-r revA:revB] <script>
317 Bisect automatically using <script> to determine 'yes' or 'no'.327 Bisect automatically using <script> to determine 'yes' or 'no'.
318 <script> should exit with:328 <script> should exit with:
319 0 for yes329 0 for yes
320 125 for unknown (like build failed so we could not test)330 125 for unknown (like build failed so we could not test)
321 anything else for no331 anything else for no
332
333 "-r revA:revB" is handled as for "start".
322 """334 """
323335
324 takes_args = ['subcommand', 'args*']336 takes_args = ['subcommand', 'args*']
@@ -353,23 +365,47 @@
353 """Handle the bisect command."""365 """Handle the bisect command."""
354366
355 log_fn = None367 log_fn = None
368 # Handle args_list
369 if subcommand == 'replay':
370 if len(args_list) > 1:
371 raise BzrCommandError(
372 "The 'bisect replay' command only accepts one argument.")
373 elif args_list:
374 log_fn = args_list[0]
375 pass
376 elif subcommand == 'run':
377 if len(args_list) != 1:
378 raise BzrCommandError(
379 "The 'bisect run' command requires a single argument.")
380 run_script = args_list[0]
381 elif args_list:
382 raise BzrCommandError(
383 "The 'bisect %s' command doesn't take any arguments." %
384 (subcommand))
385
386 # Handle revision
356 if subcommand in ('yes', 'no', 'move') and revision:387 if subcommand in ('yes', 'no', 'move') and revision:
357 pass388 if len(revision) > 1:
358 elif subcommand in ('replay', ) and args_list and len(args_list) == 1:389 raise BzrCommandError(
359 log_fn = args_list[0]390 "The 'bisect %s' command doesn't accept revision ranges." %
360 elif subcommand in ('move', ) and not revision:391 (subcommand))
392 elif subcommand == 'move' and not revision:
361 raise BzrCommandError(393 raise BzrCommandError(
362 "The 'bisect move' command requires a revision.")394 "The 'bisect move' command requires a revision.")
363 elif subcommand in ('run', ):395 elif subcommand in ('start', 'run') and revision:
364 run_script = args_list[0]396 if len(revision) != 2:
365 elif args_list or revision:397 raise BzrCommandError(
398 "The 'bisect %s' command only accepts revision ranges." %
399 (subcommand))
400 elif revision:
366 raise BzrCommandError(401 raise BzrCommandError(
367 "Improper arguments to bisect " + subcommand)402 "The 'bisect %s' command doesn't accept revspecs." %
403 (subcommand))
368404
369 # Dispatch.405 # Dispatch.
370406
371 if subcommand == "start":407 if subcommand == "start":
372 self.start()408 self.start(revision)
373 elif subcommand == "yes":409 elif subcommand == "yes":
374 self.yes(revision)410 self.yes(revision)
375 elif subcommand == "no":411 elif subcommand == "no":
@@ -383,7 +419,7 @@
383 elif subcommand == "replay":419 elif subcommand == "replay":
384 self.replay(log_fn)420 self.replay(log_fn)
385 elif subcommand == "run":421 elif subcommand == "run":
386 self.run_bisect(run_script)422 self.run_bisect(run_script, revision)
387 else:423 else:
388 raise BzrCommandError(424 raise BzrCommandError(
389 "Unknown bisect command: " + subcommand)425 "Unknown bisect command: " + subcommand)
@@ -394,7 +430,7 @@
394 BisectCurrent().reset()430 BisectCurrent().reset()
395 os.unlink(bisect_info_path)431 os.unlink(bisect_info_path)
396432
397 def start(self):433 def start(self, revspec):
398 """Reset the bisect state, then prepare for a new bisection."""434 """Reset the bisect state, then prepare for a new bisection."""
399 if os.path.exists(bisect_info_path):435 if os.path.exists(bisect_info_path):
400 BisectCurrent().reset()436 BisectCurrent().reset()
@@ -402,6 +438,13 @@
402438
403 bisect_log = BisectLog()439 bisect_log = BisectLog()
404 bisect_log.set_current("start")440 bisect_log.set_current("start")
441
442 if revspec:
443 revA, revB = revspec
444 if revA: bisect_log.set_status_from_revspec([revA], 'yes')
445 if revB: bisect_log.set_status_from_revspec([revB], 'no')
446 bisect_log.bisect(self.outf)
447
405 bisect_log.save()448 bisect_log.save()
406449
407 def yes(self, revspec):450 def yes(self, revspec):
@@ -437,15 +480,16 @@
437480
438 bisect_log.bisect(self.outf)481 bisect_log.bisect(self.outf)
439482
440 def run_bisect(self, script):483 def run_bisect(self, script, revspec):
441 import subprocess484 import subprocess
442 note("Starting bisect.")485 note("Starting bisect.")
443 self.start()486 self.start(revspec)
444 while True:487 while True:
445 try:488 try:
446 process = subprocess.Popen(script, shell=True)489 process = subprocess.Popen(script, shell=True)
447 process.wait()490 process.wait()
448 retcode = process.returncode491 retcode = process.returncode
492 mutter("Script returned %d", retcode)
449 if retcode == 0:493 if retcode == 0:
450 done = self._set_state(None, 'yes')494 done = self._set_state(None, 'yes')
451 elif retcode == 125:495 elif retcode == 125:
@@ -455,4 +499,4 @@
455 if done:499 if done:
456 break500 break
457 except RuntimeError:501 except RuntimeError:
458 break502 raise
459503
=== modified file 'tests.py'
--- tests.py 2011-11-24 16:04:11 +0000
+++ tests.py 2012-09-21 21:50:23 +0000
@@ -253,6 +253,38 @@
253 self.run_bzr(['bisect', 'no'])253 self.run_bzr(['bisect', 'no'])
254 self.assertRevno(3)254 self.assertRevno(3)
255255
256 def testWorkflowReverse(self):
257 """Run through a basic usage scenario, except with senses of
258 'yes' and 'no' reversed. The plugin should pick up on that
259 and adjust."""
260
261 # Start up the bisection. When the two ends are set, we should
262 # end up in the middle.
263
264 self.run_bzr(['bisect', 'start'])
265 self.run_bzr(['bisect', 'no'])
266 self.run_bzr(['bisect', 'yes', '-r', '1'])
267 self.assertRevno(3)
268
269 # Mark feature as absent in the middle. Should move us
270 # halfway back between the current middle and the start.
271
272 self.run_bzr(['bisect', 'no'])
273 self.assertRevno(2)
274
275 # Mark feature as present. Since this is only one
276 # rev back from the lowest marked revision without the feature,
277 # the process should end, with the current rev set to the
278 # rev following.
279
280 self.run_bzr(['bisect', 'yes'])
281 self.assertRevno(3)
282
283 # Run again. Since we're done, this should do nothing.
284
285 self.run_bzr(['bisect', 'yes'])
286 self.assertRevno(3)
287
256 def testWorkflowSubtree(self):288 def testWorkflowSubtree(self):
257 """Run through a usage scenario where the offending change289 """Run through a usage scenario where the offending change
258 is in a subtree."""290 is in a subtree."""
@@ -414,3 +446,8 @@
414 raise KnownFailure\446 raise KnownFailure\
415 ("bisect does not drill down into merge commits: "447 ("bisect does not drill down into merge commits: "
416 "https://bugs.launchpad.net/bzr-bisect/+bug/539937")448 "https://bugs.launchpad.net/bzr-bisect/+bug/539937")
449
450 def testRunRange(self):
451 out, err = self.run_bzr(['bisect', 'run', '-r', '2..5',
452 '! grep -q one test_file_append'])
453 self.assertRevno(3)

Subscribers

People subscribed via source and target branches

to all changes: