Merge lp:~jelmer/bzr-search/hpss into lp:bzr-search
- hpss
- Merge into trunk
Proposed by
Jelmer Vernooij
Status: | Merged |
---|---|
Merged at revision: | 89 |
Proposed branch: | lp:~jelmer/bzr-search/hpss |
Merge into: | lp:bzr-search |
Diff against target: |
519 lines (+446/-3) 5 files modified
__init__.py (+20/-0) index.py (+21/-3) remote.py (+276/-0) tests/__init__.py (+1/-0) tests/test_remote.py (+128/-0) |
To merge this branch: | bzr merge lp:~jelmer/bzr-search/hpss |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Bazaar Developers | Pending | ||
Review via email: mp+83224@code.launchpad.net |
Commit message
Description of the change
Add HPSS calls to allow remote access to the bzr-search index, and matching client side calls.
* Branch.open_index
* Branch.init_index
* Index.index_
* Index.indexed_
* Index.suggest
* Index.search
(now updated to not have lp:~jelmer/bzr-search/lazy merged)
To post a comment you must log in.
Revision history for this message
Robert Collins (lifeless) wrote : | # |
Revision history for this message
Andrew Bennetts (spiv) wrote : | # |
Robert Collins wrote:
> Shiny. +1
I haven't had time to look at the code, but the description is very shiny.
Thanks for making extending HPSS via plugins a reality! :)
-Andrew.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file '__init__.py' | |||
2 | --- __init__.py 2008-08-28 02:13:47 +0000 | |||
3 | +++ __init__.py 2011-11-23 21:50:26 +0000 | |||
4 | @@ -38,6 +38,7 @@ | |||
5 | 38 | import commands | 38 | import commands |
6 | 39 | import errors | 39 | import errors |
7 | 40 | import index | 40 | import index |
8 | 41 | from bzrlib.smart.request import request_handlers as smart_request_handlers | ||
9 | 41 | 42 | ||
10 | 42 | 43 | ||
11 | 43 | for command in [ | 44 | for command in [ |
12 | @@ -79,6 +80,25 @@ | |||
13 | 79 | index.make_log_search_filter) | 80 | index.make_log_search_filter) |
14 | 80 | 81 | ||
15 | 81 | 82 | ||
16 | 83 | smart_request_handlers.register_lazy( | ||
17 | 84 | "Branch.open_index", 'bzrlib.plugins.search.remote', | ||
18 | 85 | 'SmartServerBranchRequestOpenIndex') | ||
19 | 86 | smart_request_handlers.register_lazy( | ||
20 | 87 | "Branch.init_index", 'bzrlib.plugins.search.remote', | ||
21 | 88 | 'SmartServerBranchRequestInitIndex') | ||
22 | 89 | smart_request_handlers.register_lazy( | ||
23 | 90 | "Index.index_revisions", 'bzrlib.plugins.search.remote', | ||
24 | 91 | 'SmartServerIndexRequestIndexRevisions') | ||
25 | 92 | smart_request_handlers.register_lazy( | ||
26 | 93 | "Index.indexed_revisions", 'bzrlib.plugins.search.remote', | ||
27 | 94 | 'SmartServerIndexRequestIndexedRevisions') | ||
28 | 95 | smart_request_handlers.register_lazy( | ||
29 | 96 | "Index.suggest", 'bzrlib.plugins.search.remote', | ||
30 | 97 | 'SmartServerIndexRequestSuggest') | ||
31 | 98 | smart_request_handlers.register_lazy( | ||
32 | 99 | "Index.search", 'bzrlib.plugins.search.remote', | ||
33 | 100 | 'SmartServerIndexRequestSearch') | ||
34 | 101 | |||
35 | 82 | def test_suite(): | 102 | def test_suite(): |
36 | 83 | # Thunk across to load_tests for niceness with older bzr versions | 103 | # Thunk across to load_tests for niceness with older bzr versions |
37 | 84 | from bzrlib.tests import TestLoader | 104 | from bzrlib.tests import TestLoader |
38 | 85 | 105 | ||
39 | === modified file 'index.py' | |||
40 | --- index.py 2011-09-19 23:34:37 +0000 | |||
41 | +++ index.py 2011-11-23 21:50:26 +0000 | |||
42 | @@ -31,8 +31,8 @@ | |||
43 | 31 | NotBranchError, | 31 | NotBranchError, |
44 | 32 | NoSuchFile, | 32 | NoSuchFile, |
45 | 33 | UnknownFormatError, | 33 | UnknownFormatError, |
48 | 34 | IncompatibleAPI, | 34 | UnknownSmartMethod, |
49 | 35 | ) | 35 | ) |
50 | 36 | from bzrlib.index import CombinedGraphIndex, GraphIndex, InMemoryGraphIndex | 36 | from bzrlib.index import CombinedGraphIndex, GraphIndex, InMemoryGraphIndex |
51 | 37 | from bzrlib.lockdir import LockDir | 37 | from bzrlib.lockdir import LockDir |
52 | 38 | try: | 38 | try: |
53 | @@ -120,6 +120,14 @@ | |||
54 | 120 | transport = transport.clone(path) | 120 | transport = transport.clone(path) |
55 | 121 | transport.ensure_base() | 121 | transport.ensure_base() |
56 | 122 | index_transport = transport | 122 | index_transport = transport |
57 | 123 | elif getattr(branch.bzrdir, "_call", None) is not None: | ||
58 | 124 | # FIXME 2011-11-17 JRV: Is there a better way to probe | ||
59 | 125 | # for smart server branches ? | ||
60 | 126 | from bzrlib.plugins.search.remote import RemoteIndex | ||
61 | 127 | try: | ||
62 | 128 | return RemoteIndex.init(branch) | ||
63 | 129 | except UnknownSmartMethod: | ||
64 | 130 | raise errors.CannotIndex(branch) | ||
65 | 123 | else: | 131 | else: |
66 | 124 | raise errors.CannotIndex(branch) | 132 | raise errors.CannotIndex(branch) |
67 | 125 | lockdir = LockDir(index_transport, 'names-lock') | 133 | lockdir = LockDir(index_transport, 'names-lock') |
68 | @@ -205,6 +213,16 @@ | |||
69 | 205 | path = 'bzr-search/svn-lookaside/' + uuid + '/' + branch_path | 213 | path = 'bzr-search/svn-lookaside/' + uuid + '/' + branch_path |
70 | 206 | transport = transport.clone(path) | 214 | transport = transport.clone(path) |
71 | 207 | commits_only = False | 215 | commits_only = False |
72 | 216 | elif getattr(branch.bzrdir, "_call", None) is not None: | ||
73 | 217 | # FIXME 2011-11-17 JRV: Is there a better way to probe | ||
74 | 218 | # for smart server branches ? | ||
75 | 219 | from bzrlib.plugins.search.remote import RemoteIndex | ||
76 | 220 | try: | ||
77 | 221 | return RemoteIndex.open(branch) | ||
78 | 222 | except UnknownSmartMethod: | ||
79 | 223 | # Fall back to traditional methods... | ||
80 | 224 | transport = branch.bzrdir.transport.clone('bzr-search') | ||
81 | 225 | commits_only = False | ||
82 | 208 | else: | 226 | else: |
83 | 209 | transport = branch.bzrdir.transport.clone('bzr-search') | 227 | transport = branch.bzrdir.transport.clone('bzr-search') |
84 | 210 | commits_only = False | 228 | commits_only = False |
85 | @@ -391,7 +409,7 @@ | |||
86 | 391 | 409 | ||
87 | 392 | def _add_index(self, builder, to_remove=None, allow_pack=True): | 410 | def _add_index(self, builder, to_remove=None, allow_pack=True): |
88 | 393 | """Add a new component index to the list of indices. | 411 | """Add a new component index to the list of indices. |
90 | 394 | 412 | ||
91 | 395 | :param builder: A component builder supporting the upload_index call. | 413 | :param builder: A component builder supporting the upload_index call. |
92 | 396 | :param to_remove: An optional iterable of components to remove. | 414 | :param to_remove: An optional iterable of components to remove. |
93 | 397 | :param allow_pack: Whether an auto pack is permitted by this operation. | 415 | :param allow_pack: Whether an auto pack is permitted by this operation. |
94 | 398 | 416 | ||
95 | === added file 'remote.py' | |||
96 | --- remote.py 1970-01-01 00:00:00 +0000 | |||
97 | +++ remote.py 2011-11-23 21:50:26 +0000 | |||
98 | @@ -0,0 +1,276 @@ | |||
99 | 1 | # search, a bzr plugin for searching within bzr branches/repositories. | ||
100 | 2 | # Copyright (C) 2011 Jelmer Vernooij | ||
101 | 3 | # | ||
102 | 4 | # This program is free software; you can redistribute it and/or modify | ||
103 | 5 | # it under the terms of the GNU General Public License version 2 as published | ||
104 | 6 | # by the Free Software Foundation. | ||
105 | 7 | # | ||
106 | 8 | # This program is distributed in the hope that it will be useful, | ||
107 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
108 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
109 | 11 | # GNU General Public License for more details. | ||
110 | 12 | # | ||
111 | 13 | # You should have received a copy of the GNU General Public License | ||
112 | 14 | # along with this program; if not, write to the Free Software | ||
113 | 15 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
114 | 16 | # | ||
115 | 17 | |||
116 | 18 | """Smart server integration for bzr-search.""" | ||
117 | 19 | |||
118 | 20 | from bzrlib import remote | ||
119 | 21 | from bzrlib.controldir import ControlDir | ||
120 | 22 | from bzrlib.errors import ( | ||
121 | 23 | ErrorFromSmartServer, | ||
122 | 24 | UnexpectedSmartServerResponse, | ||
123 | 25 | ) | ||
124 | 26 | from bzrlib.smart.branch import ( | ||
125 | 27 | SmartServerBranchRequest, | ||
126 | 28 | ) | ||
127 | 29 | from bzrlib.smart.request import SuccessfulSmartServerResponse | ||
128 | 30 | |||
129 | 31 | from bzrlib.plugins.search import errors, index | ||
130 | 32 | |||
131 | 33 | |||
132 | 34 | def _encode_termlist(termlist): | ||
133 | 35 | return ["\0".join([k.encode('utf-8') for k in term]) for term in termlist] | ||
134 | 36 | |||
135 | 37 | def _decode_termlist(termlist): | ||
136 | 38 | return [tuple([k.decode('utf-8') for k in term.split('\0')]) for term in termlist] | ||
137 | 39 | |||
138 | 40 | |||
139 | 41 | class RemoteIndex(object): | ||
140 | 42 | """Index accessed over a smart server.""" | ||
141 | 43 | |||
142 | 44 | def __init__(self, client, path, branch=None): | ||
143 | 45 | self._client = client | ||
144 | 46 | self._path = path | ||
145 | 47 | self._branch = branch | ||
146 | 48 | |||
147 | 49 | def _call(self, method, *args, **err_context): | ||
148 | 50 | try: | ||
149 | 51 | return self._client.call(method, *args) | ||
150 | 52 | except ErrorFromSmartServer, err: | ||
151 | 53 | self._translate_error(err, **err_context) | ||
152 | 54 | |||
153 | 55 | def _call_expecting_body(self, method, *args, **err_context): | ||
154 | 56 | try: | ||
155 | 57 | return self._client.call_expecting_body(method, *args) | ||
156 | 58 | except ErrorFromSmartServer, err: | ||
157 | 59 | self._translate_error(err, **err_context) | ||
158 | 60 | |||
159 | 61 | def _call_with_body_bytes(self, method, args, body_bytes, **err_context): | ||
160 | 62 | try: | ||
161 | 63 | return self._client.call_with_body_bytes(method, args, body_bytes) | ||
162 | 64 | except ErrorFromSmartServer, err: | ||
163 | 65 | self._translate_error(err, **err_context) | ||
164 | 66 | |||
165 | 67 | def _call_with_body_bytes_expecting_body(self, method, args, body_bytes, | ||
166 | 68 | **err_context): | ||
167 | 69 | try: | ||
168 | 70 | return self._client.call_with_body_bytes_expecting_body( | ||
169 | 71 | method, args, body_bytes) | ||
170 | 72 | except errors.ErrorFromSmartServer, err: | ||
171 | 73 | self._translate_error(err, **err_context) | ||
172 | 74 | |||
173 | 75 | def _translate_error(self, err, **context): | ||
174 | 76 | remote._translate_error(err, index=self, **context) | ||
175 | 77 | |||
176 | 78 | @classmethod | ||
177 | 79 | def open(cls, branch): | ||
178 | 80 | # This might raise UnknownSmartMethod, | ||
179 | 81 | # but the caller should handle that. | ||
180 | 82 | response = branch._call("Branch.open_index", | ||
181 | 83 | branch._remote_path()) | ||
182 | 84 | if response == ('no', ): | ||
183 | 85 | raise errors.NoSearchIndex(branch.user_transport) | ||
184 | 86 | if response != ('yes', ): | ||
185 | 87 | raise UnexpectedSmartServerResponse(response) | ||
186 | 88 | return RemoteIndex(branch._client, branch._remote_path(), branch) | ||
187 | 89 | |||
188 | 90 | @classmethod | ||
189 | 91 | def init(cls, branch): | ||
190 | 92 | response = branch._call("Branch.init_index", | ||
191 | 93 | branch._remote_path()) | ||
192 | 94 | if response != ('ok', ): | ||
193 | 95 | raise errors.UnexpectedSmartServerResponse(response) | ||
194 | 96 | return RemoteIndex(branch._client, branch._remote_path(), branch) | ||
195 | 97 | |||
196 | 98 | def index_branch(self, branch, tip_revision): | ||
197 | 99 | """Index revisions from a branch. | ||
198 | 100 | |||
199 | 101 | :param branch: The branch to index. | ||
200 | 102 | :param tip_revision: The tip of the branch. | ||
201 | 103 | """ | ||
202 | 104 | self.index_revisions(branch, [tip_revision]) | ||
203 | 105 | |||
204 | 106 | def index_revisions(self, branch, revisions_to_index): | ||
205 | 107 | """Index some revisions from branch. | ||
206 | 108 | |||
207 | 109 | :param branch: A branch to index. | ||
208 | 110 | :param revisions_to_index: A set of revision ids to index. | ||
209 | 111 | """ | ||
210 | 112 | body = "\n".join(revisions_to_index) | ||
211 | 113 | response = self._call_with_body_bytes( | ||
212 | 114 | 'Index.index_revisions', (self._path, branch._remote_path(),), | ||
213 | 115 | body) | ||
214 | 116 | if response != ('ok', ): | ||
215 | 117 | raise errors.UnexpectedSmartServerResponse(response) | ||
216 | 118 | |||
217 | 119 | def indexed_revisions(self): | ||
218 | 120 | """Return the revision_keys that this index contains terms for.""" | ||
219 | 121 | response, handler = self._call_expecting_body( | ||
220 | 122 | 'Index.indexed_revisions', self._path) | ||
221 | 123 | if response != ('ok', ): | ||
222 | 124 | raise errors.UnexpectedSmartServerResponse(response) | ||
223 | 125 | byte_stream = handler.read_streamed_body() | ||
224 | 126 | data = "" | ||
225 | 127 | for bytes in byte_stream: | ||
226 | 128 | data += bytes | ||
227 | 129 | lines = data.split("\n") | ||
228 | 130 | data = lines.pop() | ||
229 | 131 | for revid in lines: | ||
230 | 132 | yield (revid, ) | ||
231 | 133 | |||
232 | 134 | def search(self, termlist): | ||
233 | 135 | """Trivial set-based search of the index. | ||
234 | 136 | |||
235 | 137 | :param termlist: A list of terms. | ||
236 | 138 | :return: An iterator of SearchResults for documents indexed by all | ||
237 | 139 | terms in the termlist. | ||
238 | 140 | """ | ||
239 | 141 | index._ensure_regexes() | ||
240 | 142 | response, handler = self._call_expecting_body('Index.search', | ||
241 | 143 | self._path, _encode_termlist(termlist)) | ||
242 | 144 | if response != ('ok', ): | ||
243 | 145 | raise errors.UnexpectedSmartServerResponse(response) | ||
244 | 146 | byte_stream = handler.read_streamed_body() | ||
245 | 147 | data = "" | ||
246 | 148 | ret = [] | ||
247 | 149 | for bytes in byte_stream: | ||
248 | 150 | data += bytes | ||
249 | 151 | lines = data.split("\n") | ||
250 | 152 | data = lines.pop() | ||
251 | 153 | for l in lines: | ||
252 | 154 | if l[0] == 'r': | ||
253 | 155 | hit = index.RevisionHit(self._branch.repository, (l[1:], )) | ||
254 | 156 | elif l[0] == 't': | ||
255 | 157 | hit = index.FileTextHit(self, self._branch.repository, | ||
256 | 158 | tuple(l[1:].split("\0")), termlist) | ||
257 | 159 | elif l[0] == 'p': | ||
258 | 160 | hit = index.PathHit(l[1:]) | ||
259 | 161 | else: | ||
260 | 162 | raise AssertionError("Unknown hit kind %r" % l[0]) | ||
261 | 163 | # We can't yield, since the caller might try to look up results | ||
262 | 164 | # over the same medium. | ||
263 | 165 | ret.append(hit) | ||
264 | 166 | return iter(ret) | ||
265 | 167 | |||
266 | 168 | def suggest(self, termlist): | ||
267 | 169 | """Generate suggestions for extending a search. | ||
268 | 170 | |||
269 | 171 | :param termlist: A list of terms. | ||
270 | 172 | :return: An iterator of terms that start with the last search term in | ||
271 | 173 | termlist, and match the rest of the search. | ||
272 | 174 | """ | ||
273 | 175 | response = self._call('Index.suggest', | ||
274 | 176 | self._path, _encode_termlist(termlist)) | ||
275 | 177 | if response[0] != 'ok': | ||
276 | 178 | raise UnexpectedSmartServerResponse(response) | ||
277 | 179 | return [(suggestion.decode('utf-8'),) for suggestion in response[1]] | ||
278 | 180 | |||
279 | 181 | |||
280 | 182 | class SmartServerBranchRequestOpenIndex(SmartServerBranchRequest): | ||
281 | 183 | """Open an index file.""" | ||
282 | 184 | |||
283 | 185 | def do_with_branch(self, branch): | ||
284 | 186 | """open an index.""" | ||
285 | 187 | try: | ||
286 | 188 | idx = index.open_index_branch(branch) | ||
287 | 189 | except errors.NoSearchIndex: | ||
288 | 190 | return SuccessfulSmartServerResponse(('no', )) | ||
289 | 191 | else: | ||
290 | 192 | return SuccessfulSmartServerResponse(('yes', )) | ||
291 | 193 | |||
292 | 194 | |||
293 | 195 | class SmartServerBranchRequestInitIndex(SmartServerBranchRequest): | ||
294 | 196 | """Create an index.""" | ||
295 | 197 | |||
296 | 198 | def do_with_branch(self, branch, format=None): | ||
297 | 199 | """Create an index.""" | ||
298 | 200 | if format is None: | ||
299 | 201 | idx = index.init_index(branch) | ||
300 | 202 | else: | ||
301 | 203 | idx = index.init_index(branch, format) | ||
302 | 204 | return SuccessfulSmartServerResponse(('ok', )) | ||
303 | 205 | |||
304 | 206 | |||
305 | 207 | class SmartServerIndexRequest(SmartServerBranchRequest): | ||
306 | 208 | """Base class for index requests.""" | ||
307 | 209 | |||
308 | 210 | def do_with_branch(self, branch, *args): | ||
309 | 211 | idx = index.open_index_branch(branch) | ||
310 | 212 | return self.do_with_index(idx, *args) | ||
311 | 213 | |||
312 | 214 | def do_with_index(self, index, *args): | ||
313 | 215 | raise NotImplementedError(self.do_with_index) | ||
314 | 216 | |||
315 | 217 | |||
316 | 218 | class SmartServerIndexRequestIndexRevisions(SmartServerIndexRequest): | ||
317 | 219 | """Index a set of revisions.""" | ||
318 | 220 | |||
319 | 221 | def do_body(self, body_bytes): | ||
320 | 222 | revids = body_bytes.split("\n") | ||
321 | 223 | self._index.index_revisions(self._branch, revids) | ||
322 | 224 | return SuccessfulSmartServerResponse(('ok', )) | ||
323 | 225 | |||
324 | 226 | def do_with_index(self, index, branch_path): | ||
325 | 227 | self._index = index | ||
326 | 228 | transport = self.transport_from_client_path(branch_path) | ||
327 | 229 | controldir = ControlDir.open_from_transport(transport) | ||
328 | 230 | if controldir.get_branch_reference() is not None: | ||
329 | 231 | raise errors.NotBranchError(transport.base) | ||
330 | 232 | self._branch = controldir.open_branch(ignore_fallbacks=True) | ||
331 | 233 | # Indicate we want a body | ||
332 | 234 | return None | ||
333 | 235 | |||
334 | 236 | |||
335 | 237 | class SmartServerIndexRequestIndexedRevisions(SmartServerIndexRequest): | ||
336 | 238 | """Retrieve the set of revisions in the index.""" | ||
337 | 239 | |||
338 | 240 | def body_stream(self, index): | ||
339 | 241 | for revid in index.indexed_revisions(): | ||
340 | 242 | yield "%s\n" % "\0".join(revid) | ||
341 | 243 | |||
342 | 244 | def do_with_index(self, index): | ||
343 | 245 | return SuccessfulSmartServerResponse(('ok', ), | ||
344 | 246 | body_stream=self.body_stream(index)) | ||
345 | 247 | |||
346 | 248 | |||
347 | 249 | class SmartServerIndexRequestSuggest(SmartServerIndexRequest): | ||
348 | 250 | """Suggest alternative terms.""" | ||
349 | 251 | |||
350 | 252 | def do_with_index(self, index, termlist): | ||
351 | 253 | suggestions = index.suggest(_decode_termlist(termlist)) | ||
352 | 254 | return SuccessfulSmartServerResponse( | ||
353 | 255 | ('ok', | ||
354 | 256 | [suggestion.encode('utf-8') for (suggestion,) in suggestions])) | ||
355 | 257 | |||
356 | 258 | |||
357 | 259 | class SmartServerIndexRequestSearch(SmartServerIndexRequest): | ||
358 | 260 | """Search for terms.""" | ||
359 | 261 | |||
360 | 262 | def body_stream(self, results): | ||
361 | 263 | for hit in results: | ||
362 | 264 | if isinstance(hit, index.FileTextHit): | ||
363 | 265 | yield "t%s\0%s\n" % hit.text_key | ||
364 | 266 | elif isinstance(hit, index.RevisionHit): | ||
365 | 267 | yield "r%s\n" % hit.revision_key[0] | ||
366 | 268 | elif isinstance(hit, index.PathHit): | ||
367 | 269 | yield "p%s\n" % hit.path_utf8 | ||
368 | 270 | else: | ||
369 | 271 | raise AssertionError("Unknown hit type %r" % hit) | ||
370 | 272 | |||
371 | 273 | def do_with_index(self, index, termlist): | ||
372 | 274 | results = index.search(_decode_termlist(termlist)) | ||
373 | 275 | return SuccessfulSmartServerResponse( | ||
374 | 276 | ('ok',), body_stream=self.body_stream(results)) | ||
375 | 0 | 277 | ||
376 | === modified file 'tests/__init__.py' | |||
377 | --- tests/__init__.py 2008-06-14 05:07:54 +0000 | |||
378 | +++ tests/__init__.py 2011-11-23 21:50:26 +0000 | |||
379 | @@ -25,6 +25,7 @@ | |||
380 | 25 | 'errors', | 25 | 'errors', |
381 | 26 | 'index', | 26 | 'index', |
382 | 27 | 'inventory', | 27 | 'inventory', |
383 | 28 | 'remote', | ||
384 | 28 | 'transport', | 29 | 'transport', |
385 | 29 | ] | 30 | ] |
386 | 30 | standard_tests.addTests(loader.loadTestsFromModuleNames( | 31 | standard_tests.addTests(loader.loadTestsFromModuleNames( |
387 | 31 | 32 | ||
388 | === added file 'tests/test_remote.py' | |||
389 | --- tests/test_remote.py 1970-01-01 00:00:00 +0000 | |||
390 | +++ tests/test_remote.py 2011-11-23 21:50:26 +0000 | |||
391 | @@ -0,0 +1,128 @@ | |||
392 | 1 | # search, a bzr plugin for searching within bzr branches/repositories. | ||
393 | 2 | # Copyright (C) 2011 Jelmer Vernooij | ||
394 | 3 | # | ||
395 | 4 | # This program is free software; you can redistribute it and/or modify | ||
396 | 5 | # it under the terms of the GNU General Public License version 2 as published | ||
397 | 6 | # by the Free Software Foundation. | ||
398 | 7 | # | ||
399 | 8 | # This program is distributed in the hope that it will be useful, | ||
400 | 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
401 | 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
402 | 11 | # GNU General Public License for more details. | ||
403 | 12 | # | ||
404 | 13 | # You should have received a copy of the GNU General Public License | ||
405 | 14 | # along with this program; if not, write to the Free Software | ||
406 | 15 | # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | ||
407 | 16 | # | ||
408 | 17 | |||
409 | 18 | """Tests for the smart server verbs.""" | ||
410 | 19 | |||
411 | 20 | from bzrlib import tests | ||
412 | 21 | from bzrlib.branch import Branch | ||
413 | 22 | from bzrlib.smart import ( | ||
414 | 23 | request as smart_req, | ||
415 | 24 | ) | ||
416 | 25 | |||
417 | 26 | from bzrlib.plugins.search import ( | ||
418 | 27 | errors, | ||
419 | 28 | index, | ||
420 | 29 | ) | ||
421 | 30 | from bzrlib.plugins.search.remote import ( | ||
422 | 31 | RemoteIndex, | ||
423 | 32 | SmartServerBranchRequestOpenIndex, | ||
424 | 33 | ) | ||
425 | 34 | |||
426 | 35 | |||
427 | 36 | class TestSmartServerBranchRequestOpenIndex( | ||
428 | 37 | tests.TestCaseWithMemoryTransport): | ||
429 | 38 | |||
430 | 39 | def test_missing(self): | ||
431 | 40 | """For an empty branch, the result is ('no', ).""" | ||
432 | 41 | backing = self.get_transport() | ||
433 | 42 | request = SmartServerBranchRequestOpenIndex(backing) | ||
434 | 43 | self.make_branch('.') | ||
435 | 44 | self.assertEqual(smart_req.SmartServerResponse(('no', )), | ||
436 | 45 | request.execute('')) | ||
437 | 46 | |||
438 | 47 | def test_present(self): | ||
439 | 48 | """For a branch with an index, ('yes', ) is returned.""" | ||
440 | 49 | backing = self.get_transport() | ||
441 | 50 | request = SmartServerBranchRequestOpenIndex(backing) | ||
442 | 51 | b = self.make_branch('.') | ||
443 | 52 | index.init_index(b) | ||
444 | 53 | self.assertEqual(smart_req.SmartServerResponse(('yes', )), | ||
445 | 54 | request.execute('')) | ||
446 | 55 | |||
447 | 56 | |||
448 | 57 | class TestRemoteIndex(tests.TestCaseWithTransport): | ||
449 | 58 | |||
450 | 59 | def test_no_index(self): | ||
451 | 60 | local_branch = self.make_branch('.') | ||
452 | 61 | remote_transport = self.make_smart_server('.') | ||
453 | 62 | remote_branch = Branch.open_from_transport(remote_transport) | ||
454 | 63 | self.assertRaises(errors.NoSearchIndex, RemoteIndex.open, | ||
455 | 64 | remote_branch) | ||
456 | 65 | |||
457 | 66 | def test_open(self): | ||
458 | 67 | local_branch = self.make_branch('.') | ||
459 | 68 | index.init_index(local_branch) | ||
460 | 69 | remote_transport = self.make_smart_server('.') | ||
461 | 70 | remote_branch = Branch.open_from_transport(remote_transport) | ||
462 | 71 | idx = RemoteIndex.open(remote_branch) | ||
463 | 72 | self.assertIsInstance(idx, RemoteIndex) | ||
464 | 73 | |||
465 | 74 | def test_init(self): | ||
466 | 75 | local_branch = self.make_branch('.') | ||
467 | 76 | remote_transport = self.make_smart_server('.') | ||
468 | 77 | remote_branch = Branch.open_from_transport(remote_transport) | ||
469 | 78 | idx = index.init_index(remote_branch) | ||
470 | 79 | self.assertIsInstance(idx, RemoteIndex) | ||
471 | 80 | |||
472 | 81 | def test_init_exists(self): | ||
473 | 82 | local_branch = self.make_branch('.') | ||
474 | 83 | index.init_index(local_branch) | ||
475 | 84 | remote_transport = self.make_smart_server('.') | ||
476 | 85 | remote_branch = Branch.open_from_transport(remote_transport) | ||
477 | 86 | #self.assertRaises( index.init_index, remote_branch) | ||
478 | 87 | |||
479 | 88 | |||
480 | 89 | class TestWithRemoteIndex(tests.TestCaseWithTransport): | ||
481 | 90 | |||
482 | 91 | def make_remote_index(self): | ||
483 | 92 | tree = self.make_branch_and_tree('.') | ||
484 | 93 | local_branch = tree.branch | ||
485 | 94 | index.init_index(local_branch) | ||
486 | 95 | remote_transport = self.make_smart_server('.') | ||
487 | 96 | remote_branch = Branch.open_from_transport(remote_transport) | ||
488 | 97 | return tree, remote_branch, RemoteIndex.open(remote_branch) | ||
489 | 98 | |||
490 | 99 | def test_index_revisions(self): | ||
491 | 100 | tree, branch, index = self.make_remote_index() | ||
492 | 101 | tree.commit(message="message", rev_id='revid1') | ||
493 | 102 | index.index_revisions(branch, ['revid1']) | ||
494 | 103 | self.assertEquals([('revid1',)], list(index.indexed_revisions())) | ||
495 | 104 | |||
496 | 105 | def test_indexed_revisions(self): | ||
497 | 106 | tree, branch, remote_index = self.make_remote_index() | ||
498 | 107 | tree.commit(message="message", rev_id='revid1') | ||
499 | 108 | self.assertEquals([], list(remote_index.indexed_revisions())) | ||
500 | 109 | local_index = index.open_index_branch(tree.branch) | ||
501 | 110 | local_index.index_revisions(tree.branch, ['revid1']) | ||
502 | 111 | self.assertEquals([('revid1',)], list(remote_index.indexed_revisions())) | ||
503 | 112 | |||
504 | 113 | def test_suggest(self): | ||
505 | 114 | tree, branch, remote_index = self.make_remote_index() | ||
506 | 115 | tree.commit(message="first", rev_id='revid1') | ||
507 | 116 | local_index = index.open_index_branch(tree.branch) | ||
508 | 117 | local_index.index_revisions(tree.branch, ['revid1']) | ||
509 | 118 | self.assertEquals([(u'first',)], list(remote_index.suggest([(u'f',)]))) | ||
510 | 119 | |||
511 | 120 | def test_search(self): | ||
512 | 121 | tree, branch, remote_index = self.make_remote_index() | ||
513 | 122 | # The double-space is a cheap smoke test for the tokeniser. | ||
514 | 123 | revid = tree.commit('first post') | ||
515 | 124 | remote_index.index_revisions(branch, [revid]) | ||
516 | 125 | results = list(remote_index.search([('post',)])) | ||
517 | 126 | self.assertEqual(1, len(results)) | ||
518 | 127 | self.assertIsInstance(results[0], index.RevisionHit) | ||
519 | 128 | self.assertEqual((revid,), results[0].revision_key) |
Shiny. +1