Merge lp:~maxiberta/launchpad/app-browser-dedup-sitesearch-doctests into lp:launchpad

Proposed by Maximiliano Bertacchini
Status: Merged
Merged at revision: 18626
Proposed branch: lp:~maxiberta/launchpad/app-browser-dedup-sitesearch-doctests
Merge into: lp:launchpad
Diff against target: 965 lines (+55/-768)
6 files modified
lib/lp/app/browser/doc/launchpad-search-pages-bing.txt (+0/-726)
lib/lp/app/browser/doc/launchpad-search-pages.txt (+20/-18)
lib/lp/app/browser/tests/test_views.py (+18/-7)
lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-1.json (+1/-1)
lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-2.json (+11/-11)
lib/lp/services/sitesearch/tests/test_bing.py (+5/-5)
To merge this branch: bzr merge lp:~maxiberta/launchpad/app-browser-dedup-sitesearch-doctests
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+343250@code.launchpad.net

Commit message

Deduplicate lp.app.browser sitesearch doctests.

Description of the change

Deduplicate lp.app.browser sitesearch doctests. In particular, launchpad-search-pages-bing.txt and launchpad-search-pages-google.txt were merged into one single file, which is tested with all search backends.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file 'lib/lp/app/browser/doc/launchpad-search-pages-bing.txt'
2--- lib/lp/app/browser/doc/launchpad-search-pages-bing.txt 2018-04-10 18:17:07 +0000
3+++ lib/lp/app/browser/doc/launchpad-search-pages-bing.txt 1970-01-01 00:00:00 +0000
4@@ -1,726 +0,0 @@
5-Launchpad search page
6-=====================
7-
8-Users can search for Launchpad objects and pages from the search form
9-located on all pages. The search is performed and displayed by the
10-LaunchpadSearchView.
11-
12- >>> from zope.component import getMultiAdapter, getUtility
13- >>> from lp.services.webapp.interfaces import ILaunchpadRoot
14- >>> from lp.services.webapp.servers import LaunchpadTestRequest
15-
16- >>> root = getUtility(ILaunchpadRoot)
17- >>> request = LaunchpadTestRequest()
18- >>> search_view = getMultiAdapter((root, request), name="+search")
19- >>> search_view.initialize()
20- >>> search_view
21- <....SimpleViewClass from .../templates/launchpad-search.pt ...>
22-
23-
24-Page title and heading
25-----------------------
26-
27-The page title and heading suggest to the user to search launchpad
28-when there is no search text.
29-
30- >>> print search_view.text
31- None
32- >>> search_view.page_title
33- 'Search Launchpad'
34- >>> search_view.page_heading
35- 'Search Launchpad'
36-
37-When text is not None, the title indicates what was searched.
38-
39- >>> def getSearchView(form):
40- ... search_param_list = []
41- ... for name in sorted(form):
42- ... value = form[name]
43- ... search_param_list.append('%s=%s' % (name, value))
44- ... query_string = '&'.join(search_param_list)
45- ... request = LaunchpadTestRequest(
46- ... SERVER_URL='https://launchpad.dev/+search',
47- ... QUERY_STRING=query_string, form=form, PATH_INFO='/+search')
48- ... search_view = getMultiAdapter((root, request), name="+search")
49- ... search_view.initialize()
50- ... return search_view
51-
52- >>> search_view = getSearchView(
53- ... form={'field.text': 'albatross'})
54-
55- >>> search_view.text
56- u'albatross'
57- >>> search_view.page_title
58- u'Pages matching "albatross" in Launchpad'
59- >>> search_view.page_heading
60- u'Pages matching "albatross" in Launchpad'
61-
62-
63-No matches
64-----------
65-
66-There were no matches for 'albatross'.
67-
68- >>> search_view.has_matches
69- False
70-
71-When search text is not submitted there are no matches. Search text is
72-required to perform a search. Note that field.actions.search is not a
73-required param to call the Search Action. The view always calls the
74-search action.
75-
76- >>> search_view = getSearchView(form={})
77-
78- >>> print search_view.text
79- None
80- >>> search_view.has_matches
81- False
82-
83-
84-Bug and Question Searches
85--------------------------
86-
87-When a numeric token can be extracted from the submitted search text,
88-the view tries to match a bug and question. Bugs and questions are
89-matched by their id.
90-
91- >>> search_view = getSearchView(
92- ... form={'field.text': '5'})
93- >>> search_view._getNumericToken(search_view.text)
94- u'5'
95- >>> search_view.has_matches
96- True
97- >>> search_view.bug.title
98- u'Firefox install instructions should be complete'
99- >>> search_view.question.title
100- u'Installation failed'
101-
102-Bugs and questions are matched independent of each other. The number
103-extracted may only match one kind of object. For example, there are
104-more bugs than questions.
105-
106- >>> search_view = getSearchView(
107- ... form={'field.text': '15'})
108- >>> search_view._getNumericToken(search_view.text)
109- u'15'
110- >>> search_view.has_matches
111- True
112- >>> search_view.bug.title
113- u'Nonsensical bugs are useless'
114- >>> print search_view.question
115- None
116-
117-Private bugs are not matched if the user does not have permission to
118-see them. For example, Sample Person can see a private bug that they
119-created because they are the owner.
120-
121- >>> from lp.services.webapp.interfaces import ILaunchBag
122- >>> from lp.app.enums import InformationType
123-
124- >>> login('test@canonical.com')
125- >>> sample_person = getUtility(ILaunchBag).user
126- >>> private_bug = factory.makeBug(
127- ... owner=sample_person, information_type=InformationType.USERDATA)
128-
129- >>> search_view = getSearchView(
130- ... form={'field.text': private_bug.id})
131- >>> search_view.bug.private
132- True
133-
134-But anonymous and unprivileged users cannot see the private bug.
135-
136- >>> login(ANONYMOUS)
137- >>> search_view = getSearchView(
138- ... form={'field.text': private_bug.id})
139- >>> print search_view.bug
140- None
141-
142-The text and punctuation in the search text is ignored, and only the
143-first group of numbers is matched. For example a user searches for three
144-questions by number ('Question #15, #7, and 5.'). Only the first number
145-is used, and it matches a bug, not a question. The second and third
146-numbers do match questions, but they are not used.
147-
148- >>> search_view = getSearchView(
149- ... form={'field.text': 'Question #15, #7, and 5.'})
150- >>> search_view._getNumericToken(search_view.text)
151- u'15'
152- >>> search_view.has_matches
153- True
154- >>> search_view.bug.title
155- u'Nonsensical bugs are useless'
156- >>> print search_view.question
157- None
158-
159-It is not an error to search for a non-existent bug or question.
160-
161- >>> search_view = getSearchView(
162- ... form={'field.text': '55555'})
163- >>> search_view._getNumericToken(search_view.text)
164- u'55555'
165- >>> search_view.has_matches
166- False
167- >>> print search_view.bug
168- None
169- >>> print search_view.question
170- None
171-
172-There is no error if a number cannot be extracted from the search text.
173-
174- >>> search_view = getSearchView(
175- ... form={'field.text': 'fifteen'})
176- >>> print search_view._getNumericToken(
177- ... search_view.text)
178- None
179- >>> search_view.has_matches
180- False
181- >>> print search_view.bug
182- None
183- >>> print search_view.question
184- None
185-
186-Bugs and questions are only returned for the first page of search,
187-when the start param is 0.
188-
189- >>> search_view = getSearchView(
190- ... form={'field.text': '5',
191- ... 'start': '20'})
192- >>> search_view.has_matches
193- False
194- >>> print search_view.bug
195- None
196- >>> print search_view.question
197- None
198-
199-
200-
201-Projects and Persons and Teams searches
202----------------------------------------
203-
204-When a Launchpad name can be made from the search text, the view tries
205-to match the name to a pillar or person. a pillar is a distribution,
206-product, or project group. A person is a person or a team.
207-
208- >>> search_view = getSearchView(
209- ... form={'field.text': 'launchpad'})
210- >>> search_view._getNameToken(search_view.text)
211- u'launchpad'
212- >>> search_view.has_matches
213- True
214- >>> search_view.pillar.displayname
215- u'Launchpad'
216- >>> search_view.person_or_team.displayname
217- u'Launchpad Developers'
218-
219-A launchpad name is constructed from the search text. The letters are
220-converted to lowercase. groups of spaces and punctuation are replaced
221-with a hyphen.
222-
223- >>> search_view = getSearchView(
224- ... form={'field.text': 'Gnome Terminal'})
225- >>> search_view._getNameToken(search_view.text)
226- u'gnome-terminal'
227- >>> search_view.has_matches
228- True
229- >>> search_view.pillar.displayname
230- u'GNOME Terminal'
231- >>> print search_view.person_or_team
232- None
233-
234-Since our pillars can have aliases, it's also possible to look up a pillar
235-by any of its aliases.
236-
237- >>> from lp.registry.interfaces.product import IProductSet
238- >>> firefox = getUtility(IProductSet)['firefox']
239- >>> login('foo.bar@canonical.com')
240- >>> firefox.setAliases(['iceweasel'])
241- >>> login(ANONYMOUS)
242- >>> search_view = getSearchView(
243- ... form={'field.text': 'iceweasel'})
244- >>> search_view._getNameToken(search_view.text)
245- u'iceweasel'
246- >>> search_view.has_matches
247- True
248- >>> search_view.pillar.displayname
249- u'Mozilla Firefox'
250-
251-This is a harder example that illustrates that text that is clearly not
252-the name of a pillar will none-the-less be tried. See the `Page searches`
253-section for how this kind of search can return matches.
254-
255- >>> search_view = getSearchView(
256- ... form={'field.text': "YAHOO! webservice's Python API."})
257- >>> search_view._getNameToken(search_view.text)
258- u'yahoo-webservices-python-api.'
259- >>> search_view.has_matches
260- False
261- >>> print search_view.pillar
262- None
263- >>> print search_view.person_or_team
264- None
265-
266-Leading and trailing punctuation and whitespace are stripped.
267-
268- >>> search_view = getSearchView(
269- ... form={'field.text': "~name12"})
270- >>> search_view._getNameToken(search_view.text)
271- u'name12'
272- >>> search_view.has_matches
273- True
274- >>> print search_view.pillar
275- None
276- >>> search_view.person_or_team.displayname
277- u'Sample Person'
278-
279-Pillars, persons and teams are only returned for the first page of
280-search, when the start param is 0.
281-
282- >>> search_view = getSearchView(
283- ... form={'field.text': 'launchpad',
284- ... 'start': '20'})
285- >>> search_view.has_matches
286- True
287- >>> print search_view.bug
288- None
289- >>> print search_view.question
290- None
291- >>> print search_view.pillar
292- None
293-
294-Deactivated pillars and non-valid persons and teams cannot be exact
295-matches. For example, the python-gnome2-dev product will not match a
296-pillar, nor will nsv match Nicolas Velin's unclaimed account.
297-
298- >>> from lp.registry.interfaces.person import IPersonSet
299-
300- >>> python_gnome2 = getUtility(IProductSet).getByName('python-gnome2-dev')
301- >>> python_gnome2.active
302- False
303-
304- >>> search_view = getSearchView(
305- ... form={'field.text': 'python-gnome2-dev',
306- ... 'start': '0'})
307- >>> search_view._getNameToken(search_view.text)
308- u'python-gnome2-dev'
309- >>> print search_view.pillar
310- None
311-
312- >>> nsv = getUtility(IPersonSet).getByName('nsv')
313- >>> nsv.displayname
314- u'Nicolas Velin'
315- >>> nsv.is_valid_person_or_team
316- False
317-
318- >>> search_view = getSearchView(
319- ... form={'field.text': 'nsv',
320- ... 'start': '0'})
321- >>> search_view._getNameToken(search_view.text)
322- u'nsv'
323- >>> print search_view.person_or_team
324- None
325-
326-Private pillars are not matched if the user does not have permission to see
327-them. For example, Sample Person can see a private project that they created
328-because they are the owner.
329-
330- >>> from lp.registry.interfaces.product import License
331-
332- >>> login('test@canonical.com')
333- >>> private_product = factory.makeProduct(
334- ... owner=sample_person, information_type=InformationType.PROPRIETARY,
335- ... licenses=[License.OTHER_PROPRIETARY])
336- >>> private_product_name = private_product.name
337-
338- >>> search_view = getSearchView(form={'field.text': private_product_name})
339- >>> search_view.pillar.private
340- True
341-
342-But anonymous and unprivileged users cannot see the private project.
343-
344- >>> login(ANONYMOUS)
345- >>> search_view = getSearchView(form={'field.text': private_product_name})
346- >>> print search_view.pillar
347- None
348-
349-
350-Shipit CD searches
351-------------------
352-
353-The has_shipit property will be True when the search looks like the user
354-is searching for Shipit CDs. There is no correct object in Launchpad to
355-display. The page template decides how to handle when has_shipit is
356-True.
357-
358-The match is based on an intersection to the words in the search text
359-and the shipit_keywords. The comparison is case-insensitive, has_shipit
360-is True when 2 or more words match.
361-
362- >>> sorted(search_view.shipit_keywords)
363- ['cd', 'cds', 'disc', 'dvd', 'dvds', 'edubuntu', 'free', 'get', 'kubuntu',
364- 'mail', 'send', 'ship', 'shipit', 'ubuntu']
365- >>> search_view = getSearchView(
366- ... form={'field.text': 'ubuntu CDs',
367- ... 'start': '0'})
368- >>> search_view.has_shipit
369- True
370-
371- >>> search_view = getSearchView(
372- ... form={'field.text': 'shipit',
373- ... 'start': '0'})
374- >>> search_view.has_shipit
375- False
376-
377- >>> search_view = getSearchView(
378- ... form={'field.text': 'get Kubuntu cds',
379- ... 'start': '0'})
380- >>> search_view.has_shipit
381- True
382-
383-There are shipit_anti_keywords too, words that indicate the search is
384-not for free CDs from Shipit. Search that have any of these word will
385-set has_shipit to False.
386-
387- >>> sorted(search_view.shipit_anti_keywords)
388- ['burn', 'burning', 'enable', 'error', 'errors', 'image', 'iso',
389- 'read', 'rip', 'write']
390-
391- >>> search_view = getSearchView(
392- ... form={'field.text': 'ubuntu CD write',
393- ... 'start': '0'})
394- >>> search_view.has_shipit
395- False
396-
397- >>> search_view = getSearchView(
398- ... form={'field.text': 'shipit error',
399- ... 'start': '0'})
400- >>> search_view.has_shipit
401- False
402-
403-
404-The shipit FAQ URL is provides by the view for the template to use.
405-
406- >>> search_view.shipit_faq_url
407- 'http://www.ubuntu.com/getubuntu/shipit-faq'
408-
409-
410-Page searches
411--------------
412-
413-The view uses the BingSearchService to locate pages that match the
414-search terms.
415-
416- >>> search_view = getSearchView(
417- ... form={'field.text': " bug"})
418- >>> search_view.text
419- u'bug'
420- >>> search_view.has_matches
421- True
422- >>> search_view.pages
423- <...SiteSearchBatchNavigator ...>
424-
425-The BingSearchService may not be available due to connectivity problems.
426-The view's has_page_service attribute reports when the search was performed
427-with Bing page matches.
428-
429- >>> search_view.has_page_service
430- True
431-
432-The batch navigation heading is created by the view. The heading
433-property returns a 2-tuple of singular and plural heading. There
434-is a heading when there are only Bing page matches...
435-
436- >>> search_view.has_exact_matches
437- False
438- >>> search_view.batch_heading
439- (u'page matching "bug"', u'pages matching "bug"')
440-
441-...and a heading for when there are exact matches and Bing page
442-matches.
443-
444- >>> search_view = getSearchView(
445- ... form={'field.text': " launchpad"})
446- >>> search_view.has_exact_matches
447- True
448- >>> search_view.batch_heading
449- (u'other page matching "launchpad"', u'other pages matching "launchpad"')
450-
451-The SiteSearchBatchNavigator behaves like most BatchNavigators, except that
452-its batch size is always 20. The size restriction conforms to Google's
453-maximum number of results that can be returned per request.
454-
455- >>> search_view.start
456- 0
457- >>> search_view.pages.currentBatch().size
458- 20
459- >>> pages = list(search_view.pages.currentBatch())
460- >>> len(pages)
461- 20
462- >>> for page in pages[0:5]:
463- ... page.title
464- u'Launchpad Bugs'
465- u'Bugs in Ubuntu Linux'
466- u'Bugs related to Sample Person'
467- u'Bug #1 in Mozilla Firefox: Firefox does not support SVG'
468- u'Question #232632 : Questions : OpenStack Heat'
469-
470-The batch navigator provides access to the other batches. There are two
471-batches of pages that match the search text 'bugs'. The navigator
472-provides a link to the next batch, which also happens to be the last
473-batch.
474-
475- >>> search_view.pages.nextBatchURL()
476- '...start=20'
477- >>> search_view.pages.lastBatchURL()
478- '...start=20'
479-
480-The second batch has only five matches in it, even though the batch size
481-is 20. That is because there were only 25 matching pages.
482-
483- >>> search_view = getSearchView(
484- ... form={'field.text': "bug",
485- ... 'start': '20'})
486- >>> search_view.start
487- 20
488- >>> search_view.text
489- u'bug'
490- >>> search_view.has_matches
491- True
492-
493- >>> search_view.pages.currentBatch().size
494- 20
495- >>> pages = list(search_view.pages.currentBatch())
496- >>> len(pages)
497- 5
498- >>> for page in pages:
499- ... page.title
500- u'Bugs - Launchpad Help'
501- u'Of Bugs and Statuses - Launchpad Blog'
502- u'Mahara 1.8.0'
503- u'Mighty Box in Launchpad'
504- u'Bug tracking - Launchpad Bugs'
505-
506- >>> search_view.pages.nextBatchURL()
507- ''
508- >>> search_view.pages.lastBatchURL()
509- ''
510-
511-The PageMatch object has a title, url, and summary. The title and url
512-are used for making links to the pages. The summary contains markup
513-showing the matching terms in context of the page text.
514-
515- >>> print range(20)
516- [0, 1, ..., 18, 19]
517- >>> page = pages[0]
518- >>> page
519- <...PageMatch ...>
520- >>> page.title
521- u'Bugs - Launchpad Help'
522- >>> page.url
523- 'https://help.launchpad.net/Bugs'
524- >>> page.summary # doctest: +ELLIPSIS
525- u'Launchpad Help &gt; Bugs . Use Launchpad&#x27;s bug tracker for your
526- project...'
527-
528-See `google-searchservice.txt` for more information about the
529-BingSearchService and PageMatch objects.
530-
531-
532-No page matches
533----------------
534-
535-When an empty PageMatches object is returned by the BingSearchService to
536-the view, there are no matches to show.
537-
538- >>> search_view = getSearchView(form={'field.text': 'no-meaningful'})
539- >>> search_view.has_matches
540- False
541-
542-
543-Unintelligible searches
544------------------------
545-
546-When a user searches for a malformed string, we don't OOPS, but show an
547-error. Also disable warnings, since we are tossing around malformed Unicode.
548-
549- >>> import warnings
550- >>> with warnings.catch_warnings():
551- ... warnings.simplefilter('ignore')
552- ... search_view = getSearchView(
553- ... form={'field.text': '\xfe\xfckr\xfc'})
554- >>> html = search_view()
555- >>> 'Can not convert your search term' in html
556- True
557-
558-
559-Bad Bing response handling
560-----------------------------
561-
562-Connectivity problems can cause missing or incomplete responses from
563-Bing. The LaunchpadSearchView will display the other searches and
564-show a message explaining that the user can search again to find
565-matching pages.
566-
567- >>> search_view = getSearchView(form={'field.text': 'gnomebaker'})
568- >>> search_view.has_matches
569- True
570- >>> search_view.pillar.displayname
571- u'gnomebaker'
572- >>> search_view.has_page_service
573- False
574-
575-The view provides the requested URL so that the template can make a
576-link to try the search again
577-
578- >>> print search_view.url
579- https://launchpad.dev/+search?field.text=gnomebaker
580-
581-
582-SearchFormView and SearchFormPrimaryView
583-----------------------------------------
584-
585-Two companion views are used to help render the global search form.
586-They define the required attributes to render the form in the
587-correct state.
588-
589-The LaunchpadSearchFormView provides the minimum information to display
590-the form, but cannot handled the submitted data. It appends a suffix
591-('-secondary') to the id= and name= of the form and inputs, to prevent
592-them from conflicting with the other form. The search text is not the
593-default value of the text field; 'bug' was submitted above, but is not
594-present in the rendered form.
595-
596- >>> search_form_view = getMultiAdapter(
597- ... (search_view, request), name='+search-form')
598- >>> search_form_view.initialize()
599- >>> search_form_view.id_suffix
600- '-secondary'
601- >>> print search_form_view.render()
602- <form action="http://launchpad.dev/+search" method="get"
603- accept-charset="UTF-8" id="sitesearch-secondary"
604- name="sitesearch-secondary">
605- <div>
606- <input class="textType" type="text" size="36"
607- id="field.text-secondary" name="field.text" />
608- <input class="button" type="submit" value="Search"
609- id="field.text-secondary" name="field.actions.search-secondary" />
610- </div>
611- </form>
612-
613-LaunchpadPrimarySearchFormView can handle submitted form by deferring to
614-its context (the LaunchpadSearchView) for the needed information. The
615-view does not append a suffix to the form and input ids. The search
616-field's value is 'bug', as was submitted above.
617-
618- >>> search_form_view = getMultiAdapter(
619- ... (search_view, request), name='+primary-search-form')
620- >>> search_form_view.initialize()
621- >>> search_form_view.id_suffix
622- ''
623- >>> print search_form_view.render()
624- <form action="http://launchpad.dev/+search" method="get"
625- accept-charset="UTF-8" id="sitesearch"
626- name="sitesearch">
627- <div>
628- <input class="textType" type="text" size="36"
629- id="field.text" value="gnomebaker" name="field.text" />
630- <input class="button" type="submit" value="Search"
631- id="field.text" name="field.actions.search" />
632- </div>
633- </form>
634-
635-WindowedList and SiteSearchBatchNavigator
636--------------------------------------
637-
638-The LaunchpadSearchView uses two helper classes to work with
639-PageMatches.
640-
641-The PageMatches object returned by the BingSearchService contains 20
642-or fewer PageMatches of what could be thousands of matches. Bing
643-requires client's to make repeats request to step though the batches of
644-matches. The Windowed list is a list that contains only a subset of its
645-reported size. It is used to make batches in the SiteSearchBatchNavigator.
646-
647-For example, the last batch of the 'bug' search contained 5 of the 25
648-matching pages. The WindowList claims to be 25 items in length, but
649-the first 20 items are None. Only the last 5 items are PageMatches.
650-
651- >>> from lp.app.browser.root import WindowedList
652- >>> from lp.services.sitesearch import BingSearchService
653-
654- >>> bing_search = BingSearchService()
655- >>> page_matches = bing_search.search(terms='bug', start=20)
656- >>> results = WindowedList(
657- ... page_matches, page_matches.start, page_matches.total)
658- >>> len(results)
659- 25
660- >>> print results[0]
661- None
662- >>> results[24].title
663- u'Bug tracking - Launchpad Bugs'
664- >>> results[18, 22]
665- [None, None, <...PageMatch ...>, <...PageMatch ...>]
666-
667-The SiteSearchBatchNavigator restricts the batch size to 20. the 'batch'
668-parameter that comes from the URL is ignored. For example, setting
669-the 'batch' parameter to 100 has no affect upon the Bing search
670-or on the navigator object.
671-
672- >>> from lp.app.browser.root import SiteSearchBatchNavigator
673-
674- >>> SiteSearchBatchNavigator.batch_variable_name
675- 'batch'
676-
677- >>> search_view = getSearchView(
678- ... form={'field.text': "bug",
679- ... 'start': '0',
680- ... 'batch': '100',})
681-
682- >>> navigator = search_view.pages
683- >>> navigator.currentBatch().size
684- 20
685- >>> len(navigator.currentBatch())
686- 20
687- >>> navigator.nextBatchURL()
688- '...start=20'
689-
690-Even if the PageMatch object to have an impossibly large size, the
691-navigator conforms to Google's maximum size of 20.
692-
693- >>> matches = list(range(0, 100))
694- >>> page_matches._matches = matches
695- >>> page_matches.start = 0
696- >>> page_matches.total = 100
697- >>> navigator = SiteSearchBatchNavigator(
698- ... page_matches, search_view.request, page_matches.start, size=100)
699- >>> navigator.currentBatch().size
700- 20
701- >>> len(navigator.currentBatch())
702- 20
703- >>> navigator.nextBatchURL()
704- '...start=20'
705-
706-The PageMatches object can be smaller than 20, for instance, pages
707-without titles are skipped when parsing the Bing Search JSON. The size
708-of the batch is still 20, but when the items in the batch are iterated,
709-the true size can be seen. For example there could be only 3 matches in
710-the PageMatches object, so only 3 are yielded. The start of the next
711-batch is 20, which is the start of the next batch from Bing.
712-
713- >>> matches = list(range(0, 3))
714- >>> page_matches._matches = matches
715- >>> navigator = SiteSearchBatchNavigator(
716- ... page_matches, search_view.request, page_matches.start, size=100)
717- >>> batch = navigator.currentBatch()
718- >>> batch.size
719- 20
720- >>> len(batch)
721- 20
722- >>> batch.endNumber()
723- 3
724- >>> for item in batch:
725- ... print item
726- 0
727- 1
728- 2
729- >>> navigator.nextBatchURL()
730- '...start=20'
731
732=== renamed file 'lib/lp/app/browser/doc/launchpad-search-pages-google.txt' => 'lib/lp/app/browser/doc/launchpad-search-pages.txt'
733--- lib/lp/app/browser/doc/launchpad-search-pages-google.txt 2018-03-28 19:31:02 +0000
734+++ lib/lp/app/browser/doc/launchpad-search-pages.txt 2018-04-25 15:46:57 +0000
735@@ -456,12 +456,12 @@
736 >>> len(pages)
737 20
738 >>> for page in pages[0:5]:
739- ... page.title
740- 'Launchpad Bugs'
741- 'Bugs in Ubuntu Linux'
742- 'Bugs related to Sample Person'
743- u'<b>Bug</b> #1 in Mozilla Firefox: ...Firefox does not support SVG...'
744- 'Bugs in Source Package "thunderbird" in Ubuntu Linux'
745+ ... unicode(page.title)
746+ u'Launchpad Bugs'
747+ u'Bugs in Ubuntu Linux'
748+ u'Bugs related to Sample Person'
749+ u'...Bug... #1 in Mozilla Firefox: ...Firefox does not support SVG...'
750+ u'Bugs in Source Package ...thunderbird... in Ubuntu Linux'
751
752 The batch navigator provides access to the other batches. There are two
753 batches of pages that match the search text 'bugs'. The navigator
754@@ -492,12 +492,12 @@
755 >>> len(pages)
756 5
757 >>> for page in pages:
758- ... page.title
759- u'<b>Bug</b> #2 in Ubuntu Hoary: \u201cBlackhole Trash folder\u201d'
760- u'<b>Bug</b> #2 in mozilla-firefox (Debian): ...Blackhole Trash folder...'
761- u'<b>Bug</b> #3 in mozilla-firefox (Debian): \u201cBug Title Test\u201d'
762- '<b>Bug</b> trackers registered in Launchpad'
763- u'<b>Bug</b> tracker \u201cDebian Bug tracker\u201d'
764+ ... unicode(page.title)
765+ u'...Bug... #2 in Ubuntu Hoary: \u201cBlackhole Trash folder\u201d'
766+ u'...Bug... #2 in mozilla-firefox (Debian): ...Blackhole Trash folder...'
767+ u'...Bug... #3 in mozilla-firefox (Debian): \u201cBug Title Test\u201d'
768+ u'...Bug... trackers registered in Launchpad'
769+ u'...Bug... tracker \u201cDebian Bug tracker\u201d'
770
771 >>> search_view.pages.nextBatchURL()
772 ''
773@@ -512,11 +512,11 @@
774 >>> page
775 <...PageMatch ...>
776 >>> page.title
777- u'<b>Bug</b> #2 in Ubuntu Hoary: \u201cBlackhole Trash folder\u201d'
778+ u'...Bug... #2 in Ubuntu Hoary: \u201cBlackhole Trash folder\u201d'
779 >>> page.url
780 'http://bugs.launchpad.dev/ubuntu/hoary/+bug/2'
781 >>> page.summary
782- u'<b>Bug</b> tracking <b>...</b> Search <b>bugs</b> reports ...'
783+ u'...Launchpad\u2019s ...bug... tracker allows collaboration...'
784
785 See `google-searchservice.txt` for more information about the
786 GoogleSearchService and PageMatch objects.
787@@ -642,10 +642,12 @@
788 the first 20 items are None. Only the last 5 items are PageMatches.
789
790 >>> from lp.app.browser.root import WindowedList
791- >>> from lp.services.sitesearch import GoogleSearchService
792+ >>> from lp.services.sitesearch.interfaces import active_search_service
793+ >>> from zope.security.proxy import removeSecurityProxy
794
795- >>> google_search = GoogleSearchService()
796- >>> page_matches = google_search.search(terms='bug', start=20)
797+ >>> site_search = active_search_service()
798+ >>> naked_site_search = removeSecurityProxy(site_search)
799+ >>> page_matches = naked_site_search.search(terms='bug', start=20)
800 >>> results = WindowedList(
801 ... page_matches, page_matches.start, page_matches.total)
802 >>> len(results)
803@@ -653,7 +655,7 @@
804 >>> print results[0]
805 None
806 >>> results[24].title
807- u'<b>Bug</b> tracker \u201cDebian Bug tracker\u201d'
808+ u'...Bug... tracker \u201cDebian Bug tracker\u201d'
809 >>> results[18, 22]
810 [None, None, <...PageMatch ...>, <...PageMatch ...>]
811
812
813=== modified file 'lib/lp/app/browser/tests/test_views.py'
814--- lib/lp/app/browser/tests/test_views.py 2018-03-28 19:31:02 +0000
815+++ lib/lp/app/browser/tests/test_views.py 2018-04-25 15:46:57 +0000
816@@ -1,9 +1,11 @@
817 # Copyright 2009-2018 Canonical Ltd. This software is licensed under the
818 # GNU Affero General Public License version 3 (see the file LICENSE).
819
820-"""
821-Run the view tests.
822-"""
823+"""Run the view tests."""
824+
825+from __future__ import absolute_import, print_function, unicode_literals
826+
827+__metaclass__ = type
828
829 import logging
830 import os
831@@ -13,6 +15,7 @@
832 from lp.testing.layers import (
833 BingLaunchpadFunctionalLayer,
834 GoogleLaunchpadFunctionalLayer,
835+ PageTestLayer,
836 )
837 from lp.testing.systemdocs import (
838 LayeredDocFileSuite,
839@@ -50,16 +53,24 @@
840 # that require something special like the librarian or mailman must run
841 # on a layer that sets those services up.
842 special = {
843- 'launchpad-search-pages-bing.txt': LayeredDocFileSuite(
844- '../doc/launchpad-search-pages-bing.txt',
845+ 'launchpad-search-pages.txt(Bing)': LayeredDocFileSuite(
846+ '../doc/launchpad-search-pages.txt',
847+ id_extensions=['launchpad-search-pages.txt(Bing)'],
848 setUp=setUp_bing, tearDown=tearDown_bing,
849 layer=BingLaunchpadFunctionalLayer,
850 stdout_logging_level=logging.WARNING),
851- 'launchpad-search-pages-google.txt': LayeredDocFileSuite(
852- '../doc/launchpad-search-pages-google.txt',
853+ 'launchpad-search-pages.txt(Google)': LayeredDocFileSuite(
854+ '../doc/launchpad-search-pages.txt',
855+ id_extensions=['launchpad-search-pages.txt(Google)'],
856 setUp=setUp_google, tearDown=tearDown_google,
857 layer=GoogleLaunchpadFunctionalLayer,
858 stdout_logging_level=logging.WARNING),
859+ # Run these doctests again with the default search engine.
860+ 'launchpad-search-pages.txt': LayeredDocFileSuite(
861+ '../doc/launchpad-search-pages.txt',
862+ setUp=setUp, tearDown=tearDown,
863+ layer=PageTestLayer,
864+ stdout_logging_level=logging.WARNING),
865 }
866
867
868
869=== modified file 'lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-1.json'
870--- lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-1.json 2018-03-28 19:11:16 +0000
871+++ lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-1.json 2018-04-25 15:46:57 +0000
872@@ -58,7 +58,7 @@
873 },
874 {
875 "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.4",
876- "name": "Question #232632 : Questions : OpenStack Heat",
877+ "name": "Bugs in Source Package \"thunderbird\" in Ubuntu Linux",
878 "url": "https://answers.launchpad.net/heat/+question/232632",
879 "urlPingSuffix": "DevEx,5140.1",
880 "datePublished": "2013-07-18T00:00:00.0000000",
881
882=== modified file 'lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-2.json'
883--- lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-2.json 2018-03-27 20:45:52 +0000
884+++ lib/lp/services/sitesearch/tests/data/bingsearchservice-bugs-2.json 2018-04-25 15:46:57 +0000
885@@ -14,12 +14,12 @@
886 "value": [
887 {
888 "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.0",
889- "name": "Bugs - Launchpad Help",
890- "url": "https://help.launchpad.net/Bugs",
891+ "name": "Bug #2 in Ubuntu Hoary: “Blackhole Trash folder”",
892+ "url": "http://bugs.launchpad.dev/ubuntu/hoary/+bug/2",
893 "urlPingSuffix": "DevEx,5103.1",
894 "isFamilyFriendly": true,
895 "displayUrl": "https://help.launchpad.net/Bugs",
896- "snippet": "Launchpad Help > Bugs . Use Launchpad's bug tracker for your project. Bug heat: a computed estimate of a bug's significance. Learn about automatic bug expiry",
897+ "snippet": "Bug tracking ... Search bugs reports ... Launchpad’s bug tracker allows collaboration ...",
898 "deepLinks": [
899 {
900 "name": "Bugs/EmailInterface",
901@@ -57,8 +57,8 @@
902 },
903 {
904 "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.1",
905- "name": "Of Bugs and Statuses - Launchpad Blog",
906- "url": "https://blog.launchpad.net/general/of-bugs-and-statuses",
907+ "name": "Bug #2 in mozilla-firefox (Debian): “Blackhole Trash folder”",
908+ "url": "http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+bug/2",
909 "urlPingSuffix": "DevEx,5149.1",
910 "isFamilyFriendly": true,
911 "displayUrl": "https://blog.launchpad.net/general/of-bugs-and-statuses",
912@@ -68,8 +68,8 @@
913 },
914 {
915 "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.2",
916- "name": "Mahara 1.8.0",
917- "url": "https://launchpad.net/mahara/+milestone/1.8.0",
918+ "name": "Bug #3 in mozilla-firefox (Debian): “Bug Title Test”",
919+ "url": "http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+bug/3",
920 "urlPingSuffix": "DevEx,5178.1",
921 "isFamilyFriendly": true,
922 "displayUrl": "https://launchpad.net/mahara/+milestone/1.8.0",
923@@ -79,8 +79,8 @@
924 },
925 {
926 "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.3",
927- "name": "Mighty Box in Launchpad",
928- "url": "https://launchpad.net/mb",
929+ "name": "Bug trackers registered in Launchpad",
930+ "url": "http://bugs.launchpad.dev/bugs/bugtrackers",
931 "urlPingSuffix": "DevEx,5193.1",
932 "isFamilyFriendly": true,
933 "displayUrl": "https://launchpad.net/mb",
934@@ -90,8 +90,8 @@
935 },
936 {
937 "id": "https://api.cognitive.microsoft.com/api/v7/#WebPages.4",
938- "name": "Bug tracking - Launchpad Bugs",
939- "url": "https://launchpad.net/bugs",
940+ "name": "Bug tracker “Debian Bug tracker”",
941+ "url": "http://bugs.launchpad.dev/bugs/bugtrackers/debbugs",
942 "urlPingSuffix": "DevEx,5208.1",
943 "about": [
944 {
945
946=== modified file 'lib/lp/services/sitesearch/tests/test_bing.py'
947--- lib/lp/services/sitesearch/tests/test_bing.py 2018-04-16 18:43:52 +0000
948+++ lib/lp/services/sitesearch/tests/test_bing.py 2018-04-25 15:46:57 +0000
949@@ -246,11 +246,11 @@
950 self.assertEqual(25, matches.total)
951 self.assertEqual(5, len(matches))
952 self.assertEqual([
953- 'https://help.launchpad.net/Bugs',
954- 'http://blog.launchpad.dev/general/of-bugs-and-statuses',
955- 'http://launchpad.dev/mahara/+milestone/1.8.0',
956- 'http://launchpad.dev/mb',
957- 'http://launchpad.dev/bugs'],
958+ 'http://bugs.launchpad.dev/ubuntu/hoary/+bug/2',
959+ 'http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+bug/2',
960+ 'http://bugs.launchpad.dev/debian/+source/mozilla-firefox/+bug/3',
961+ 'http://bugs.launchpad.dev/bugs/bugtrackers',
962+ 'http://bugs.launchpad.dev/bugs/bugtrackers/debbugs'],
963 [match.url for match in matches])
964
965 def test_search_no_results(self):