Merge lp:~vila/udd/608563-requeue-all-of-type into lp:udd

Proposed by Vincent Ladeuil
Status: Merged
Merged at revision: 413
Proposed branch: lp:~vila/udd/608563-requeue-all-of-type
Merge into: lp:udd
Diff against target: 203 lines (+121/-20)
2 files modified
icommon.py (+42/-20)
tests.py (+79/-0)
To merge this branch: bzr merge lp:~vila/udd/608563-requeue-all-of-type
Reviewer Review Type Date Requested Status
James Westby Approve
Review via email: mp+53527@code.launchpad.net

Description of the change

Basically the fix is only:

@@ -686,7 +706,7 @@
                             % self.FAILURES_TABLE).fetchall()
                     for row in rows:
                         this_raw_reason = row[1].encode("ascii", "replace")
- this_sig = self.failure_signature(raw_reason)
+ this_sig = self.failure_signature(this_raw_reason)
                         if this_sig == sig:
                             self._retry(c, row[0], sig, row[2],
                                     priority=priority)

I've added some tangentially related tests and found a couple of minor issues while investigating which I'd like to land too.

To post a comment you must log in.
420. By Vincent Ladeuil

Mark the bug as fixed

Revision history for this message
Vincent Ladeuil (vila) wrote :

Hmm, I was so surprised when I fixed the bug that I forgot to write a proper cover letter, here it is.

requeue_package.py --all-of-type <pkg_name> had a bug where all failed imports were requeued instead of only the ones with the same failure signature. This was due to a typo that makes the signature comparison always use the signature from <pkg_name>.

This patches fixes the typo and makes the signature more precise (since the full traceback is stored in the db this should only make things clearer and doesn't require updating the db itself).

There were also some dusty code in failure_signature that I got rid off (there can't be '\n' in the trace lines since they are produced by splitlines(), annotate and some more following across files revealed that this was needed when the traceback was acquired with readlines()).

Finally there was a bogus test against running_sentinel which *contains* a '\n' that should be ignored when comparing the signature from the db.

421. By Vincent Ladeuil

Explain the traceback crude parsing.

Revision history for this message
James Westby (james-w) wrote :

Excellent, thanks for fixing this.

Thanks,

James

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'icommon.py'
--- icommon.py 2011-03-15 09:00:36 +0000
+++ icommon.py 2011-03-16 10:29:18 +0000
@@ -406,8 +406,8 @@
406 emailed integer default 0,406 emailed integer default 0,
407 constraint isprimary PRIMARY KEY407 constraint isprimary PRIMARY KEY
408 (package))''' % FAILURES_TABLE408 (package))''' % FAILURES_TABLE
409 FAILURES_TABLE_FIND = '''select * from %s where package=?''' % FAILURES_TABLE409 FAILURES_TABLE_FIND = 'select * from %s where package=?' % FAILURES_TABLE
410 FAILURES_TABLE_DELETE = '''delete from %s where package=?''' % FAILURES_TABLE410 FAILURES_TABLE_DELETE = 'delete from %s where package=?' % FAILURES_TABLE
411411
412 OLD_FAILURES_TABLE = "old_failures"412 OLD_FAILURES_TABLE = "old_failures"
413 OLD_FAILURES_TABLE_CREATE = '''create table if not exists %s413 OLD_FAILURES_TABLE_CREATE = '''create table if not exists %s
@@ -633,7 +633,7 @@
633 try:633 try:
634 row = c.execute(self.FAILURES_TABLE_FIND, (package,)).fetchone()634 row = c.execute(self.FAILURES_TABLE_FIND, (package,)).fetchone()
635 if row is None:635 if row is None:
636 return row636 return None
637 return row[1]637 return row[1]
638 finally:638 finally:
639 self.conn.rollback()639 self.conn.rollback()
@@ -641,24 +641,49 @@
641641
642 def failure_signature(self, raw_reason):642 def failure_signature(self, raw_reason):
643 trace = raw_reason.splitlines()643 trace = raw_reason.splitlines()
644 sig = ''
645 if len(trace) == 1:644 if len(trace) == 1:
646 if trace[0] == running_sentinel:645 if trace[0] == running_sentinel[:-1]: # Get rid of the final '\n'
647 return None646 return None
648 # sometimes, Python exceptions do not have file references647 # sometimes, Python exceptions do not have file references
649 m = re.match('(\w+): ', trace[0])648 m = re.match('(\w+): ', trace[0])
650 if m:649 if m:
651 return m.group(1)650 return m.group(1)
652 else:651 else:
653 return trace[0].strip().replace("\n", " ")652 return trace[0].strip()
654 elif len(trace) < 3:653 elif len(trace) < 3:
655 return " ".join(trace).strip().replace("\n", " ")654 return " ".join(trace).strip()
656655
656 # If the failure reason is a traceback (which should always be the
657 # case, the running_sentinel check above taking care of the still
658 # running imports), we build the traceback signature and capture the
659 # exception type
660 sig = ''
661 exc_type = ''
662 exc_type_coming = False
663 file_line_seen = False
657 for l in trace:664 for l in trace:
658 if l.startswith(' File'):665 if l.startswith(' File'):
666 # Keep the method/function name
659 sig += ':' + l.split()[-1]667 sig += ':' + l.split()[-1]
660668 file_line_seen = True
661 return trace[-1].split(':')[0].replace("\n", " ") + sig669 elif file_line_seen:
670 # We've seen the 'File...' line, so we are now seeing the code
671 # line
672 exc_type_coming = True
673 file_line_seen = False
674 elif exc_type_coming:
675 # We've seen the code line so we may find the exception line
676 # itself now.
677
678 # We have no way to know if we are at the last line of the
679 # traceback, but we can't rely on the execption always
680 # displaying a single line either, so the last captured
681 # exception will do.
682 exc_type = l.split(':')[0]
683 exc_type_coming = False
684
685 sig = exc_type + sig
686 return sig
662687
663 def retry(self, package, force=False, priority=False, auto=False,688 def retry(self, package, force=False, priority=False, auto=False,
664 all=False):689 all=False):
@@ -686,7 +711,7 @@
686 % self.FAILURES_TABLE).fetchall()711 % self.FAILURES_TABLE).fetchall()
687 for row in rows:712 for row in rows:
688 this_raw_reason = row[1].encode("ascii", "replace")713 this_raw_reason = row[1].encode("ascii", "replace")
689 this_sig = self.failure_signature(raw_reason)714 this_sig = self.failure_signature(this_raw_reason)
690 if this_sig == sig:715 if this_sig == sig:
691 self._retry(c, row[0], sig, row[2],716 self._retry(c, row[0], sig, row[2],
692 priority=priority)717 priority=priority)
@@ -705,17 +730,14 @@
705 if row[4] > self.MAX_AUTO_RETRY_COUNT:730 if row[4] > self.MAX_AUTO_RETRY_COUNT:
706 print ("Warning: %s has failed %d times in the same way"731 print ("Warning: %s has failed %d times in the same way"
707 % (package, row[4]))732 % (package, row[4]))
708 c.execute('update %s set package=?, reason=?, '733 failure_count = row[4]+1
709 'when_failed=?, last_failed=?, failure_count=? '
710 'where package=?'
711 % self.OLD_FAILURES_TABLE,
712 (package, signature, timestamp, timestamp, row[4]+1,
713 package))
714 else:734 else:
715 c.execute('update %s set package=?, reason=?, when_failed=?, '735 failure_count = 1
716 'last_failed=?, failure_count=? where package=?'736 c.execute('update %s set package=?, reason=?, when_failed=?, '
717 % self.OLD_FAILURES_TABLE,737 'last_failed=?, failure_count=? where package=?'
718 (package, signature, timestamp, timestamp, 1, package))738 % self.OLD_FAILURES_TABLE,
739 (package, signature, timestamp, timestamp, failure_count,
740 package))
719 else:741 else:
720 c.execute('insert into %s values (?, ?, ?, ?, ?)'742 c.execute('insert into %s values (?, ?, ?, ?, ?)'
721 % self.OLD_FAILURES_TABLE,743 % self.OLD_FAILURES_TABLE,
722744
=== modified file 'tests.py'
--- tests.py 2011-02-23 18:23:37 +0000
+++ tests.py 2011-03-16 10:29:18 +0000
@@ -229,6 +229,85 @@
229 self.check_rows(0, 0, 2)229 self.check_rows(0, 0, 2)
230230
231231
232class StatusDatabaseTests(tests.TestCase):
233
234 def setUp(self):
235 super(StatusDatabaseTests, self).setUp()
236 self.db = icommon.StatusDatabase(":memory:")
237
238 def test_no_failures_in_empty_db(self):
239 reasons, package_info = self.db.summarise_failures()
240 self.assertEquals({}, reasons)
241 self.assertEquals([], package_info)
242
243 def test_no_unemailed_failures_in_empty_db(self):
244 self.assertEquals([], self.db.unemailed_failures())
245
246 def assertSignature(self, expected, raw):
247 self.assertEquals(expected, self.db.failure_signature(raw))
248
249 def test_running_signature(self):
250 # Running imports use a special failure signature
251 self.assertSignature(None, icommon.running_sentinel)
252
253 def test_one_line_signature(self):
254 self.assertSignature('AssertionError', 'AssertionError: xx')
255
256 def test_multi_line_signature(self):
257 # We use a real-life example here, the apparent duplication of method
258 # names is due to calls to the base class methods
259 self.assertSignature(
260 'OperationalError:do_one_step:do_one_step'
261 ':collect_terminated_threads'
262 ':collect_terminated_threads:collect'
263 ':finish_job:_set_failure',
264 '''Traceback (most recent call last):
265 File "/srv/package-import.canonical.com/new/scripts/mass_import.py", line 234, in do_one_step
266 super(ImportDriver, self).do_one_step()
267 File "/srv/package-import.canonical.com/new/scripts/icommon.py", line 2091, in do_one_step
268 self.collect_terminated_threads()
269 File "/srv/package-import.canonical.com/new/scripts/mass_import.py", line 253, in collect_terminated_threads
270 super(ImportDriver, self).collect_terminated_threads()
271 File "/srv/package-import.canonical.com/new/scripts/icommon.py", line 2119, in collect_terminated_threads
272 t.collect()
273 File "/srv/package-import.canonical.com/new/scripts/mass_import.py", line 162, in collect
274 unicode_output.encode("utf-8", "replace"))
275 File "/srv/package-import.canonical.com/new/scripts/icommon.py", line 552, in finish_job
276 self._set_failure(c, package, output, now)
277 File "/srv/package-import.canonical.com/new/scripts/icommon.py", line 485, in _set_failure
278 % self.FAILURES_TABLE, (package, reason, now))
279OperationalError: table failures has 4 columns but 3 values were supplied
280''')
281
282 def test_regular_traceback(self):
283 self.assertSignature(
284 'bzrlib.errors.NoSuchTag:lookup_tag',
285 '''Traceback (most recent call last):
286 File "/usr/lib/python2.5/site-packages/bzrlib/tag.py", line 109, in lookup_tag
287 raise errors.NoSuchTag(tag_name)
288bzrlib.errors.NoSuchTag: No such tag: upstream-4.6.2
289''')
290
291 def test_traceback_with_verbose_execption(self):
292 # make sure we get bzrlib.errors.NoFinalPath without being tricked by:
293 # the final empty line, nor the 'file-id:' and 'root trans-id' lines.
294 self.assertSignature(
295 'bzrlib.errors.NoFinalPath:get_path:_determine_path:final_name',
296 ''''Traceback (most recent call last):
297 File "/usr/lib/python2.5/site-packages/bzrlib/transform.py", line 2368, in get_path
298 self._known_paths[trans_id] = self._determine_path(trans_id)
299 File "/usr/lib/python2.5/site-packages/bzrlib/transform.py", line 2358, in _determine_path
300 name = self.transform.final_name(trans_id)
301 File "/usr/lib/python2.5/site-packages/bzrlib/transform.py", line 470, in final_name
302 raise NoFinalPath(trans_id, self)
303bzrlib.errors.NoFinalPath: No final name for trans_id 'new-18'
304file-id: None
305root trans-id: 'new-0'
306
307''')
308
309
310
232class FindEarliestMergeTests(tests.TestCaseWithTransport):311class FindEarliestMergeTests(tests.TestCaseWithTransport):
233312
234 def test_tip_revision(self):313 def test_tip_revision(self):

Subscribers

People subscribed via source and target branches