Merge lp:~inspirated/launchpad/implement-Bug-findAttachments into lp:launchpad

Proposed by Kamran Riaz Khan
Status: Superseded
Proposed branch: lp:~inspirated/launchpad/implement-Bug-findAttachments
Merge into: lp:launchpad
Diff against target: 179 lines (+132/-0)
6 files modified
lib/lp/bugs/configure.zcml (+1/-0)
lib/lp/bugs/interfaces/bug.py (+8/-0)
lib/lp/bugs/model/bug.py (+18/-0)
lib/lp/bugs/tests/test_bug_find_attachment.py (+68/-0)
lib/lp/bugs/tests/testfiles/sample-attachment-alsa.diff (+15/-0)
lib/lp/bugs/tests/testfiles/sample-attachment-pidgin.diff (+22/-0)
To merge this branch: bzr merge lp:~inspirated/launchpad/implement-Bug-findAttachments
Reviewer Review Type Date Requested Status
Graham Binns (community) code Disapprove
Review via email: mp+27569@code.launchpad.net

This proposal has been superseded by a proposal from 2010-06-16.

Commit message

Implemented Bug.findAttachments()

Description of the change

Summary:
As part of my Google Summer of Code project, I had to implement attachment searching functionality in Arsenal. The end-product would allow user to specify a search string which would be searched in all the bug attachments for a project.

Doing this efficiently required two modifications in Launchpad:
    * Exposing a bug collection for a particular product
    * Implement a attachment search method for a particular bug (this branch)

Proposed fix:
Export a read operation findAttachments in IBug which returns a collection of IBugAttachment.

Pre-implementation Notes:
    * The search operation is essentially a text-only grep.

Implementation Details:
    * Zope configuration had to be updated to export the method
    * Multi-line searches break the GET request with a “HTTP/1.1 400 Bad Request” response
    * The attachments are all opened as regular file objects and then searched for the target pattern.

Tests:
bin/test -vv -m lp.bugs.tests.test_bug_find_attachment

Demo and Q/A:
Open any Launchpad bug in a browser:
 https://launchpad.dev/bugs/15
Create an attachment and upload any text file containing the string ‘char buf’.
Create a Launchpad instance:
 >>> from launchpadlib.launchpad import Launchpad
 >>> launchpad = Launchpad.login_with('just testing', 'https://api.launchpad.dev/beta/')
 The authorization page:
    (https://launchpad.dev/+authorize-token?oauth_token=ppd2bRZDnN6VX94lRqrq)
 should be opening in your browser. After you have authorized
 this program to access Launchpad on your behalf you should come
 back here and press <Enter> to finish the authentication process.
Load the bug:
 >>> bug = launchpad.bugs[15]
Search for the attachment containing the string ‘char buf’:
 >>> results = bug.findAttachments(text=u'char buf')
 >>> for attachment in results:
 ... print attachment.title
 ...
 Buffer Overflow Intro.txt

lint:
The changes are lint clean.

To post a comment you must log in.
Revision history for this message
Graham Binns (gmb) wrote :

After a lengthy discussion in #launchpad-reviews we've agreed that there should be a different solution to this problem.

review: Disapprove (code)
11013. By Kamran Riaz Khan

Fixed unicode searches in Bug.findAttachments()

11014. By Kamran Riaz Khan

Changes in Bug.findAttachments():
 * Limited the size of attachments that can be searched to 8 MB
 * Modified searching to go-through attachments line-wise

11015. By Kamran Riaz Khan

Fixed Bug.findAttachments() to use preferred encoding instead of UTF-8

11016. By Kamran Riaz Khan

Added support for multi-line searches in Bug.findAttachments() using
is_encoded parameter

11017. By Kamran Riaz Khan

Changes in Bug.findAttachments():
 * Use Boyer-Moore-Horspool algorithm to search attachments in chunks
 * Fixed unicode and multiline unittests

11018. By Kamran Riaz Khan

Use encode('unicode_escape') instead of urllib.quote() for multiline
searches

11019. By Kamran Riaz Khan

Minor changes to improve code readability.

11020. By Kamran Riaz Khan

Removed urllib2 usage in Bug.findAttachments() by opening attachments
directly.

11021. By Kamran Riaz Khan

Minor changes to improve unittest readability.

Unmerged revisions

11021. By Kamran Riaz Khan

Minor changes to improve unittest readability.

11020. By Kamran Riaz Khan

Removed urllib2 usage in Bug.findAttachments() by opening attachments
directly.

11019. By Kamran Riaz Khan

Minor changes to improve code readability.

11018. By Kamran Riaz Khan

Use encode('unicode_escape') instead of urllib.quote() for multiline
searches

11017. By Kamran Riaz Khan

Changes in Bug.findAttachments():
 * Use Boyer-Moore-Horspool algorithm to search attachments in chunks
 * Fixed unicode and multiline unittests

11016. By Kamran Riaz Khan

Added support for multi-line searches in Bug.findAttachments() using
is_encoded parameter

11015. By Kamran Riaz Khan

Fixed Bug.findAttachments() to use preferred encoding instead of UTF-8

11014. By Kamran Riaz Khan

Changes in Bug.findAttachments():
 * Limited the size of attachments that can be searched to 8 MB
 * Modified searching to go-through attachments line-wise

11013. By Kamran Riaz Khan

Fixed unicode searches in Bug.findAttachments()

11012. By Kamran Riaz Khan

Added unit tests for Bug.findAttachments() method

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/bugs/configure.zcml'
2--- lib/lp/bugs/configure.zcml 2010-06-04 09:31:21 +0000
3+++ lib/lp/bugs/configure.zcml 2010-06-15 00:23:30 +0000
4@@ -644,6 +644,7 @@
5 getBugWatch
6 canBeNominatedFor
7 getNominationFor
8+ findAttachments
9 getNominations
10 date_last_message
11 number_of_duplicates
12
13=== modified file 'lib/lp/bugs/interfaces/bug.py'
14--- lib/lp/bugs/interfaces/bug.py 2010-06-10 18:55:22 +0000
15+++ lib/lp/bugs/interfaces/bug.py 2010-06-15 00:23:30 +0000
16@@ -361,6 +361,14 @@
17 latest_patch = Attribute("The most recent patch of this bug.")
18
19 @operation_parameters(
20+ text=TextLine(title=_("Search text"), default=u""))
21+ @operation_returns_collection_of(IBugAttachment)
22+ @export_read_operation()
23+ def findAttachments(text=""):
24+ """Return all attachments related to this bug which match
25+ <text>."""
26+
27+ @operation_parameters(
28 subject=optional_message_subject_field(),
29 content=copy_field(IMessage['content']))
30 @call_with(owner=REQUEST_USER)
31
32=== modified file 'lib/lp/bugs/model/bug.py'
33--- lib/lp/bugs/model/bug.py 2010-06-10 18:55:22 +0000
34+++ lib/lp/bugs/model/bug.py 2010-06-15 00:23:30 +0000
35@@ -812,6 +812,24 @@
36 notification.date_emailed = UTC_NOW
37 notification.syncUpdate()
38
39+ def findAttachments(self, text=u""):
40+ """See `IBugAttachment`."""
41+ if not isinstance(text, unicode):
42+ raise TypeError, "`text` must be a unicode string"
43+ if not text:
44+ return EmptyResultSet()
45+
46+ store = IStore(Bug)
47+
48+ result = store.find(BugAttachment, BugAttachment.bug == self.id)
49+ matches = []
50+ for attachment in result:
51+ data = attachment.data.read()
52+ if text in data:
53+ matches.append(attachment)
54+
55+ return matches
56+
57 def newMessage(self, owner=None, subject=None,
58 content=None, parent=None, bugwatch=None,
59 remote_comment_id=None):
60
61=== added file 'lib/lp/bugs/tests/test_bug_find_attachment.py'
62--- lib/lp/bugs/tests/test_bug_find_attachment.py 1970-01-01 00:00:00 +0000
63+++ lib/lp/bugs/tests/test_bug_find_attachment.py 2010-06-15 00:23:30 +0000
64@@ -0,0 +1,68 @@
65+# Copyright 2010 Canonical Ltd. This software is licensed under the
66+# GNU Affero General Public License version 3 (see the file LICENSE).
67+
68+__metaclass__ = type
69+
70+import os
71+import transaction
72+import unittest
73+
74+from canonical.config import config
75+from canonical.testing import LaunchpadZopelessLayer
76+
77+from lp.bugs.interfaces.bugjob import BugJobType
78+from lp.bugs.model.bugjob import BugJob, BugJobDerived
79+from lp.testing import TestCaseWithFactory
80+
81+from storm.store import EmptyResultSet
82+
83+from zope.security.proxy import isinstance
84+
85+class BugFindAttachmentTestCase(TestCaseWithFactory):
86+ """Test case for bug attachment searches."""
87+
88+ layer = LaunchpadZopelessLayer
89+
90+ def setUp(self):
91+ super(BugFindAttachmentTestCase, self).setUp()
92+
93+ dir = os.path.join(config.root, 'lib/lp/bugs/tests/testfiles')
94+ filenames = [
95+ 'sample-attachment-alsa.diff',
96+ 'sample-attachment-pidgin.diff',
97+ ]
98+ self.searches = [
99+ u'CONFIG_SND_HDA_POWER_SAVE',
100+ u'PURPLE_CONNECTION_ERROR_NETWORK_ERROR',
101+ ]
102+ filepaths = [
103+ os.path.join(dir, filename) for
104+ filename in filenames
105+ ]
106+ person = self.factory.makePerson()
107+ self.bug = self.factory.makeBug()
108+
109+ self.attachments = []
110+ for filename, filepath in zip(filenames, filepaths):
111+ attachment = self.factory.makeBugAttachment(
112+ bug=self.bug, owner=person, data=file(filepath),
113+ comment=filename, filename=filename)
114+ self.attachments.append(attachment)
115+
116+ transaction.commit()
117+
118+ def test_text_type_check(self):
119+ self.assertRaises(TypeError, self.bug.findAttachments, 42)
120+
121+ def test_empty_result_set(self):
122+ result = self.bug.findAttachments(u'')
123+ self.assertTrue(isinstance(result, EmptyResultSet))
124+
125+ def test_result_consistency(self):
126+ for (search, attachment) in zip(self.searches, self.attachments):
127+ result = self.bug.findAttachments(search)
128+ self.assertEqual(1, len(result))
129+ self.assertEqual(attachment, result[0])
130+
131+def test_suite():
132+ return unittest.TestLoader().loadTestsFromName(__name__)
133
134=== added file 'lib/lp/bugs/tests/testfiles/sample-attachment-alsa.diff'
135--- lib/lp/bugs/tests/testfiles/sample-attachment-alsa.diff 1970-01-01 00:00:00 +0000
136+++ lib/lp/bugs/tests/testfiles/sample-attachment-alsa.diff 2010-06-15 00:23:30 +0000
137@@ -0,0 +1,15 @@
138+Signed-off-by: Linuxant Support <support@linuxant.com>
139+
140+--- alsa-driver-1.0.16-lnxt/alsa-kernel/pci/hda/hda_codec.h 2008/04/04 19:28:32 1.1
141++++ alsa-driver-1.0.16-lnxt/alsa-kernel/pci/hda/hda_codec.h 2008/04/04 19:34:36
142+@@ -464,6 +464,10 @@
143+ unsigned int verb, unsigned int parm);
144+ /* get a response from the last command */
145+ unsigned int (*get_response)(struct hda_codec *codec);
146++ /* get the wall clock counter */
147++ u32 (*get_wallclock)(struct hda_codec *codec);
148++ /* get the link position counter */
149++ u32 (*get_linkpos)(struct snd_pcm_substream *substream);
150+ /* free the private data */
151+ void (*private_free)(struct hda_bus *);
152+ #ifdef CONFIG_SND_HDA_POWER_SAVE
153
154=== added file 'lib/lp/bugs/tests/testfiles/sample-attachment-pidgin.diff'
155--- lib/lp/bugs/tests/testfiles/sample-attachment-pidgin.diff 1970-01-01 00:00:00 +0000
156+++ lib/lp/bugs/tests/testfiles/sample-attachment-pidgin.diff 2010-06-15 00:23:30 +0000
157@@ -0,0 +1,22 @@
158+--- libpurple/protocols/jabber/jabber.c e98b3c84ef06f15c947b3190304799abef8f30b3
159++++ libpurple/protocols/jabber/jabber.c f005b3dede749de2f533f11c1c70a8575f2ef47a
160+@@ -430,12 +430,17 @@ jabber_recv_cb_ssl(gpointer data, Purple
161+ jabber_stream_init(js);
162+ }
163+
164+- if(errno == EAGAIN)
165++ if(len < 0 && errno == EAGAIN)
166+ return;
167+- else
168++ else {
169++ if (len == 0)
170++ purple_debug_info("jabber", "Server closed the connection.\n");
171++ else
172++ purple_debug_info("jabber", "Disconnected: %s\n", g_strerror(errno));
173+ purple_connection_error_reason (js->gc,
174+ PURPLE_CONNECTION_ERROR_NETWORK_ERROR,
175+ _("Read Error"));
176++ }
177+ }
178+
179+ static void