10
from textwrap import TextWrapper
12
from BeautifulSoup import BeautifulSoup
8
13
from lxml import html
15
from storm.zope.interfaces import IResultSet
16
from testtools.content import (
20
from testtools.content_type import UTF8_TEXT
21
from testtools.matchers import (
26
from zope.component import getUtility
27
from zope.security.proxy import removeSecurityProxy
10
from canonical.testing.layers import DatabaseFunctionalLayer
29
from canonical.config import config
30
from canonical.database.sqlbase import flush_database_caches
31
from canonical.launchpad.interfaces.launchpad import ILaunchpadCelebrities
32
from canonical.launchpad.testing.pages import find_tag_by_id
33
from canonical.launchpad.webapp.batching import BatchNavigator
34
from canonical.launchpad.webapp.publisher import canonical_url
35
from canonical.testing.layers import (
36
DatabaseFunctionalLayer,
37
LaunchpadFunctionalLayer,
38
LaunchpadZopelessLayer,
40
from lp.registry.browser.distroseries import (
42
HIGHER_VERSION_THAN_PARENT,
46
from lp.registry.enum import (
47
DistroSeriesDifferenceStatus,
48
DistroSeriesDifferenceType,
50
from lp.services.features import (
51
get_relevant_feature_controller,
53
install_feature_controller,
55
from lp.services.features.flags import FeatureController
56
from lp.services.features.model import (
60
from lp.soyuz.enums import (
61
PackagePublishingStatus,
64
from lp.soyuz.interfaces.distributionjob import (
65
IInitialiseDistroSeriesJobSource,
67
from lp.soyuz.interfaces.sourcepackageformat import (
68
ISourcePackageFormatSelectionSet,
11
70
from lp.testing import (
76
StormStatementRecorder,
15
77
TestCaseWithFactory,
79
from lp.testing.matchers import HasQueryCount
17
80
from lp.testing.views import create_initialized_view
83
def set_derived_series_ui_feature_flag(test_case):
84
# Helper to set the feature flag enabling the derived series ui.
85
getFeatureStore().add(FeatureFlag(
86
scope=u'default', flag=u'soyuz.derived-series-ui.enabled',
87
value=u'on', priority=1))
89
# XXX Michael Nelson 2010-09-21 bug=631884
90
# Currently LaunchpadTestRequest doesn't set per-thread
94
install_feature_controller(FeatureController(in_scope))
95
test_case.addCleanup(install_feature_controller, None)
98
class TestDistroSeriesView(TestCaseWithFactory):
99
"""Test the distroseries +index view."""
101
layer = LaunchpadZopelessLayer
103
def test_needs_linking(self):
104
ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
105
distroseries = self.factory.makeDistroSeries(distribution=ubuntu)
106
view = create_initialized_view(distroseries, '+index')
107
self.assertEqual(view.needs_linking, None)
109
def _createDifferenceAndGetView(self, difference_type):
110
# Helper function to create a valid DSD.
111
distroseries = self.factory.makeDistroSeries(
112
parent_series=self.factory.makeDistroSeries())
113
ds_diff = self.factory.makeDistroSeriesDifference(
114
derived_series=distroseries, difference_type=difference_type)
115
return create_initialized_view(distroseries, '+index')
117
def test_num_differences(self):
118
diff_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
119
view = self._createDifferenceAndGetView(diff_type)
120
self.assertEqual(1, view.num_differences)
122
def test_num_differences_in_parent(self):
123
diff_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
124
view = self._createDifferenceAndGetView(diff_type)
125
self.assertEqual(1, view.num_differences_in_parent)
127
def test_num_differences_in_child(self):
128
diff_type = DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES
129
view = self._createDifferenceAndGetView(diff_type)
130
self.assertEqual(1, view.num_differences_in_child)
133
class DistroSeriesIndexFunctionalTestCase(TestCaseWithFactory):
134
"""Test the distroseries +index page."""
136
layer = DatabaseFunctionalLayer
138
def _setupDifferences(self, name, parent_name, nb_diff_versions,
139
nb_diff_child, nb_diff_parent):
140
# Helper to create DSD of the different types.
141
derived_series = self.factory.makeDistroSeries(
143
parent_series=self.factory.makeDistroSeries(name=parent_name))
144
self.simple_user = self.factory.makePerson()
145
for i in range(nb_diff_versions):
146
diff_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
147
self.factory.makeDistroSeriesDifference(
148
derived_series=derived_series,
149
difference_type=diff_type)
150
for i in range(nb_diff_child):
151
diff_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
152
self.factory.makeDistroSeriesDifference(
153
derived_series=derived_series,
154
difference_type=diff_type)
155
for i in range(nb_diff_parent):
156
diff_type = DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES
157
self.factory.makeDistroSeriesDifference(
158
derived_series=derived_series,
159
difference_type=diff_type)
160
return derived_series
162
def test_differences_no_flag_no_portlet(self):
163
# The portlet is not displayed if the feature flag is not enabled.
164
derived_series = self._setupDifferences('deri', 'sid', 1, 2, 2)
165
portlet_header = soupmatchers.HTMLContains(
167
'Derivation portlet header', 'h2',
168
text='Derived from Sid'),
171
with person_logged_in(self.simple_user):
172
view = create_initialized_view(
175
principal=self.simple_user)
179
None, getFeatureFlag('soyuz.derived-series-ui.enabled'))
180
self.assertThat(html, Not(portlet_header))
182
def test_differences_portlet_all_differences(self):
183
# The difference portlet shows the differences with the parent
185
set_derived_series_ui_feature_flag(self)
186
derived_series = self._setupDifferences('deri', 'sid', 1, 2, 3)
187
portlet_display = soupmatchers.HTMLContains(
189
'Derivation portlet header', 'h2',
190
text='Derived from Sid'),
192
'Differences link', 'a',
193
text=re.compile('\s*1 package with differences.\s*'),
194
attrs={'href': re.compile('.*/\+localpackagediffs')}),
196
'Parent diffs link', 'a',
197
text=re.compile('\s*2 packages in Sid.\s*'),
198
attrs={'href': re.compile('.*/\+missingpackages')}),
200
'Child diffs link', 'a',
201
text=re.compile('\s*3 packages in Deri.\s*'),
202
attrs={'href': re.compile('.*/\+uniquepackages')}))
204
with person_logged_in(self.simple_user):
205
view = create_initialized_view(
208
principal=self.simple_user)
209
# XXX rvb 2011-04-12 bug=758649: LaunchpadTestRequest overrides
210
# self.features to NullFeatureController.
211
view.request.features = get_relevant_feature_controller()
214
self.assertThat(html, portlet_display)
216
def test_differences_portlet_no_differences(self):
217
# The difference portlet displays 'No differences' if there is no
218
# differences with the parent.
219
set_derived_series_ui_feature_flag(self)
220
derived_series = self._setupDifferences('deri', 'sid', 0, 0, 0)
221
portlet_display = soupmatchers.HTMLContains(
223
'Derivation portlet header', 'h2',
224
text='Derived from Sid'),
226
'Child diffs link', True,
227
text=re.compile('\s*No differences\s*')),
230
with person_logged_in(self.simple_user):
231
view = create_initialized_view(
234
principal=self.simple_user)
235
# XXX rvb 2011-04-12 bug=758649: LaunchpadTestRequest overrides
236
# self.features to NullFeatureController.
237
view.request.features = get_relevant_feature_controller()
240
self.assertThat(html, portlet_display)
242
def test_differences_portlet_initialising(self):
243
# The difference portlet displays 'The series is initialising.' if
244
# there is an initialising job for the series.
245
set_derived_series_ui_feature_flag(self)
246
derived_series = self._setupDifferences('deri', 'sid', 0, 0, 0)
247
job_source = getUtility(IInitialiseDistroSeriesJobSource)
248
job = job_source.create(derived_series.parent, derived_series)
249
portlet_display = soupmatchers.HTMLContains(
251
'Derived series', 'h2',
252
text='Derived series'),
254
'Init message', True,
255
text=re.compile('\s*This series is initialising.\s*')),
258
with person_logged_in(self.simple_user):
259
view = create_initialized_view(
262
principal=self.simple_user)
263
# XXX rvb 2011-04-12 bug=758649: LaunchpadTestRequest overrides
264
# self.features to NullFeatureController.
265
view.request.features = get_relevant_feature_controller()
268
self.assertTrue(derived_series.is_initialising)
269
self.assertThat(html, portlet_display)
272
class TestMilestoneBatchNavigatorAttribute(TestCaseWithFactory):
273
"""Test the series.milestone_batch_navigator attribute."""
275
layer = LaunchpadZopelessLayer
277
def test_distroseries_milestone_batch_navigator(self):
278
ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
279
distroseries = self.factory.makeDistroSeries(distribution=ubuntu)
280
for name in ('a', 'b', 'c', 'd'):
281
distroseries.newMilestone(name)
282
view = create_initialized_view(distroseries, name='+index')
283
self._check_milestone_batch_navigator(view)
285
def test_productseries_milestone_batch_navigator(self):
286
product = self.factory.makeProduct()
287
for name in ('a', 'b', 'c', 'd'):
288
product.development_focus.newMilestone(name)
290
view = create_initialized_view(
291
product.development_focus, name='+index')
292
self._check_milestone_batch_navigator(view)
294
def _check_milestone_batch_navigator(self, view):
295
config.push('default-batch-size', """
297
default_batch_size: 2
300
isinstance(view.milestone_batch_navigator, BatchNavigator),
301
'milestone_batch_navigator is not a BatchNavigator object: %r'
302
% view.milestone_batch_navigator)
303
self.assertEqual(4, view.milestone_batch_navigator.batch.total())
310
for item in view.milestone_batch_navigator.currentBatch()]
311
self.assertEqual(expected, milestone_names)
312
config.pop('default-batch-size')
20
315
class TestDistroSeriesAddView(TestCaseWithFactory):
22
317
layer = DatabaseFunctionalLayer
102
397
u"javascript-disabled",
103
398
message.get("class").split())
401
class DistroSeriesDifferenceMixin:
402
"""A helper class for testing differences pages"""
404
def _test_packagesets(self, html, packageset_text,
405
packageset_class, msg_text):
406
parent_packagesets = soupmatchers.HTMLContains(
409
attrs={'class': packageset_class},
410
text=packageset_text))
412
self.assertThat(html, parent_packagesets)
415
class TestDistroSeriesLocalDifferences(
416
DistroSeriesDifferenceMixin, TestCaseWithFactory):
417
"""Test the distroseries +localpackagediffs page."""
419
layer = DatabaseFunctionalLayer
422
super(TestDistroSeriesLocalDifferences,
423
self).setUp('foo.bar@canonical.com')
424
set_derived_series_ui_feature_flag(self)
425
self.simple_user = self.factory.makePerson()
427
def test_filter_form_if_differences(self):
428
# Test that the page includes the filter form if differences
430
login_person(self.simple_user)
431
derived_series = self.factory.makeDistroSeries(
432
name='derilucid', parent_series=self.factory.makeDistroSeries(
434
self.factory.makeDistroSeriesDifference(
435
derived_series=derived_series)
437
view = create_initialized_view(
438
derived_series, '+localpackagediffs', principal=self.simple_user)
442
find_tag_by_id(view(), 'distroseries-localdiff-search-filter'),
443
"Form filter should be shown when there are differences.")
445
def test_filter_noform_if_nodifferences(self):
446
# Test that the page doesn't includes the filter form if no
447
# differences are present
448
login_person(self.simple_user)
449
derived_series = self.factory.makeDistroSeries(
450
name='derilucid', parent_series=self.factory.makeDistroSeries(
453
view = create_initialized_view(
454
derived_series, '+localpackagediffs', principal=self.simple_user)
458
find_tag_by_id(view(), 'distroseries-localdiff-search-filter'),
459
"Form filter should not be shown when there are no differences.")
461
def test_parent_packagesets_localpackagediffs(self):
462
# +localpackagediffs displays the parent packagesets.
463
ds_diff = self.factory.makeDistroSeriesDifference()
464
with celebrity_logged_in('admin'):
465
ps = self.factory.makePackageset(
466
packages=[ds_diff.source_package_name],
467
distroseries=ds_diff.derived_series.parent_series)
469
with person_logged_in(self.simple_user):
470
view = create_initialized_view(
471
ds_diff.derived_series,
472
'+localpackagediffs',
473
principal=self.simple_user)
476
packageset_text = re.compile('\s*' + ps.name)
477
self._test_packagesets(
478
html, packageset_text, 'parent-packagesets', 'Parent packagesets')
480
def test_parent_packagesets_localpackagediffs_sorts(self):
481
# Multiple packagesets are sorted in a comma separated list.
482
ds_diff = self.factory.makeDistroSeriesDifference()
483
unsorted_names = [u"zzz", u"aaa"]
484
with celebrity_logged_in('admin'):
485
for name in unsorted_names:
486
self.factory.makePackageset(
488
packages=[ds_diff.source_package_name],
489
distroseries=ds_diff.derived_series.parent_series)
491
with person_logged_in(self.simple_user):
492
view = create_initialized_view(
493
ds_diff.derived_series,
494
'+localpackagediffs',
495
principal=self.simple_user)
498
packageset_text = re.compile(
499
'\s*' + ', '.join(sorted(unsorted_names)))
500
self._test_packagesets(
501
html, packageset_text, 'parent-packagesets', 'Parent packagesets')
503
def test_queries(self):
504
# With no DistroSeriesDifferences the query count should be low and
505
# fairly static. However, with some DistroSeriesDifferences the query
506
# count will be higher, but it should remain the same no matter how
507
# many differences there are.
508
login_person(self.simple_user)
509
derived_series = self.factory.makeDistroSeries(
510
parent_series=self.factory.makeDistroSeries())
512
def add_differences(num):
513
for index in xrange(num):
514
version = self.factory.getUniqueInteger()
516
'base': u'1.%d' % version,
517
'derived': u'1.%dderived1' % version,
518
'parent': u'1.%d-1' % version,
520
dsd = self.factory.makeDistroSeriesDifference(
521
derived_series=derived_series,
524
# Push a base_version in... not sure how better to do it.
525
removeSecurityProxy(dsd).base_version = versions["base"]
527
# Add a couple of comments.
528
self.factory.makeDistroSeriesDifferenceComment(dsd)
529
self.factory.makeDistroSeriesDifferenceComment(dsd)
531
# Update the spr, some with recipes, some with signing keys.
532
# SPR.uploader references both, and the uploader is referenced
534
spr = dsd.source_pub.sourcepackagerelease
536
removeSecurityProxy(spr).source_package_recipe_build = (
537
self.factory.makeSourcePackageRecipeBuild(
538
sourcename=spr.sourcepackagename.name,
539
distroseries=derived_series))
541
removeSecurityProxy(spr).dscsigningkey = (
542
self.factory.makeGPGKey(owner=spr.creator))
544
def flush_and_render():
545
flush_database_caches()
546
# Pull in the calling user's location so that it isn't recorded in
547
# the query count; it causes the total to be fragile for no
548
# readily apparent reason.
549
self.simple_user.location
550
with StormStatementRecorder() as recorder:
551
view = create_initialized_view(
552
derived_series, '+localpackagediffs',
553
principal=self.simple_user)
555
return recorder, view.cached_differences.batch.trueSize
557
def statement_differ(rec1, rec2):
558
wrapper = TextWrapper(break_long_words=False)
560
def prepare_statements(rec):
561
for statement in rec.statements:
562
for line in wrapper.wrap(statement):
564
yield "-" * wrapper.width
566
def statement_diff():
567
diff = difflib.ndiff(
568
list(prepare_statements(rec1)),
569
list(prepare_statements(rec2)))
573
return statement_diff
575
# Render without differences and check the query count isn't silly.
576
recorder1, batch_size = flush_and_render()
577
self.assertThat(recorder1, HasQueryCount(LessThan(30)))
579
"statement-count-0-differences",
580
text_content(u"%d" % recorder1.count))
581
# Add some differences and render.
583
recorder2, batch_size = flush_and_render()
585
"statement-count-2-differences",
586
text_content(u"%d" % recorder2.count))
587
# Add more differences and render again.
589
recorder3, batch_size = flush_and_render()
591
"statement-count-4-differences",
592
text_content(u"%d" % recorder3.count))
593
# The last render should not need more queries than the previous.
595
"statement-diff", Content(
596
UTF8_TEXT, statement_differ(recorder2, recorder3)))
597
# Details about the number of statements per row.
598
statement_count_per_row = (
599
(recorder3.count - recorder1.count) / float(batch_size))
601
"statement-count-per-row-average",
602
text_content(u"%.2f" % statement_count_per_row))
603
# XXX: GavinPanella 2011-04-12 bug=760733: Reducing the query count
604
# further needs work. Ideally this test would be along the lines of
605
# recorder3.count == recorder2.count. 4 queries above the recorder2
606
# count is 2 queries per difference which is not acceptable, but is
607
# *far* better than without the changes introduced by landing this.
608
compromise_statement_count = recorder2.count + 4
610
recorder3, HasQueryCount(
611
LessThan(compromise_statement_count + 1)))
614
class TestDistroSeriesLocalDifferencesZopeless(TestCaseWithFactory):
615
"""Test the distroseries +localpackagediffs view."""
617
layer = LaunchpadZopelessLayer
619
def test_view_redirects_without_feature_flag(self):
620
# If the feature flag soyuz.derived-series-ui.enabled is not set the
621
# view simply redirects to the derived series.
622
derived_series = self.factory.makeDistroSeries(
623
name='derilucid', parent_series=self.factory.makeDistroSeries(
627
None, getFeatureFlag('soyuz.derived-series-ui.enabled'))
628
view = create_initialized_view(
629
derived_series, '+localpackagediffs')
631
response = view.request.response
632
self.assertEqual(302, response.getStatus())
634
canonical_url(derived_series), response.getHeader('location'))
636
def test_label(self):
637
# The view label includes the names of both series.
638
derived_series = self.factory.makeDistroSeries(
639
name='derilucid', parent_series=self.factory.makeDistroSeries(
642
view = create_initialized_view(
643
derived_series, '+localpackagediffs')
646
"Source package differences between 'Derilucid' and "
647
"parent series 'Lucid'",
650
def test_batch_includes_needing_attention_only(self):
651
# The differences attribute includes differences needing
653
derived_series = self.factory.makeDistroSeries(
654
name='derilucid', parent_series=self.factory.makeDistroSeries(
656
current_difference = self.factory.makeDistroSeriesDifference(
657
derived_series=derived_series)
658
self.factory.makeDistroSeriesDifference(
659
derived_series=derived_series,
660
status=DistroSeriesDifferenceStatus.RESOLVED)
662
view = create_initialized_view(
663
derived_series, '+localpackagediffs')
665
self.assertContentEqual(
666
[current_difference], view.cached_differences.batch)
668
def test_batch_includes_different_versions_only(self):
669
# The view contains differences of type DIFFERENT_VERSIONS only.
670
derived_series = self.factory.makeDistroSeries(
671
name='derilucid', parent_series=self.factory.makeDistroSeries(
673
different_versions_diff = self.factory.makeDistroSeriesDifference(
674
derived_series=derived_series)
675
self.factory.makeDistroSeriesDifference(
676
derived_series=derived_series,
678
DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES))
680
view = create_initialized_view(
681
derived_series, '+localpackagediffs')
683
self.assertContentEqual(
684
[different_versions_diff], view.cached_differences.batch)
686
def test_template_includes_help_link(self):
687
# The help link for popup help is included.
688
derived_series = self.factory.makeDistroSeries(
689
name='derilucid', parent_series=self.factory.makeDistroSeries(
692
set_derived_series_ui_feature_flag(self)
693
view = create_initialized_view(
694
derived_series, '+localpackagediffs')
696
soup = BeautifulSoup(view())
697
help_links = soup.findAll(
698
'a', href='/+help/soyuz/derived-series-syncing.html')
699
self.assertEqual(1, len(help_links))
701
def test_diff_row_includes_last_comment_only(self):
702
# The most recent comment is rendered for each difference.
703
derived_series = self.factory.makeDistroSeries(
704
name='derilucid', parent_series=self.factory.makeDistroSeries(
706
difference = self.factory.makeDistroSeriesDifference(
707
derived_series=derived_series)
708
difference.addComment(difference.owner, "Earlier comment")
709
difference.addComment(difference.owner, "Latest comment")
711
set_derived_series_ui_feature_flag(self)
712
view = create_initialized_view(
713
derived_series, '+localpackagediffs')
715
# Find all the rows within the body of the table
716
# listing the differences.
717
soup = BeautifulSoup(view())
718
diff_table = soup.find('table', {'class': 'listing'})
719
rows = diff_table.tbody.findAll('tr')
721
self.assertEqual(1, len(rows))
722
self.assertIn("Latest comment", unicode(rows[0]))
723
self.assertNotIn("Earlier comment", unicode(rows[0]))
725
def test_diff_row_links_to_extra_details(self):
726
# The source package name links to the difference details.
727
derived_series = self.factory.makeDistroSeries(
728
name='derilucid', parent_series=self.factory.makeDistroSeries(
730
difference = self.factory.makeDistroSeriesDifference(
731
derived_series=derived_series)
733
set_derived_series_ui_feature_flag(self)
734
view = create_initialized_view(
735
derived_series, '+localpackagediffs')
736
soup = BeautifulSoup(view())
737
diff_table = soup.find('table', {'class': 'listing'})
738
row = diff_table.tbody.findAll('tr')[0]
740
href = canonical_url(difference).replace('http://launchpad.dev', '')
741
links = row.findAll('a', href=href)
742
self.assertEqual(1, len(links))
743
self.assertEqual(difference.source_package_name.name, links[0].string)
745
def test_diff_row_shows_version_attached(self):
746
# The +localpackagediffs page shows the version attached to the
747
# DSD and not the last published version (bug=745776).
748
package_name = 'package-1'
749
derived_series = self.factory.makeDistroSeries(
750
name='derilucid', parent_series=self.factory.makeDistroSeries(
754
'derived': u'1.0derived1',
759
difference = self.factory.makeDistroSeriesDifference(
761
source_package_name_str=package_name,
762
derived_series=derived_series)
764
# Create a more recent source package publishing history.
765
sourcepackagename = self.factory.getOrMakeSourcePackageName(
767
self.factory.makeSourcePackagePublishingHistory(
768
sourcepackagename=sourcepackagename,
769
distroseries=derived_series,
772
set_derived_series_ui_feature_flag(self)
773
view = create_initialized_view(
774
derived_series, '+localpackagediffs')
775
soup = BeautifulSoup(view())
776
diff_table = soup.find('table', {'class': 'listing'})
777
row = diff_table.tbody.tr
778
links = row.findAll('a', {'class': 'derived-version'})
780
# The version displayed is the version attached to the
782
self.assertEqual(1, len(links))
783
self.assertEqual(versions['derived'], links[0].string.strip())
785
link = canonical_url(difference.source_pub.sourcepackagerelease)
786
self.assertTrue(link, EndsWith(new_version))
787
# The link points to the sourcepackagerelease referenced in the
790
links[0].get('href'), EndsWith(difference.source_version))
792
def test_diff_row_no_published_version(self):
793
# The +localpackagediffs page shows only the version (no link)
794
# if we fail to fetch the published version.
795
package_name = 'package-1'
796
derived_series = self.factory.makeDistroSeries(
797
name='derilucid', parent_series=self.factory.makeDistroSeries(
801
'derived': u'1.0derived1',
805
difference = self.factory.makeDistroSeriesDifference(
807
source_package_name_str=package_name,
808
derived_series=derived_series)
810
# Delete the publications.
811
difference.source_pub.status = PackagePublishingStatus.DELETED
812
difference.parent_source_pub.status = PackagePublishingStatus.DELETED
813
# Flush out the changes and invalidate caches (esp. property caches).
814
flush_database_caches()
816
set_derived_series_ui_feature_flag(self)
817
view = create_initialized_view(
818
derived_series, '+localpackagediffs')
819
soup = BeautifulSoup(view())
820
diff_table = soup.find('table', {'class': 'listing'})
821
row = diff_table.tbody.tr
823
# The table feature a simple span since we were unable to fetch a
824
# published sourcepackage.
825
derived_span = row.findAll('span', {'class': 'derived-version'})
826
parent_span = row.findAll('span', {'class': 'parent-version'})
827
self.assertEqual(1, len(derived_span))
828
self.assertEqual(1, len(parent_span))
830
# The versions displayed are the versions attached to the
832
self.assertEqual(versions['derived'], derived_span[0].string.strip())
833
self.assertEqual(versions['parent'], parent_span[0].string.strip())
836
class TestDistroSeriesLocalDifferencesFunctional(TestCaseWithFactory):
838
layer = LaunchpadFunctionalLayer
840
def test_higher_radio_mentions_parent(self):
841
set_derived_series_ui_feature_flag(self)
842
parent_series = self.factory.makeDistroSeries(
843
name='lucid', displayname='Lucid')
844
derived_series = self.factory.makeDistroSeries(
845
name='derilucid', parent_series=parent_series)
846
self.factory.makeDistroSeriesDifference(
847
derived_series=derived_series,
848
source_package_name_str="my-src-package")
849
view = create_initialized_view(
851
'+localpackagediffs')
854
" Blacklisted packages with a higher version than in 'Lucid'"
855
radio_option_matches = soupmatchers.HTMLContains(
857
"radio displays parent's name", 'label',
860
self.assertThat(view.render(), radio_option_matches)
862
def _set_source_selection(self, series):
863
# Set up source package format selection so that copying will
864
# work with the default dsc_format used in
865
# makeSourcePackageRelease.
866
getUtility(ISourcePackageFormatSelectionSet).add(
867
series, SourcePackageFormat.FORMAT_1_0)
869
def test_batch_filtered(self):
870
# The name_filter parameter allows filtering of packages by name.
871
set_derived_series_ui_feature_flag(self)
872
derived_series = self.factory.makeDistroSeries(
873
name='derilucid', parent_series=self.factory.makeDistroSeries(
875
diff1 = self.factory.makeDistroSeriesDifference(
876
derived_series=derived_series,
877
source_package_name_str="my-src-package")
878
diff2 = self.factory.makeDistroSeriesDifference(
879
derived_series=derived_series,
880
source_package_name_str="my-second-src-package")
882
filtered_view = create_initialized_view(
884
'+localpackagediffs',
885
query_string='field.name_filter=my-src-package')
886
unfiltered_view = create_initialized_view(
888
'+localpackagediffs')
890
self.assertContentEqual(
891
[diff1], filtered_view.cached_differences.batch)
892
self.assertContentEqual(
893
[diff2, diff1], unfiltered_view.cached_differences.batch)
895
def test_batch_non_blacklisted(self):
896
# The default filter is all non blacklisted differences.
897
set_derived_series_ui_feature_flag(self)
898
derived_series = self.factory.makeDistroSeries(
899
name='derilucid', parent_series=self.factory.makeDistroSeries(
901
diff1 = self.factory.makeDistroSeriesDifference(
902
derived_series=derived_series,
903
source_package_name_str="my-src-package")
904
diff2 = self.factory.makeDistroSeriesDifference(
905
derived_series=derived_series,
906
source_package_name_str="my-second-src-package")
907
self.factory.makeDistroSeriesDifference(
908
derived_series=derived_series,
909
status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
911
filtered_view = create_initialized_view(
913
'+localpackagediffs',
914
query_string='field.package_type=%s' % NON_BLACKLISTED)
915
filtered_view2 = create_initialized_view(
917
'+localpackagediffs')
919
self.assertContentEqual(
920
[diff2, diff1], filtered_view.cached_differences.batch)
921
self.assertContentEqual(
922
[diff2, diff1], filtered_view2.cached_differences.batch)
924
def test_batch_differences_packages(self):
925
# field.package_type parameter allows to list only
926
# blacklisted differences.
927
set_derived_series_ui_feature_flag(self)
928
derived_series = self.factory.makeDistroSeries(
929
name='derilucid', parent_series=self.factory.makeDistroSeries(
931
blacklisted_diff = self.factory.makeDistroSeriesDifference(
932
derived_series=derived_series,
933
status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
935
blacklisted_view = create_initialized_view(
937
'+localpackagediffs',
938
query_string='field.package_type=%s' % BLACKLISTED)
939
unblacklisted_view = create_initialized_view(
941
'+localpackagediffs')
943
self.assertContentEqual(
944
[blacklisted_diff], blacklisted_view.cached_differences.batch)
945
self.assertContentEqual(
946
[], unblacklisted_view.cached_differences.batch)
948
def test_batch_blacklisted_differences_with_higher_version(self):
949
# field.package_type parameter allows to list only
950
# blacklisted differences with a child's version higher than parent's.
951
set_derived_series_ui_feature_flag(self)
952
derived_series = self.factory.makeDistroSeries(
953
name='derilucid', parent_series=self.factory.makeDistroSeries(
955
blacklisted_diff_higher = self.factory.makeDistroSeriesDifference(
956
derived_series=derived_series,
957
status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
958
versions={'base': '1.1', 'parent': '1.3', 'derived': '1.10'})
959
self.factory.makeDistroSeriesDifference(
960
derived_series=derived_series,
961
status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT,
962
versions={'base': '1.1', 'parent': '1.12', 'derived': '1.10'})
964
blacklisted_view = create_initialized_view(
966
'+localpackagediffs',
967
query_string='field.package_type=%s' % HIGHER_VERSION_THAN_PARENT)
968
unblacklisted_view = create_initialized_view(
970
'+localpackagediffs')
972
self.assertContentEqual(
973
[blacklisted_diff_higher],
974
blacklisted_view.cached_differences.batch)
975
self.assertContentEqual(
976
[], unblacklisted_view.cached_differences.batch)
978
def test_batch_resolved_differences(self):
979
# Test that we can search for differences that we marked
981
set_derived_series_ui_feature_flag(self)
982
derived_series = self.factory.makeDistroSeries(
983
name='derilucid', parent_series=self.factory.makeDistroSeries(
986
self.factory.makeDistroSeriesDifference(
987
derived_series=derived_series,
988
source_package_name_str="my-src-package")
989
self.factory.makeDistroSeriesDifference(
990
derived_series=derived_series,
991
source_package_name_str="my-second-src-package")
992
resolved_diff = self.factory.makeDistroSeriesDifference(
993
derived_series=derived_series,
994
status=DistroSeriesDifferenceStatus.RESOLVED)
996
filtered_view = create_initialized_view(
998
'+localpackagediffs',
999
query_string='field.package_type=%s' % RESOLVED)
1001
self.assertContentEqual(
1002
[resolved_diff], filtered_view.cached_differences.batch)
1004
def test_canPerformSync_non_editor(self):
1005
# Non-editors do not see options to sync.
1006
derived_series = self.factory.makeDistroSeries(
1007
name='derilucid', parent_series=self.factory.makeDistroSeries(
1009
self.factory.makeDistroSeriesDifference(
1010
derived_series=derived_series)
1012
set_derived_series_ui_feature_flag(self)
1013
with person_logged_in(self.factory.makePerson()):
1014
view = create_initialized_view(
1015
derived_series, '+localpackagediffs')
1017
self.assertFalse(view.canPerformSync())
1019
def test_canPerformSync_editor(self):
1020
# Editors are presented with options to perform syncs.
1021
derived_series = self.factory.makeDistroSeries(
1022
name='derilucid', parent_series=self.factory.makeDistroSeries(
1024
self.factory.makeDistroSeriesDifference(
1025
derived_series=derived_series)
1027
set_derived_series_ui_feature_flag(self)
1028
with person_logged_in(derived_series.owner):
1029
view = create_initialized_view(
1030
derived_series, '+localpackagediffs')
1031
self.assertTrue(view.canPerformSync())
1033
def test_sync_notification_on_success(self):
1034
# Syncing one or more diffs results in a stub notification.
1037
'derived': '1.0derived1',
1040
parent_series = self.factory.makeDistroSeries(name='warty')
1041
derived_distro = self.factory.makeDistribution(name='deribuntu')
1042
derived_series = self.factory.makeDistroSeries(
1043
distribution=derived_distro, name='derilucid',
1044
parent_series=parent_series)
1045
self._set_source_selection(derived_series)
1046
difference = self.factory.makeDistroSeriesDifference(
1047
source_package_name_str='my-src-name',
1048
derived_series=derived_series, versions=versions)
1050
# The inital state is that 1.0-1 is not in the derived series.
1051
pubs = derived_series.main_archive.getPublishedSources(
1052
name='my-src-name', version=versions['parent'],
1053
distroseries=derived_series).any()
1054
self.assertIs(None, pubs)
1056
# Now, sync the source from the parent using the form.
1057
set_derived_series_ui_feature_flag(self)
1058
with person_logged_in(derived_series.owner):
1059
view = create_initialized_view(
1060
derived_series, '+localpackagediffs',
1061
method='POST', form={
1062
'field.selected_differences': [
1063
difference.source_package_name.name,
1065
'field.actions.sync': 'Sync',
1068
# The parent's version should now be in the derived series:
1069
pub = derived_series.main_archive.getPublishedSources(
1070
name='my-src-name', version=versions['parent'],
1071
distroseries=derived_series).one()
1072
self.assertIsNot(None, pub)
1073
self.assertEqual(versions['parent'], pub.sourcepackagerelease.version)
1075
# The view should show no errors, and the notification should
1076
# confirm the sync worked.
1077
self.assertEqual(0, len(view.errors))
1078
notifications = view.request.response.notifications
1079
self.assertEqual(1, len(notifications))
1081
'<p>Packages copied to '
1082
'<a href="http://launchpad.dev/deribuntu/derilucid"'
1083
'>Derilucid</a>:</p>\n<ul>\n<li>my-src-name 1.0-1 in '
1084
'derilucid</li>\n</ul>',
1085
notifications[0].message)
1086
# 302 is a redirect back to the same page.
1087
self.assertEqual(302, view.request.response.getStatus())
1089
def test_sync_error_nothing_selected(self):
1090
# An error is raised when a sync is requested without any selection.
1091
derived_series = self.factory.makeDistroSeries(
1092
name='derilucid', parent_series=self.factory.makeDistroSeries(
1094
self.factory.makeDistroSeriesDifference(
1095
source_package_name_str='my-src-name',
1096
derived_series=derived_series)
1098
set_derived_series_ui_feature_flag(self)
1099
with person_logged_in(derived_series.owner):
1100
view = create_initialized_view(
1101
derived_series, '+localpackagediffs',
1102
method='POST', form={
1103
'field.selected_differences': [],
1104
'field.actions.sync': 'Sync',
1107
self.assertEqual(1, len(view.errors))
1109
'No differences selected.', view.errors[0])
1111
def test_sync_error_invalid_selection(self):
1112
# An error is raised when an invalid difference is selected.
1113
derived_series = self.factory.makeDistroSeries(
1114
name='derilucid', parent_series=self.factory.makeDistroSeries(
1116
self._set_source_selection(derived_series)
1117
self.factory.makeDistroSeriesDifference(
1118
source_package_name_str='my-src-name',
1119
derived_series=derived_series)
1121
set_derived_series_ui_feature_flag(self)
1122
with person_logged_in(derived_series.owner):
1123
view = create_initialized_view(
1124
derived_series, '+localpackagediffs',
1125
method='POST', form={
1126
'field.selected_differences': ['some-other-name'],
1127
'field.actions.sync': 'Sync',
1130
self.assertEqual(2, len(view.errors))
1132
'No differences selected.', view.errors[0])
1134
'Invalid value', view.errors[1].error_name)
1137
class TestDistroSeriesNeedsPackagesView(TestCaseWithFactory):
1138
"""Test the distroseries +needs-packaging view."""
1140
layer = LaunchpadZopelessLayer
1142
def test_cached_unlinked_packages(self):
1143
ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
1144
distroseries = self.factory.makeDistroSeries(distribution=ubuntu)
1145
view = create_initialized_view(distroseries, '+needs-packaging')
1147
IResultSet.providedBy(
1148
view.cached_unlinked_packages.currentBatch().list),
1149
'%s should batch IResultSet so that slicing will limit the '
1150
'query' % view.cached_unlinked_packages.currentBatch().list)
1153
class DistroSeriesMissingPackageDiffsTestCase(TestCaseWithFactory):
1154
"""Test the distroseries +missingpackages view."""
1156
layer = LaunchpadZopelessLayer
1158
def test_missingpackages_differences(self):
1159
# The view fetches the differences with type
1160
# MISSING_FROM_DERIVED_SERIES.
1161
derived_series = self.factory.makeDistroSeries(
1162
parent_series=self.factory.makeDistroSeries())
1164
missing_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
1165
# Missing blacklisted diff.
1166
self.factory.makeDistroSeriesDifference(
1167
difference_type=missing_type,
1168
derived_series=derived_series,
1169
status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
1171
missing_diff = self.factory.makeDistroSeriesDifference(
1172
difference_type=missing_type,
1173
derived_series=derived_series,
1174
status=DistroSeriesDifferenceStatus.NEEDS_ATTENTION)
1176
view = create_initialized_view(
1177
derived_series, '+missingpackages')
1179
self.assertContentEqual(
1180
[missing_diff], view.cached_differences.batch)
1182
def test_missingpackages_differences_empty(self):
1183
# The view is empty if there is no differences with type
1184
# MISSING_FROM_DERIVED_SERIES.
1185
derived_series = self.factory.makeDistroSeries(
1186
parent_series=self.factory.makeDistroSeries())
1188
not_missing_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
1191
self.factory.makeDistroSeriesDifference(
1192
difference_type=not_missing_type,
1193
derived_series=derived_series,
1194
status=DistroSeriesDifferenceStatus.NEEDS_ATTENTION)
1196
view = create_initialized_view(
1197
derived_series, '+missingpackages')
1199
self.assertContentEqual(
1200
[], view.cached_differences.batch)
1203
class DistroSeriesMissingPackagesPageTestCase(DistroSeriesDifferenceMixin,
1204
TestCaseWithFactory):
1205
"""Test the distroseries +missingpackages page."""
1207
layer = DatabaseFunctionalLayer
1210
super(DistroSeriesMissingPackagesPageTestCase,
1211
self).setUp('foo.bar@canonical.com')
1212
set_derived_series_ui_feature_flag(self)
1213
self.simple_user = self.factory.makePerson()
1215
def test_parent_packagesets_missingpackages(self):
1216
# +missingpackages displays the packagesets in the parent.
1217
missing_type = DistroSeriesDifferenceType.MISSING_FROM_DERIVED_SERIES
1218
self.ds_diff = self.factory.makeDistroSeriesDifference(
1219
difference_type=missing_type)
1221
with celebrity_logged_in('admin'):
1222
ps = self.factory.makePackageset(
1223
packages=[self.ds_diff.source_package_name],
1224
distroseries=self.ds_diff.derived_series.parent_series)
1226
with person_logged_in(self.simple_user):
1227
view = create_initialized_view(
1228
self.ds_diff.derived_series,
1230
principal=self.simple_user)
1233
packageset_text = re.compile('\s*' + ps.name)
1234
self._test_packagesets(
1235
html, packageset_text, 'parent-packagesets', 'Parent packagesets')
1238
class DistroSerieUniquePackageDiffsTestCase(TestCaseWithFactory):
1239
"""Test the distroseries +uniquepackages view."""
1241
layer = LaunchpadZopelessLayer
1243
def test_uniquepackages_differences(self):
1244
# The view fetches the differences with type
1245
# UNIQUE_TO_DERIVED_SERIES.
1246
derived_series = self.factory.makeDistroSeries(
1247
name='derilucid', parent_series=self.factory.makeDistroSeries(
1250
missing_type = DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES
1251
# Missing blacklisted diff.
1252
self.factory.makeDistroSeriesDifference(
1253
difference_type=missing_type,
1254
derived_series=derived_series,
1255
status=DistroSeriesDifferenceStatus.BLACKLISTED_CURRENT)
1257
missing_diff = self.factory.makeDistroSeriesDifference(
1258
difference_type=missing_type,
1259
derived_series=derived_series,
1260
status=DistroSeriesDifferenceStatus.NEEDS_ATTENTION)
1262
view = create_initialized_view(
1263
derived_series, '+uniquepackages')
1265
self.assertContentEqual(
1266
[missing_diff], view.cached_differences.batch)
1268
def test_uniquepackages_differences_empty(self):
1269
# The view is empty if there is no differences with type
1270
# UNIQUE_TO_DERIVED_SERIES.
1271
derived_series = self.factory.makeDistroSeries(
1272
parent_series=self.factory.makeDistroSeries())
1274
not_missing_type = DistroSeriesDifferenceType.DIFFERENT_VERSIONS
1277
self.factory.makeDistroSeriesDifference(
1278
difference_type=not_missing_type,
1279
derived_series=derived_series,
1280
status=DistroSeriesDifferenceStatus.NEEDS_ATTENTION)
1282
view = create_initialized_view(
1283
derived_series, '+uniquepackages')
1285
self.assertContentEqual(
1286
[], view.cached_differences.batch)
1289
class DistroSeriesUniquePackagesPageTestCase(DistroSeriesDifferenceMixin,
1290
TestCaseWithFactory):
1291
"""Test the distroseries +uniquepackages page."""
1293
layer = DatabaseFunctionalLayer
1296
super(DistroSeriesUniquePackagesPageTestCase,
1297
self).setUp('foo.bar@canonical.com')
1298
set_derived_series_ui_feature_flag(self)
1299
self.simple_user = self.factory.makePerson()
1301
def test_packagesets_uniquepackages(self):
1302
# +uniquepackages displays the packagesets in the parent.
1303
missing_type = DistroSeriesDifferenceType.UNIQUE_TO_DERIVED_SERIES
1304
self.ds_diff = self.factory.makeDistroSeriesDifference(
1305
difference_type=missing_type)
1307
with celebrity_logged_in('admin'):
1308
ps = self.factory.makePackageset(
1309
packages=[self.ds_diff.source_package_name],
1310
distroseries=self.ds_diff.derived_series)
1312
with person_logged_in(self.simple_user):
1313
view = create_initialized_view(
1314
self.ds_diff.derived_series,
1316
principal=self.simple_user)
1319
packageset_text = re.compile('\s*' + ps.name)
1320
self._test_packagesets(
1321
html, packageset_text, 'packagesets', 'Packagesets')