Merge lp:~mabac/svammel/log-filed-bugs into lp:svammel

Proposed by Mattias Backman
Status: Merged
Approved by: James Westby
Approved revision: 78
Merged at revision: 78
Proposed branch: lp:~mabac/svammel/log-filed-bugs
Merge into: lp:svammel
Diff against target: 350 lines (+265/-3)
7 files modified
bug_reporting.py (+1/-1)
config.py (+3/-0)
file-failures.py (+18/-2)
logging.py (+90/-0)
tests/__init__.py (+1/-0)
tests/fixtures.py (+2/-0)
tests/test_bug_logging.py (+150/-0)
To merge this branch: bzr merge lp:~mabac/svammel/log-filed-bugs
Reviewer Review Type Date Requested Status
James Westby (community) Approve
Review via email: mp+54319@code.launchpad.net

Description of the change

Hi,

This branch adds the "nice to have" feature of avoiding to re-file bugs for build failures. There is already a check that there is no open bug on the failed software package with the default tags. It might however happen that a bug has been re-targeted or closed since we filed it and by logging the bug reports to file we avoid re-filing them.

For this to work it requires that the user supplies an existing log file with --logfile. If that parameter is not supplied at all, nothing will be logged.

The implementation assumes that having filed a bug against a particular version of a package makes reporting bugs for newer build failures on the same version uninteresting. That is regardless of which rebuild archive the first failure was found in, if the same log file is supplied. Failures on higher versions will always be reported though.

Thanks,

Mattias

To post a comment you must log in.
Revision history for this message
James Westby (james-w) wrote :

Hi,

This looks great.

I'm not sure we should open the log file repeatedly, but it's just
a hunch, so I have no problem with this for now.

Thanks,

James

review: Approve
lp:~mabac/svammel/log-filed-bugs updated
76. By Mattias Backman

Improve tests.

Revision history for this message
Mattias Backman (mabac) wrote :

> Hi,
>
> This looks great.
>
> I'm not sure we should open the log file repeatedly, but it's just
> a hunch, so I have no problem with this for now.

I did think about that and weighed it against opening the file for writing once and keeping it open while iterating over the packages. It felt at bit greedy since it's not certain that there will be anything to write.

I let the script fail if there's a problem writing to the log so perhaps it would be better to grab access to the log file before even trying to file bugs.

Thanks,

Mattias

lp:~mabac/svammel/log-filed-bugs updated
77. By Mattias Backman

Wrap some long lines.

78. By Mattias Backman

Moved opening of log file to Log constructor.

Revision history for this message
Mattias Backman (mabac) wrote :

> > Hi,
> >
> > This looks great.
> >
> > I'm not sure we should open the log file repeatedly, but it's just
> > a hunch, so I have no problem with this for now.
>
> I did think about that and weighed it against opening the file for writing
> once and keeping it open while iterating over the packages. It felt at bit
> greedy since it's not certain that there will be anything to write.
>
> I let the script fail if there's a problem writing to the log so perhaps it
> would be better to grab access to the log file before even trying to file
> bugs.

I thought a bit about this and changed it to just open the file once since it was a small change. In case we want to lock the file during bug filing (which we don't do now) this helps. Other than that this just reduced the number of times to open/close the file.

Thanks,

Mattias

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'bug_reporting.py'
2--- bug_reporting.py 2011-03-14 20:33:45 +0000
3+++ bug_reporting.py 2011-03-23 11:14:27 +0000
4@@ -39,7 +39,7 @@
5 target=target_url,
6 tags=tags)
7 if bug is not None:
8- print 'Filed bug %i: %s' % (bug.id, bug.self_link)
9+ print 'Filed bug %i: %s' % (bug.id, bug.web_link)
10 else:
11 print "Error: unable to file bug '%s'." % title
12
13
14=== modified file 'config.py'
15--- config.py 2011-03-17 08:31:23 +0000
16+++ config.py 2011-03-23 11:14:27 +0000
17@@ -117,6 +117,9 @@
18 help="The series from where to get builds, for instance, 'natty'. " \
19 "Note that the series is only used when looking in the " \
20 "primary archive.")
21+ parser.add_argument(
22+ '--logfile', required=False,
23+ help="Path to file where reported bugs are logged.")
24
25 return parser
26
27
28=== modified file 'file-failures.py'
29--- file-failures.py 2011-03-11 10:19:44 +0000
30+++ file-failures.py 2011-03-23 11:14:27 +0000
31@@ -30,6 +30,10 @@
32 get_file_contents,
33 get_interesting_part,
34 )
35+from logging import (
36+ Log,
37+ LogEntry,
38+)
39
40
41 class LaunchpadError(Exception):
42@@ -59,6 +63,7 @@
43 print 'Error: Could not get build status'
44 raise
45
46+bug_log = Log(args.logfile)
47 for spph in interesting_spph:
48
49 if bug_already_filed_by_url(spph.url, get_launchpad()):
50@@ -66,6 +71,13 @@
51 (spph.package_name, spph.version))
52 continue
53
54+ if bug_log.get_entry(spph.package_name, spph.version,
55+ fail_platform) is not None:
56+ print_message("Bug already reported for %s version %s. Perhaps it " \
57+ "has been closed or retargeted." % \
58+ (spph.package_name, spph.version))
59+ continue
60+
61 log = spph.get_arch(fail_platform)
62 if log is not None:
63 build_log = get_file_contents(log.build_log_url)
64@@ -89,5 +101,9 @@
65 print_message(
66 "Will file bug for %s\n desc: %s\n title: %s\n tags: %s" %
67 (spph.package_name, bug_description[0:220], bug_title, bug_tags))
68- file_bug_by_url(spph.url, bug_description, bug_title,
69- bug_tags, get_launchpad())
70+ bug = file_bug_by_url(spph.url, bug_description, bug_title,
71+ bug_tags, get_launchpad())
72+ if bug is not None and args.logfile is not None:
73+ bug_log.write_entry(
74+ LogEntry(spph.package_name, spph.version, fail_platform,
75+ bug.self_link).to_string())
76
77=== added file 'logging.py'
78--- logging.py 1970-01-01 00:00:00 +0000
79+++ logging.py 2011-03-23 11:14:27 +0000
80@@ -0,0 +1,90 @@
81+###############################################################################
82+# Copyright (c) 2011, 2011 Linaro
83+# All rights reserved. This program and the accompanying materials
84+# are made available under the terms of the Eclipse Public License v1.0
85+# which accompanies this distribution, and is available at
86+# http://www.eclipse.org/legal/epl-v10.html
87+#
88+# Contributors:
89+# Linaro Infrastructure Team - initial implementation
90+###############################################################################
91+from config import (
92+ print_message,
93+ )
94+
95+
96+LOG_ITEM_SEPARATOR = ';'
97+
98+
99+class Log(object):
100+ def __init__(self, log_file_name=None):
101+ self.log_entries = []
102+ self.log_file_name = log_file_name
103+ self.populate_from_file()
104+
105+ self.log_file = None
106+ if self.log_file_name is not None:
107+ try:
108+ self.log_file = open(self.log_file_name, 'a')
109+ except IOError:
110+ print 'Error: Could not open log file for writing.'
111+ raise
112+
113+ def __del__(self):
114+ if self.log_file is not None:
115+ self.log_file.close()
116+
117+ def populate_from_file(self):
118+ if self.log_file_name is not None:
119+ log_file = None
120+ try:
121+ log_file = open(self.log_file_name, 'r')
122+ for line in log_file:
123+ log_items = line.rstrip('\n').split(LOG_ITEM_SEPARATOR)
124+ if len(log_items) != 4:
125+ # Skip invalid log line
126+ continue
127+ log_entry = LogEntry(log_items[0], log_items[1],
128+ log_items[2], log_items[3])
129+ self.add_entry(log_entry)
130+ except IOError:
131+ print_message("Log file '%s' does not exist." % \
132+ self.log_file_name)
133+ finally:
134+ if log_file is not None:
135+ log_file.close()
136+
137+ def add_entry(self, log_entry):
138+ self.log_entries.append(log_entry)
139+
140+ def get_entry(self, package_name, package_version, build_arch):
141+ for entry in self.log_entries:
142+ if entry.matches(package_name, package_version, build_arch):
143+ return entry
144+ return None
145+
146+ def write_entry(self, log_string):
147+ if self.log_file is not None:
148+ try:
149+ self.log_file.write(log_string + '\n')
150+ except IOError:
151+ print 'Error: Could not write log to file.'
152+ self.log_file.close()
153+ raise
154+
155+class LogEntry(object):
156+ def __init__(self, package_name, package_version, build_arch, bugtask_url):
157+ self.package_name = package_name
158+ self.package_version = package_version
159+ self.build_arch = build_arch
160+ self.bugtask_url = bugtask_url
161+
162+ def to_string(self):
163+ return LOG_ITEM_SEPARATOR.join(
164+ [self.package_name, self.package_version, self.build_arch,
165+ self.bugtask_url])
166+
167+ def matches(self, package_name, package_version, build_arch):
168+ return (self.package_name == package_name and
169+ self.package_version == package_version and
170+ self.build_arch == build_arch)
171
172=== modified file 'tests/__init__.py'
173--- tests/__init__.py 2011-03-01 11:50:47 +0000
174+++ tests/__init__.py 2011-03-23 11:14:27 +0000
175@@ -14,6 +14,7 @@
176 def test_suite():
177 module_names = [
178 'tests.test_fail_filer',
179+ 'tests.test_bug_logging',
180 ]
181
182 loader = unittest.TestLoader()
183
184=== modified file 'tests/fixtures.py'
185--- tests/fixtures.py 2011-03-01 11:50:47 +0000
186+++ tests/fixtures.py 2011-03-23 11:14:27 +0000
187@@ -10,6 +10,8 @@
188 ###############################################################################
189 from testtools import TestCase
190 from launchpadlib.launchpad import Launchpad
191+import tempfile
192+import os
193
194
195 class TestCaseWithFixtures(TestCase):
196
197=== added file 'tests/test_bug_logging.py'
198--- tests/test_bug_logging.py 1970-01-01 00:00:00 +0000
199+++ tests/test_bug_logging.py 2011-03-23 11:14:27 +0000
200@@ -0,0 +1,150 @@
201+###############################################################################
202+# Copyright (c) 2011, 2011 Linaro
203+# All rights reserved. This program and the accompanying materials
204+# are made available under the terms of the Eclipse Public License v1.0
205+# which accompanies this distribution, and is available at
206+# http://www.eclipse.org/legal/epl-v10.html
207+#
208+# Contributors:
209+# Linaro Infrastructure Team - initial implementation
210+###############################################################################
211+import __builtin__
212+from testtools import TestCase
213+
214+from tests.fixtures import (
215+ TestCaseWithFixtures,
216+ MockSomethingFixture,
217+ )
218+from logging import (
219+ LOG_ITEM_SEPARATOR,
220+ Log,
221+ LogEntry,
222+ )
223+
224+
225+class TestLogEntry(TestCase):
226+
227+ def setUp(self):
228+ super(TestLogEntry, self).setUp()
229+
230+ def test_log_line_format(self):
231+ package_name = 'package'
232+ package_version = '1.0.1'
233+ build_arch = 'armel'
234+ bugtask_url = 'http://dummy'
235+ log_entry = LogEntry(package_name, package_version, build_arch,
236+ bugtask_url)
237+ self.assertEquals(log_entry.to_string(),
238+ package_name + LOG_ITEM_SEPARATOR + package_version +
239+ LOG_ITEM_SEPARATOR + build_arch +
240+ LOG_ITEM_SEPARATOR + bugtask_url)
241+
242+ def test_log_line_matches(self):
243+ package_name = 'package'
244+ package_version = '1.0.1'
245+ build_arch = 'armel'
246+ bugtask_url = 'http://dummy'
247+ log_entry = LogEntry(package_name, package_version, build_arch,
248+ bugtask_url)
249+ self.assertTrue(log_entry.matches(package_name, package_version,
250+ build_arch))
251+
252+ def test_log_line_does_not_match_other_version(self):
253+ package_name = 'package'
254+ package_version_logged = '1.0.1'
255+ package_version_to_match = '2'
256+ build_arch = 'armel'
257+ bugtask_url = 'http://dummy'
258+ log_entry = LogEntry(package_name, package_version_logged, build_arch,
259+ bugtask_url)
260+ self.assertFalse(
261+ log_entry.matches(package_name, package_version_to_match,
262+ build_arch))
263+
264+
265+class TestLog(TestCaseWithFixtures):
266+
267+ def setUp(self):
268+ super(TestLog, self).setUp()
269+
270+ def test_add_entry_does_so(self):
271+ package_name = 'package'
272+ package_version = '1.0.1'
273+ build_arch = 'armel'
274+ bugtask_url = 'http://dummy'
275+ log_entry = LogEntry(package_name, package_version, build_arch,
276+ bugtask_url)
277+ log = Log()
278+ log.add_entry(log_entry)
279+ self.assertIn(log_entry, log.log_entries)
280+
281+ def test_get_nonexistent_logs_fails(self):
282+ log = Log()
283+ self.assertEquals(None, log.get_entry('package', 'version', 'arch'))
284+
285+ def test_stored_log_is_found(self):
286+ package_name = 'package'
287+ package_version = '1.0.1'
288+ build_arch = 'armel'
289+ bugtask_url = 'http://dummy'
290+ log_entry = LogEntry(package_name, package_version, build_arch,
291+ bugtask_url)
292+ log = Log()
293+ log.add_entry(log_entry)
294+ self.assertEquals(
295+ log_entry, log.get_entry(package_name, package_version,
296+ build_arch))
297+
298+ def test_no_entries_in_log_without_file(self):
299+ log = Log(None)
300+ self.assertEquals([], log.log_entries)
301+
302+ def test_no_entries_in_log_with_empty_file(self):
303+ log_file = self.createTempFileAsFixture()
304+ log = Log(log_file)
305+ self.assertEquals([], log.log_entries)
306+
307+ def test_read_log_entry_from_file(self):
308+ package_name = 'packagename'
309+ package_version = '1.1.2'
310+ build_arch = 'armel'
311+ bugtask_url = 'http://dummy'
312+ log_entry = LogEntry(package_name, package_version, build_arch,
313+ bugtask_url)
314+ log_file_name = self.createTempFileAsFixture()
315+ with open(log_file_name, 'w') as log_file_write:
316+ log_file_write.write(log_entry.to_string())
317+
318+ log = Log(log_file_name)
319+ log_entry_read = log.get_entry(package_name, package_version,
320+ build_arch)
321+ self.assertTrue(len(log.log_entries) == 1)
322+ self.assertTrue(log_entry_read.matches(
323+ package_name, package_version, build_arch))
324+
325+ def test_write_log_entry_to_file(self):
326+ package_name = 'packagename'
327+ package_version = '1.2.3-1'
328+ build_arch = 'armel'
329+ bugtask_url = 'http://dummy'
330+ log_entry = LogEntry(package_name, package_version, build_arch,
331+ bugtask_url)
332+ log_file_name = self.createTempFileAsFixture()
333+ log_write = Log(log_file_name)
334+ log_write.write_entry(log_entry.to_string())
335+ del log_write
336+ with open(log_file_name, 'r') as log_file:
337+ log_line = log_file.readline().rstrip('\n')
338+ self.assertEquals(log_entry.to_string(), log_line)
339+
340+ def test_failed_open_log_for_writing_raises(self):
341+ def mock_open(self, string):
342+ raise IOError("Test error.")
343+
344+ def create_log(file_name):
345+ log_write = Log(file_name)
346+
347+ self.useFixture(MockSomethingFixture(
348+ __builtin__, 'open', mock_open))
349+ log_file_name = 'logfile'
350+ self.assertRaises(IOError, create_log, log_file_name)

Subscribers

People subscribed via source and target branches