Merge lp:~allenap/launchpad/initseries-for-one-night-only-bug-793620 into lp:launchpad
- initseries-for-one-night-only-bug-793620
- Merge into devel
Status: | Merged |
---|---|
Approved by: | Julian Edwards |
Approved revision: | no longer in the source branch. |
Merged at revision: | 13200 |
Proposed branch: | lp:~allenap/launchpad/initseries-for-one-night-only-bug-793620 |
Merge into: | lp:launchpad |
Diff against target: |
587 lines (+199/-52) 11 files modified
lib/lp/registry/browser/distroseries.py (+23/-0) lib/lp/registry/browser/tests/test_distroseries.py (+52/-16) lib/lp/registry/interfaces/distroseries.py (+14/-11) lib/lp/registry/model/distroseries.py (+11/-7) lib/lp/registry/templates/distroseries-index.pt (+2/-1) lib/lp/registry/templates/distroseries-initialize.pt (+19/-3) lib/lp/registry/templates/distroseries-portlet-derivation.pt (+2/-2) lib/lp/registry/tests/test_distroseries.py (+17/-8) lib/lp/testing/__init__.py (+1/-1) lib/lp/testing/matchers.py (+24/-3) lib/lp/testing/tests/test_matchers.py (+34/-0) |
To merge this branch: | bzr merge lp:~allenap/launchpad/initseries-for-one-night-only-bug-793620 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Julian Edwards (community) | Approve | ||
Steve Kowalik (community) | code | Needs Fixing | |
Review via email: mp+63756@code.launchpad.net |
Commit message
[r=julian-edwards][bug=793620] Show a message on DistroSeries:
Description of the change
Show a message on +initseries, and prevent attempts to use the page,
when the distroseries is in the process of being derived or when the
distroseries has already been derived.
Gavin Panella (allenap) wrote : | # |
Thanks for the review!
> I think you're conflating things were they don't need to be. For
> +initseries, you only really care if the distroseries has been
> initialised, not if it is a derived series.
I have completely lost track of what this all means.
DistroSeries.
are, iirc, created by InitializeDistr
are a marker that the distroseries has been initialized, that it is
now a derived distroseries. If initializing and deriving are very
different things that I'm conflating then I pity the users!
> The with featureflag(): section reads as a little messy -- I thought
> we had FeatureFixture for that which makes it easier?
I cargocult-n-pasted that. I've changed it to use FeatureFixture().
> Thank you for cleaning up the imports, but I don't think you can import DSDJ
> from lp.soyuz.model? Or if you can, why are you importing the whole module?
I didn't clean up the imports, utilities/
that (by way of utilities/
machine overlord has made its decision.
Gavin Panella (allenap) wrote : | # |
Okay, Julian explained it to me. The follow-up is quite large, so I'll
explain what I've done:
- Introduced a new matched, EqualsIgnoringW
other ways to do this, but this seems to be a common test that was
worth cementing. I suppose DocTestMatches is the closest thing to
it, but this is simpler to grok.
- Removes DistroSeries.
hits the database so would be better as a method...
- Introduce DistroSeries.
former replaces .is_initialising and the latter reports if there are
any published sources in the series' main archive.
- These are then used in the +initseries template.
- I also fixed some lint and imports.
1 | === modified file 'lib/lp/registry/browser/distroseries.py' | |||
2 | --- lib/lp/registry/browser/distroseries.py 2011-06-07 19:49:53 +0000 | |||
3 | +++ lib/lp/registry/browser/distroseries.py 2011-06-09 12:04:01 +0000 | |||
4 | @@ -654,20 +654,20 @@ | |||
5 | 654 | def show_derivation_form(self): | 654 | def show_derivation_form(self): |
6 | 655 | return ( | 655 | return ( |
7 | 656 | self.is_derived_series_feature_enabled and | 656 | self.is_derived_series_feature_enabled and |
10 | 657 | not self.context.is_derived_series and | 657 | not self.context.isInitializing() and |
11 | 658 | not self.context.is_initialising) | 658 | not self.context.isInitialized()) |
12 | 659 | 659 | ||
13 | 660 | @property | 660 | @property |
15 | 661 | def show_already_derived_message(self): | 661 | def show_already_initialized_message(self): |
16 | 662 | return ( | 662 | return ( |
17 | 663 | self.is_derived_series_feature_enabled and | 663 | self.is_derived_series_feature_enabled and |
19 | 664 | self.context.is_derived_series) | 664 | self.context.isInitialized()) |
20 | 665 | 665 | ||
21 | 666 | @property | 666 | @property |
22 | 667 | def show_already_initializing_message(self): | 667 | def show_already_initializing_message(self): |
23 | 668 | return ( | 668 | return ( |
24 | 669 | self.is_derived_series_feature_enabled and | 669 | self.is_derived_series_feature_enabled and |
26 | 670 | self.context.is_initialising) | 670 | self.context.isInitializing()) |
27 | 671 | 671 | ||
28 | 672 | @property | 672 | @property |
29 | 673 | def next_url(self): | 673 | def next_url(self): |
30 | 674 | 674 | ||
31 | === modified file 'lib/lp/registry/browser/tests/test_distroseries.py' | |||
32 | --- lib/lp/registry/browser/tests/test_distroseries.py 2011-06-08 08:22:07 +0000 | |||
33 | +++ lib/lp/registry/browser/tests/test_distroseries.py 2011-06-09 12:05:09 +0000 | |||
34 | @@ -85,7 +85,10 @@ | |||
35 | 85 | with_celebrity_logged_in, | 85 | with_celebrity_logged_in, |
36 | 86 | ) | 86 | ) |
37 | 87 | from lp.testing.fakemethod import FakeMethod | 87 | from lp.testing.fakemethod import FakeMethod |
39 | 88 | from lp.testing.matchers import HasQueryCount | 88 | from lp.testing.matchers import ( |
40 | 89 | EqualsIgnoringWhitespace, | ||
41 | 90 | HasQueryCount, | ||
42 | 91 | ) | ||
43 | 89 | from lp.testing.views import create_initialized_view | 92 | from lp.testing.views import create_initialized_view |
44 | 90 | 93 | ||
45 | 91 | 94 | ||
46 | @@ -321,7 +324,7 @@ | |||
47 | 321 | view.request.features = get_relevant_feature_controller() | 324 | view.request.features = get_relevant_feature_controller() |
48 | 322 | html_content = view() | 325 | html_content = view() |
49 | 323 | 326 | ||
51 | 324 | self.assertTrue(derived_series.is_initialising) | 327 | self.assertTrue(derived_series.isInitializing()) |
52 | 325 | self.assertThat(html_content, portlet_display) | 328 | self.assertThat(html_content, portlet_display) |
53 | 326 | 329 | ||
54 | 327 | 330 | ||
55 | @@ -455,9 +458,10 @@ | |||
56 | 455 | 458 | ||
57 | 456 | def test_form_hidden_when_distroseries_is_initialized(self): | 459 | def test_form_hidden_when_distroseries_is_initialized(self): |
58 | 457 | # The form is hidden when the feature flag is set but the series has | 460 | # The form is hidden when the feature flag is set but the series has |
60 | 458 | # already been derived. | 461 | # already been initialized. |
61 | 459 | distroseries = self.factory.makeDistroSeries() | 462 | distroseries = self.factory.makeDistroSeries() |
63 | 460 | self.factory.makeDistroSeriesParent(derived_series=distroseries) | 463 | self.factory.makeSourcePackagePublishingHistory( |
64 | 464 | distroseries=distroseries, archive=distroseries.main_archive) | ||
65 | 461 | view = create_initialized_view(distroseries, "+initseries") | 465 | view = create_initialized_view(distroseries, "+initseries") |
66 | 462 | flags = {u"soyuz.derived_series_ui.enabled": u"true"} | 466 | flags = {u"soyuz.derived_series_ui.enabled": u"true"} |
67 | 463 | with FeatureFixture(flags): | 467 | with FeatureFixture(flags): |
68 | @@ -466,9 +470,10 @@ | |||
69 | 466 | [], root.cssselect("#initseries-form-container")) | 470 | [], root.cssselect("#initseries-form-container")) |
70 | 467 | # Instead an explanatory message is shown. | 471 | # Instead an explanatory message is shown. |
71 | 468 | [message] = root.cssselect("p.error.message") | 472 | [message] = root.cssselect("p.error.message") |
75 | 469 | self.assertEqual( | 473 | self.assertThat( |
76 | 470 | u"This series has already been derived.", | 474 | message.text.strip(), EqualsIgnoringWhitespace( |
77 | 471 | message.text.strip()) | 475 | u"This series already contains source packages " |
78 | 476 | u"and cannot be initialized again.")) | ||
79 | 472 | 477 | ||
80 | 473 | def test_form_hidden_when_distroseries_is_being_initialized(self): | 478 | def test_form_hidden_when_distroseries_is_being_initialized(self): |
81 | 474 | # The form is hidden when the feature flag is set but the series has | 479 | # The form is hidden when the feature flag is set but the series has |
82 | @@ -484,9 +489,10 @@ | |||
83 | 484 | [], root.cssselect("#initseries-form-container")) | 489 | [], root.cssselect("#initseries-form-container")) |
84 | 485 | # Instead an explanatory message is shown. | 490 | # Instead an explanatory message is shown. |
85 | 486 | [message] = root.cssselect("p.error.message") | 491 | [message] = root.cssselect("p.error.message") |
89 | 487 | self.assertEqual( | 492 | self.assertThat( |
90 | 488 | u"This series is already being initialized.", | 493 | message.text.strip(), |
91 | 489 | message.text.strip()) | 494 | EqualsIgnoringWhitespace( |
92 | 495 | u"This series is already being initialized.")) | ||
93 | 490 | 496 | ||
94 | 491 | 497 | ||
95 | 492 | class DistroSeriesDifferenceMixin: | 498 | class DistroSeriesDifferenceMixin: |
96 | 493 | 499 | ||
97 | === modified file 'lib/lp/registry/interfaces/distroseries.py' | |||
98 | --- lib/lp/registry/interfaces/distroseries.py 2011-05-31 15:45:19 +0000 | |||
99 | +++ lib/lp/registry/interfaces/distroseries.py 2011-06-09 12:07:12 +0000 | |||
100 | @@ -213,7 +213,7 @@ | |||
101 | 213 | description=_("The version string for this series."))) | 213 | description=_("The version string for this series."))) |
102 | 214 | distribution = exported( | 214 | distribution = exported( |
103 | 215 | Reference( | 215 | Reference( |
105 | 216 | Interface, # Really IDistribution, see circular import fix below. | 216 | Interface, # Really IDistribution, see circular import fix below. |
106 | 217 | title=_("Distribution"), required=True, | 217 | title=_("Distribution"), required=True, |
107 | 218 | description=_("The distribution for which this is a series."))) | 218 | description=_("The distribution for which this is a series."))) |
108 | 219 | distributionID = Attribute('The distribution ID.') | 219 | distributionID = Attribute('The distribution ID.') |
109 | @@ -239,16 +239,13 @@ | |||
110 | 239 | is_derived_series = Bool( | 239 | is_derived_series = Bool( |
111 | 240 | title=u'Is this series a derived series?', readonly=True, | 240 | title=u'Is this series a derived series?', readonly=True, |
112 | 241 | description=(u"Whether or not this series is a derived series.")) | 241 | description=(u"Whether or not this series is a derived series.")) |
113 | 242 | is_initialising = Bool( | ||
114 | 243 | title=u'Is this series initialising?', readonly=True, | ||
115 | 244 | description=(u"Whether or not this series is initialising.")) | ||
116 | 245 | datereleased = exported( | 242 | datereleased = exported( |
117 | 246 | Datetime(title=_("Date released"))) | 243 | Datetime(title=_("Date released"))) |
118 | 247 | previous_series = exported( | 244 | previous_series = exported( |
119 | 248 | ReferenceChoice( | 245 | ReferenceChoice( |
120 | 249 | title=_("Parent series"), | 246 | title=_("Parent series"), |
121 | 250 | description=_("The series from which this one was branched."), | 247 | description=_("The series from which this one was branched."), |
123 | 251 | required=True, schema=Interface, # Really IDistroSeries, see below | 248 | required=True, schema=Interface, # Really IDistroSeries, see below |
124 | 252 | vocabulary='DistroSeries'), | 249 | vocabulary='DistroSeries'), |
125 | 253 | ("devel", dict(exported_as="previous_series")), | 250 | ("devel", dict(exported_as="previous_series")), |
126 | 254 | ("1.0", dict(exported_as="parent_series")), | 251 | ("1.0", dict(exported_as="parent_series")), |
127 | @@ -370,7 +367,7 @@ | |||
128 | 370 | 367 | ||
129 | 371 | main_archive = exported( | 368 | main_archive = exported( |
130 | 372 | Reference( | 369 | Reference( |
132 | 373 | Interface, # Really IArchive, see below for circular import fix. | 370 | Interface, # Really IArchive, see below for circular import fix. |
133 | 374 | title=_('Distribution Main Archive'))) | 371 | title=_('Distribution Main Archive'))) |
134 | 375 | 372 | ||
135 | 376 | supported = exported( | 373 | supported = exported( |
136 | @@ -418,7 +415,7 @@ | |||
137 | 418 | architectures = exported( | 415 | architectures = exported( |
138 | 419 | CollectionField( | 416 | CollectionField( |
139 | 420 | title=_("All architectures in this series."), | 417 | title=_("All architectures in this series."), |
141 | 421 | value_type=Reference(schema=Interface), # IDistroArchSeries. | 418 | value_type=Reference(schema=Interface), # IDistroArchSeries. |
142 | 422 | readonly=True)) | 419 | readonly=True)) |
143 | 423 | 420 | ||
144 | 424 | enabled_architectures = Attribute( | 421 | enabled_architectures = Attribute( |
145 | @@ -848,18 +845,18 @@ | |||
146 | 848 | 845 | ||
147 | 849 | @operation_parameters( | 846 | @operation_parameters( |
148 | 850 | parent_series=Reference( | 847 | parent_series=Reference( |
150 | 851 | schema=Interface, # IDistroSeries | 848 | schema=Interface, # IDistroSeries |
151 | 852 | title=_("The parent series to consider."), | 849 | title=_("The parent series to consider."), |
152 | 853 | required=False), | 850 | required=False), |
153 | 854 | difference_type=Choice( | 851 | difference_type=Choice( |
155 | 855 | vocabulary=DBEnumeratedType, # DistroSeriesDifferenceType | 852 | vocabulary=DBEnumeratedType, # DistroSeriesDifferenceType |
156 | 856 | title=_("Only return differences of this type."), required=False), | 853 | title=_("Only return differences of this type."), required=False), |
157 | 857 | source_package_name_filter=TextLine( | 854 | source_package_name_filter=TextLine( |
158 | 858 | title=_("Only return differences for packages matching this " | 855 | title=_("Only return differences for packages matching this " |
159 | 859 | "name."), | 856 | "name."), |
160 | 860 | required=False), | 857 | required=False), |
161 | 861 | status=Choice( | 858 | status=Choice( |
163 | 862 | vocabulary=DBEnumeratedType, # DistroSeriesDifferenceStatus | 859 | vocabulary=DBEnumeratedType, # DistroSeriesDifferenceStatus |
164 | 863 | title=_("Only return differences of this status."), | 860 | title=_("Only return differences of this status."), |
165 | 864 | required=False), | 861 | required=False), |
166 | 865 | child_version_higher=Bool( | 862 | child_version_higher=Bool( |
167 | @@ -886,6 +883,12 @@ | |||
168 | 886 | child's version is higher than the parent's version. | 883 | child's version is higher than the parent's version. |
169 | 887 | """ | 884 | """ |
170 | 888 | 885 | ||
171 | 886 | def isInitializing(self): | ||
172 | 887 | """Is this series initializing?""" | ||
173 | 888 | |||
174 | 889 | def isInitialized(self): | ||
175 | 890 | """Has this series been initialized?""" | ||
176 | 891 | |||
177 | 889 | 892 | ||
178 | 890 | class IDistroSeriesEditRestricted(Interface): | 893 | class IDistroSeriesEditRestricted(Interface): |
179 | 891 | """IDistroSeries properties which require launchpad.Edit.""" | 894 | """IDistroSeries properties which require launchpad.Edit.""" |
180 | @@ -1032,7 +1035,7 @@ | |||
181 | 1032 | 1035 | ||
182 | 1033 | class DerivationError(Exception): | 1036 | class DerivationError(Exception): |
183 | 1034 | """Raised when there is a problem deriving a distroseries.""" | 1037 | """Raised when there is a problem deriving a distroseries.""" |
185 | 1035 | webservice_error(400) # Bad Request | 1038 | webservice_error(400) # Bad Request |
186 | 1036 | _message_prefix = "Error deriving distro series" | 1039 | _message_prefix = "Error deriving distro series" |
187 | 1037 | 1040 | ||
188 | 1038 | 1041 | ||
189 | 1039 | 1042 | ||
190 | === modified file 'lib/lp/registry/model/distroseries.py' | |||
191 | --- lib/lp/registry/model/distroseries.py 2011-05-31 15:45:19 +0000 | |||
192 | +++ lib/lp/registry/model/distroseries.py 2011-06-09 11:58:23 +0000 | |||
193 | @@ -801,13 +801,6 @@ | |||
194 | 801 | return not self.getParentSeries() == [] | 801 | return not self.getParentSeries() == [] |
195 | 802 | 802 | ||
196 | 803 | @property | 803 | @property |
197 | 804 | def is_initialising(self): | ||
198 | 805 | """See `IDistroSeries`.""" | ||
199 | 806 | return not getUtility( | ||
200 | 807 | IInitialiseDistroSeriesJobSource).getPendingJobsForDistroseries( | ||
201 | 808 | self).is_empty() | ||
202 | 809 | |||
203 | 810 | @property | ||
204 | 811 | def bugtargetname(self): | 804 | def bugtargetname(self): |
205 | 812 | """See IBugTarget.""" | 805 | """See IBugTarget.""" |
206 | 813 | # XXX mpt 2007-07-10 bugs 113258, 113262: | 806 | # XXX mpt 2007-07-10 bugs 113258, 113262: |
207 | @@ -2023,6 +2016,17 @@ | |||
208 | 2023 | status=status, | 2016 | status=status, |
209 | 2024 | child_version_higher=child_version_higher) | 2017 | child_version_higher=child_version_higher) |
210 | 2025 | 2018 | ||
211 | 2019 | def isInitializing(self): | ||
212 | 2020 | """See `IDistroSeries`.""" | ||
213 | 2021 | job_source = getUtility(IInitialiseDistroSeriesJobSource) | ||
214 | 2022 | pending_jobs = job_source.getPendingJobsForDistroseries(self) | ||
215 | 2023 | return not pending_jobs.is_empty() | ||
216 | 2024 | |||
217 | 2025 | def isInitialized(self): | ||
218 | 2026 | """See `IDistroSeries`.""" | ||
219 | 2027 | published = self.main_archive.getPublishedSources(distroseries=self) | ||
220 | 2028 | return not published.is_empty() | ||
221 | 2029 | |||
222 | 2026 | 2030 | ||
223 | 2027 | class DistroSeriesSet: | 2031 | class DistroSeriesSet: |
224 | 2028 | implements(IDistroSeriesSet) | 2032 | implements(IDistroSeriesSet) |
225 | 2029 | 2033 | ||
226 | === modified file 'lib/lp/registry/templates/distroseries-index.pt' | |||
227 | --- lib/lp/registry/templates/distroseries-index.pt 2011-05-24 10:08:33 +0000 | |||
228 | +++ lib/lp/registry/templates/distroseries-index.pt 2011-06-09 12:00:16 +0000 | |||
229 | @@ -68,7 +68,8 @@ | |||
230 | 68 | <tal:derivation | 68 | <tal:derivation |
231 | 69 | tal:condition="request/features/soyuz.derived_series_ui.enabled"> | 69 | tal:condition="request/features/soyuz.derived_series_ui.enabled"> |
232 | 70 | <div class="yui-u" | 70 | <div class="yui-u" |
234 | 71 | tal:condition="python:context.is_derived_series or context.is_initialising"> | 71 | tal:condition="python: context.is_derived_series or |
235 | 72 | context.isInitializing()"> | ||
236 | 72 | <div tal:replace="structure context/@@+portlet-derivation" /> | 73 | <div tal:replace="structure context/@@+portlet-derivation" /> |
237 | 73 | </div> | 74 | </div> |
238 | 74 | </tal:derivation> | 75 | </tal:derivation> |
239 | 75 | 76 | ||
240 | === modified file 'lib/lp/registry/templates/distroseries-initialize.pt' | |||
241 | --- lib/lp/registry/templates/distroseries-initialize.pt 2011-06-07 19:49:53 +0000 | |||
242 | +++ lib/lp/registry/templates/distroseries-initialize.pt 2011-06-08 12:57:58 +0000 | |||
243 | @@ -50,11 +50,12 @@ | |||
244 | 50 | </p> | 50 | </p> |
245 | 51 | </tal:disabled> | 51 | </tal:disabled> |
246 | 52 | 52 | ||
248 | 53 | <tal:already-derived condition="view/show_already_derived_message"> | 53 | <tal:already-initialized condition="view/show_already_initialized_message"> |
249 | 54 | <p class="error message"> | 54 | <p class="error message"> |
251 | 55 | This series has already been derived. | 55 | This series already contains source packages and cannot be |
252 | 56 | initialized again. | ||
253 | 56 | </p> | 57 | </p> |
255 | 57 | </tal:already-derived> | 58 | </tal:already-initialized> |
256 | 58 | 59 | ||
257 | 59 | <tal:already-initializing | 60 | <tal:already-initializing |
258 | 60 | condition="view/show_already_initializing_message"> | 61 | condition="view/show_already_initializing_message"> |
259 | 61 | 62 | ||
260 | === modified file 'lib/lp/registry/templates/distroseries-portlet-derivation.pt' | |||
261 | --- lib/lp/registry/templates/distroseries-portlet-derivation.pt 2011-05-31 11:55:38 +0000 | |||
262 | +++ lib/lp/registry/templates/distroseries-portlet-derivation.pt 2011-06-09 11:59:46 +0000 | |||
263 | @@ -5,7 +5,7 @@ | |||
264 | 5 | id="series-derivation" class="portlet" | 5 | id="series-derivation" class="portlet" |
265 | 6 | tal:define="overview_menu context/menu:overview"> | 6 | tal:define="overview_menu context/menu:overview"> |
266 | 7 | <tal:is_derived condition="context/is_derived_series"> | 7 | <tal:is_derived condition="context/is_derived_series"> |
268 | 8 | <tal:is_initialised condition="not: context/is_initialising"> | 8 | <tal:is_initialised condition="not: context/isInitializing()"> |
269 | 9 | <tal:one_parent condition="view/has_unique_parent"> | 9 | <tal:one_parent condition="view/has_unique_parent"> |
270 | 10 | <h2>Derived from <tal:name replace="view/unique_parent/displayname"/></h2> | 10 | <h2>Derived from <tal:name replace="view/unique_parent/displayname"/></h2> |
271 | 11 | </tal:one_parent> | 11 | </tal:one_parent> |
272 | @@ -61,7 +61,7 @@ | |||
273 | 61 | </tal:diffs> | 61 | </tal:diffs> |
274 | 62 | </tal:is_initialised> | 62 | </tal:is_initialised> |
275 | 63 | </tal:is_derived> | 63 | </tal:is_derived> |
277 | 64 | <tal:is_initialising condition="context/is_initialising"> | 64 | <tal:is_initialising condition="context/isInitializing"> |
278 | 65 | <h2>Series initialisation in progress</h2> | 65 | <h2>Series initialisation in progress</h2> |
279 | 66 | This series is initialising. | 66 | This series is initialising. |
280 | 67 | </tal:is_initialising> | 67 | </tal:is_initialising> |
281 | 68 | 68 | ||
282 | === modified file 'lib/lp/registry/tests/test_distroseries.py' | |||
283 | --- lib/lp/registry/tests/test_distroseries.py 2011-05-31 15:40:10 +0000 | |||
284 | +++ lib/lp/registry/tests/test_distroseries.py 2011-06-09 11:57:05 +0000 | |||
285 | @@ -222,22 +222,31 @@ | |||
286 | 222 | self.assertEquals(registrant, distroseries.registrant) | 222 | self.assertEquals(registrant, distroseries.registrant) |
287 | 223 | self.assertNotEqual(distroseries.registrant, distroseries.owner) | 223 | self.assertNotEqual(distroseries.registrant, distroseries.owner) |
288 | 224 | 224 | ||
292 | 225 | def test_is_initialising(self): | 225 | def test_isInitializing(self): |
293 | 226 | # The series is_initialising only if there is an initialisation | 226 | # The series method isInitializing() returns True only if there is an |
294 | 227 | # job with a pending status attached to this series. | 227 | # initialisation job with a pending status attached to this series. |
295 | 228 | distroseries = self.factory.makeDistroSeries() | 228 | distroseries = self.factory.makeDistroSeries() |
296 | 229 | parent_distroseries = self.factory.makeDistroSeries() | 229 | parent_distroseries = self.factory.makeDistroSeries() |
298 | 230 | self.assertEquals(False, distroseries.is_initialising) | 230 | self.assertFalse(distroseries.isInitializing()) |
299 | 231 | job_source = getUtility(IInitialiseDistroSeriesJobSource) | 231 | job_source = getUtility(IInitialiseDistroSeriesJobSource) |
300 | 232 | job = job_source.create(distroseries, [parent_distroseries.id]) | 232 | job = job_source.create(distroseries, [parent_distroseries.id]) |
302 | 233 | self.assertEquals(True, distroseries.is_initialising) | 233 | self.assertTrue(distroseries.isInitializing()) |
303 | 234 | job.start() | 234 | job.start() |
305 | 235 | self.assertEquals(True, distroseries.is_initialising) | 235 | self.assertTrue(distroseries.isInitializing()) |
306 | 236 | job.queue() | 236 | job.queue() |
308 | 237 | self.assertEquals(True, distroseries.is_initialising) | 237 | self.assertTrue(distroseries.isInitializing()) |
309 | 238 | job.start() | 238 | job.start() |
310 | 239 | job.complete() | 239 | job.complete() |
312 | 240 | self.assertEquals(False, distroseries.is_initialising) | 240 | self.assertFalse(distroseries.isInitializing()) |
313 | 241 | |||
314 | 242 | def test_isInitialized(self): | ||
315 | 243 | # The series method isInitialized() returns True once the series has | ||
316 | 244 | # been initialized. | ||
317 | 245 | distroseries = self.factory.makeDistroSeries() | ||
318 | 246 | self.assertFalse(distroseries.isInitialized()) | ||
319 | 247 | self.factory.makeSourcePackagePublishingHistory( | ||
320 | 248 | distroseries=distroseries, archive=distroseries.main_archive) | ||
321 | 249 | self.assertTrue(distroseries.isInitialized()) | ||
322 | 241 | 250 | ||
323 | 242 | 251 | ||
324 | 243 | class TestDistroSeriesPackaging(TestCaseWithFactory): | 252 | class TestDistroSeriesPackaging(TestCaseWithFactory): |
325 | 244 | 253 | ||
326 | === modified file 'lib/lp/testing/__init__.py' | |||
327 | --- lib/lp/testing/__init__.py 2011-05-19 15:15:16 +0000 | |||
328 | +++ lib/lp/testing/__init__.py 2011-06-08 13:19:10 +0000 | |||
329 | @@ -163,7 +163,6 @@ | |||
330 | 163 | ) | 163 | ) |
331 | 164 | from lp.testing.fixture import ZopeEventHandlerFixture | 164 | from lp.testing.fixture import ZopeEventHandlerFixture |
332 | 165 | from lp.testing.karma import KarmaRecorder | 165 | from lp.testing.karma import KarmaRecorder |
333 | 166 | from lp.testing.matchers import Provides | ||
334 | 167 | from lp.testing.windmill import ( | 166 | from lp.testing.windmill import ( |
335 | 168 | constants, | 167 | constants, |
336 | 169 | lpuser, | 168 | lpuser, |
337 | @@ -390,6 +389,7 @@ | |||
338 | 390 | 389 | ||
339 | 391 | def assertProvides(self, obj, interface): | 390 | def assertProvides(self, obj, interface): |
340 | 392 | """Assert 'obj' correctly provides 'interface'.""" | 391 | """Assert 'obj' correctly provides 'interface'.""" |
341 | 392 | from lp.testing.matchers import Provides | ||
342 | 393 | self.assertThat(obj, Provides(interface)) | 393 | self.assertThat(obj, Provides(interface)) |
343 | 394 | 394 | ||
344 | 395 | def assertClassImplements(self, cls, interface): | 395 | def assertClassImplements(self, cls, interface): |
345 | 396 | 396 | ||
346 | === modified file 'lib/lp/testing/matchers.py' | |||
347 | --- lib/lp/testing/matchers.py 2011-03-02 23:54:25 +0000 | |||
348 | +++ lib/lp/testing/matchers.py 2011-06-08 13:33:03 +0000 | |||
349 | @@ -8,6 +8,7 @@ | |||
350 | 8 | 'DocTestMatches', | 8 | 'DocTestMatches', |
351 | 9 | 'DoesNotCorrectlyProvide', | 9 | 'DoesNotCorrectlyProvide', |
352 | 10 | 'DoesNotProvide', | 10 | 'DoesNotProvide', |
353 | 11 | 'EqualsIgnoringWhitespace', | ||
354 | 11 | 'HasQueryCount', | 12 | 'HasQueryCount', |
355 | 12 | 'IsNotProxied', | 13 | 'IsNotProxied', |
356 | 13 | 'IsProxied', | 14 | 'IsProxied', |
357 | @@ -20,17 +21,17 @@ | |||
358 | 20 | ] | 21 | ] |
359 | 21 | 22 | ||
360 | 22 | from lazr.lifecycle.snapshot import Snapshot | 23 | from lazr.lifecycle.snapshot import Snapshot |
361 | 24 | from testtools import matchers | ||
362 | 23 | from testtools.content import Content | 25 | from testtools.content import Content |
363 | 24 | from testtools.content_type import UTF8_TEXT | 26 | from testtools.content_type import UTF8_TEXT |
364 | 25 | from testtools.matchers import ( | 27 | from testtools.matchers import ( |
365 | 28 | DocTestMatches as OriginalDocTestMatches, | ||
366 | 26 | Equals, | 29 | Equals, |
367 | 27 | DocTestMatches as OriginalDocTestMatches, | ||
368 | 28 | LessThan, | 30 | LessThan, |
369 | 29 | Matcher, | 31 | Matcher, |
370 | 30 | Mismatch, | 32 | Mismatch, |
371 | 31 | MismatchesAll, | 33 | MismatchesAll, |
372 | 32 | ) | 34 | ) |
373 | 33 | from testtools import matchers | ||
374 | 34 | from zope.interface.exceptions import ( | 35 | from zope.interface.exceptions import ( |
375 | 35 | BrokenImplementation, | 36 | BrokenImplementation, |
376 | 36 | BrokenMethodImplementation, | 37 | BrokenMethodImplementation, |
377 | @@ -44,6 +45,7 @@ | |||
378 | 44 | 45 | ||
379 | 45 | from canonical.launchpad.webapp import canonical_url | 46 | from canonical.launchpad.webapp import canonical_url |
380 | 46 | from canonical.launchpad.webapp.batching import BatchNavigator | 47 | from canonical.launchpad.webapp.batching import BatchNavigator |
381 | 48 | from lp.testing import normalize_whitespace | ||
382 | 47 | from lp.testing._login import person_logged_in | 49 | from lp.testing._login import person_logged_in |
383 | 48 | from lp.testing._webservice import QueryCollector | 50 | from lp.testing._webservice import QueryCollector |
384 | 49 | 51 | ||
385 | @@ -290,7 +292,8 @@ | |||
386 | 290 | 292 | ||
387 | 291 | :param singular: The singular header the batch should be using. | 293 | :param singular: The singular header the batch should be using. |
388 | 292 | :param plural: The plural header the batch should be using. | 294 | :param plural: The plural header the batch should be using. |
390 | 293 | :param batch_size: The batch size that should be configured by default. | 295 | :param batch_size: The batch size that should be configured by |
391 | 296 | default. | ||
392 | 294 | """ | 297 | """ |
393 | 295 | self._single = Equals(singular) | 298 | self._single = Equals(singular) |
394 | 296 | self._plural = Equals(plural) | 299 | self._plural = Equals(plural) |
395 | @@ -438,3 +441,21 @@ | |||
396 | 438 | text = widget.findAll(attrs={'class': 'yui3-activator-data-box'})[0] | 441 | text = widget.findAll(attrs={'class': 'yui3-activator-data-box'})[0] |
397 | 439 | text_matcher = DocTestMatches(extract_text(text)) | 442 | text_matcher = DocTestMatches(extract_text(text)) |
398 | 440 | return text_matcher.match(matchee) | 443 | return text_matcher.match(matchee) |
399 | 444 | |||
400 | 445 | |||
401 | 446 | class EqualsIgnoringWhitespace(Equals): | ||
402 | 447 | """Compare equality, ignoring whitespace in strings. | ||
403 | 448 | |||
404 | 449 | Whitespace in strings is normalized before comparison. All other objected | ||
405 | 450 | are compared as they come. | ||
406 | 451 | """ | ||
407 | 452 | |||
408 | 453 | def __init__(self, expected): | ||
409 | 454 | if isinstance(expected, (str, unicode)): | ||
410 | 455 | expected = normalize_whitespace(expected) | ||
411 | 456 | super(EqualsIgnoringWhitespace, self).__init__(expected) | ||
412 | 457 | |||
413 | 458 | def match(self, observed): | ||
414 | 459 | if isinstance(observed, (str, unicode)): | ||
415 | 460 | observed = normalize_whitespace(observed) | ||
416 | 461 | return super(EqualsIgnoringWhitespace, self).match(observed) | ||
417 | 441 | 462 | ||
418 | === modified file 'lib/lp/testing/tests/test_matchers.py' | |||
419 | --- lib/lp/testing/tests/test_matchers.py 2011-02-25 07:15:06 +0000 | |||
420 | +++ lib/lp/testing/tests/test_matchers.py 2011-06-08 13:31:16 +0000 | |||
421 | @@ -29,6 +29,7 @@ | |||
422 | 29 | DoesNotContain, | 29 | DoesNotContain, |
423 | 30 | DoesNotCorrectlyProvide, | 30 | DoesNotCorrectlyProvide, |
424 | 31 | DoesNotProvide, | 31 | DoesNotProvide, |
425 | 32 | EqualsIgnoringWhitespace, | ||
426 | 32 | HasQueryCount, | 33 | HasQueryCount, |
427 | 33 | IsNotProxied, | 34 | IsNotProxied, |
428 | 34 | IsProxied, | 35 | IsProxied, |
429 | @@ -274,3 +275,36 @@ | |||
430 | 274 | matcher = Contains("bar") | 275 | matcher = Contains("bar") |
431 | 275 | mismatch = matcher.match("foo") | 276 | mismatch = matcher.match("foo") |
432 | 276 | self.assertEqual("bar", mismatch.expected) | 277 | self.assertEqual("bar", mismatch.expected) |
433 | 278 | |||
434 | 279 | |||
435 | 280 | class EqualsIgnoringWhitespaceTests(TestCase): | ||
436 | 281 | |||
437 | 282 | def test_str(self): | ||
438 | 283 | matcher = EqualsIgnoringWhitespace("abc") | ||
439 | 284 | self.assertEqual("EqualsIgnoringWhitespace('abc')", str(matcher)) | ||
440 | 285 | |||
441 | 286 | def test_match_str(self): | ||
442 | 287 | matcher = EqualsIgnoringWhitespace("one \t two \n three") | ||
443 | 288 | self.assertIs(None, matcher.match(" one \r two three ")) | ||
444 | 289 | |||
445 | 290 | def test_mismatch_str(self): | ||
446 | 291 | matcher = EqualsIgnoringWhitespace("one \t two \n three") | ||
447 | 292 | mismatch = matcher.match(" one \r three ") | ||
448 | 293 | self.assertEqual( | ||
449 | 294 | "'one two three' != 'one three'", | ||
450 | 295 | mismatch.describe()) | ||
451 | 296 | |||
452 | 297 | def test_match_unicode(self): | ||
453 | 298 | matcher = EqualsIgnoringWhitespace(u"one \t two \n \u1234 ") | ||
454 | 299 | self.assertIs(None, matcher.match(u" one \r two \u1234 ")) | ||
455 | 300 | |||
456 | 301 | def test_mismatch_unicode(self): | ||
457 | 302 | matcher = EqualsIgnoringWhitespace(u"one \t two \n \u1234 ") | ||
458 | 303 | mismatch = matcher.match(u" one \r \u1234 ") | ||
459 | 304 | self.assertEqual( | ||
460 | 305 | u"u'one two \\u1234' != u'one \\u1234'", | ||
461 | 306 | mismatch.describe()) | ||
462 | 307 | |||
463 | 308 | def test_match_non_string(self): | ||
464 | 309 | matcher = EqualsIgnoringWhitespace(1234) | ||
465 | 310 | self.assertIs(None, matcher.match(1234)) |
Jonathan Lange (jml) wrote : | # |
`EqualsIgnoring
EqualsIgnorin
This wouldn't have the (str, unicode) type guard, but I'm not really sure that adds much anyway.
Gavin Panella (allenap) wrote : | # |
I hadn't seen AfterPreprocessing before, neat :)
However, AfterPreprocessing only processes the matchee, not the
matcher, so that lambda doesn't ignore whitespace in the same way as
EqualsIgnoringW
EqualsIgnoringW
Having said that, EqualsIgnoringW
whitespace, it just treats any run of whitespace as equal in matcher
and matchee. It's misnamed, but I don't think people are going to be
surprised by its behaviour anyway.
The reason for the (str, unicode) guard is so that the assertion
doesn't fail with an AttributeError (i.e. '...' object has no
attribute 'split') when the matcher or, more significantly, the
matchee are None or some other unexpected result.
Jonathan Lange (jml) wrote : | # |
On Thu, Jun 9, 2011 at 3:43 PM, Gavin Panella
<email address hidden> wrote:
> I hadn't seen AfterPreprocessing before, neat :)
>
> However, AfterPreprocessing only processes the matchee, not the
> matcher, so that lambda doesn't ignore whitespace in the same way as
> EqualsIgnoringW
> EqualsIgnoringW
>
Ahh, OK. I guess testtools could use a combinator like
AfterPreprocessing that maps both the matcher and the matchee.
Shouldn't have to write a class to do this.
jml
Preview Diff
1 | === modified file 'lib/lp/registry/browser/distroseries.py' | |||
2 | --- lib/lp/registry/browser/distroseries.py 2011-06-09 10:34:37 +0000 | |||
3 | +++ lib/lp/registry/browser/distroseries.py 2011-06-10 15:18:35 +0000 | |||
4 | @@ -647,6 +647,29 @@ | |||
5 | 647 | return getFeatureFlag("soyuz.derived_series_ui.enabled") is not None | 647 | return getFeatureFlag("soyuz.derived_series_ui.enabled") is not None |
6 | 648 | 648 | ||
7 | 649 | @property | 649 | @property |
8 | 650 | def show_derivation_not_yet_available(self): | ||
9 | 651 | return not self.is_derived_series_feature_enabled | ||
10 | 652 | |||
11 | 653 | @property | ||
12 | 654 | def show_derivation_form(self): | ||
13 | 655 | return ( | ||
14 | 656 | self.is_derived_series_feature_enabled and | ||
15 | 657 | not self.context.isInitializing() and | ||
16 | 658 | not self.context.isInitialized()) | ||
17 | 659 | |||
18 | 660 | @property | ||
19 | 661 | def show_already_initialized_message(self): | ||
20 | 662 | return ( | ||
21 | 663 | self.is_derived_series_feature_enabled and | ||
22 | 664 | self.context.isInitialized()) | ||
23 | 665 | |||
24 | 666 | @property | ||
25 | 667 | def show_already_initializing_message(self): | ||
26 | 668 | return ( | ||
27 | 669 | self.is_derived_series_feature_enabled and | ||
28 | 670 | self.context.isInitializing()) | ||
29 | 671 | |||
30 | 672 | @property | ||
31 | 650 | def rebuilding_allowed(self): | 673 | def rebuilding_allowed(self): |
32 | 651 | """If the distribution has got any initialized series already, | 674 | """If the distribution has got any initialized series already, |
33 | 652 | rebuilding is not allowed. | 675 | rebuilding is not allowed. |
34 | 653 | 676 | ||
35 | === modified file 'lib/lp/registry/browser/tests/test_distroseries.py' | |||
36 | --- lib/lp/registry/browser/tests/test_distroseries.py 2011-06-09 10:34:37 +0000 | |||
37 | +++ lib/lp/registry/browser/tests/test_distroseries.py 2011-06-10 15:18:35 +0000 | |||
38 | @@ -40,11 +40,11 @@ | |||
39 | 40 | LaunchpadFunctionalLayer, | 40 | LaunchpadFunctionalLayer, |
40 | 41 | LaunchpadZopelessLayer, | 41 | LaunchpadZopelessLayer, |
41 | 42 | ) | 42 | ) |
42 | 43 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities | ||
43 | 43 | from lp.archivepublisher.debversion import Version | 44 | from lp.archivepublisher.debversion import Version |
44 | 44 | from lp.app.interfaces.launchpad import ILaunchpadCelebrities | ||
45 | 45 | from lp.registry.browser.distroseries import ( | 45 | from lp.registry.browser.distroseries import ( |
46 | 46 | HIGHER_VERSION_THAN_PARENT, | ||
47 | 46 | IGNORED, | 47 | IGNORED, |
48 | 47 | HIGHER_VERSION_THAN_PARENT, | ||
49 | 48 | NON_IGNORED, | 48 | NON_IGNORED, |
50 | 49 | RESOLVED, | 49 | RESOLVED, |
51 | 50 | ) | 50 | ) |
52 | @@ -73,22 +73,23 @@ | |||
53 | 73 | from lp.soyuz.interfaces.sourcepackageformat import ( | 73 | from lp.soyuz.interfaces.sourcepackageformat import ( |
54 | 74 | ISourcePackageFormatSelectionSet, | 74 | ISourcePackageFormatSelectionSet, |
55 | 75 | ) | 75 | ) |
56 | 76 | from lp.soyuz.model import distroseriesdifferencejob | ||
57 | 76 | from lp.soyuz.model.archivepermission import ArchivePermission | 77 | from lp.soyuz.model.archivepermission import ArchivePermission |
58 | 77 | from lp.soyuz.model.packagecopyjob import PlainPackageCopyJob | 78 | from lp.soyuz.model.packagecopyjob import PlainPackageCopyJob |
59 | 78 | from lp.soyuz.model import distroseriesdifferencejob | ||
60 | 79 | from lp.testing import ( | 79 | from lp.testing import ( |
61 | 80 | anonymous_logged_in, | 80 | anonymous_logged_in, |
62 | 81 | celebrity_logged_in, | 81 | celebrity_logged_in, |
63 | 82 | feature_flags, | ||
64 | 83 | login_person, | 82 | login_person, |
65 | 84 | person_logged_in, | 83 | person_logged_in, |
66 | 85 | set_feature_flag, | ||
67 | 86 | StormStatementRecorder, | 84 | StormStatementRecorder, |
68 | 87 | TestCaseWithFactory, | 85 | TestCaseWithFactory, |
69 | 88 | with_celebrity_logged_in, | 86 | with_celebrity_logged_in, |
70 | 89 | ) | 87 | ) |
71 | 90 | from lp.testing.fakemethod import FakeMethod | 88 | from lp.testing.fakemethod import FakeMethod |
73 | 91 | from lp.testing.matchers import HasQueryCount | 89 | from lp.testing.matchers import ( |
74 | 90 | EqualsIgnoringWhitespace, | ||
75 | 91 | HasQueryCount, | ||
76 | 92 | ) | ||
77 | 92 | from lp.testing.views import create_initialized_view | 93 | from lp.testing.views import create_initialized_view |
78 | 93 | 94 | ||
79 | 94 | 95 | ||
80 | @@ -324,7 +325,7 @@ | |||
81 | 324 | view.request.features = get_relevant_feature_controller() | 325 | view.request.features = get_relevant_feature_controller() |
82 | 325 | html_content = view() | 326 | html_content = view() |
83 | 326 | 327 | ||
85 | 327 | self.assertTrue(derived_series.is_initialising) | 328 | self.assertTrue(derived_series.isInitializing()) |
86 | 328 | self.assertThat(html_content, portlet_display) | 329 | self.assertThat(html_content, portlet_display) |
87 | 329 | 330 | ||
88 | 330 | 331 | ||
89 | @@ -417,17 +418,17 @@ | |||
90 | 417 | # the soyuz.derived_series_ui.enabled flag. | 418 | # the soyuz.derived_series_ui.enabled flag. |
91 | 418 | distroseries = self.factory.makeDistroSeries() | 419 | distroseries = self.factory.makeDistroSeries() |
92 | 419 | view = create_initialized_view(distroseries, "+initseries") | 420 | view = create_initialized_view(distroseries, "+initseries") |
94 | 420 | with feature_flags(): | 421 | with FeatureFixture({}): |
95 | 421 | self.assertFalse(view.is_derived_series_feature_enabled) | 422 | self.assertFalse(view.is_derived_series_feature_enabled) |
98 | 422 | with feature_flags(): | 423 | flags = {u"soyuz.derived_series_ui.enabled": u"true"} |
99 | 423 | set_feature_flag(u"soyuz.derived_series_ui.enabled", u"true") | 424 | with FeatureFixture(flags): |
100 | 424 | self.assertTrue(view.is_derived_series_feature_enabled) | 425 | self.assertTrue(view.is_derived_series_feature_enabled) |
101 | 425 | 426 | ||
102 | 426 | def test_form_hidden_when_derived_series_feature_disabled(self): | 427 | def test_form_hidden_when_derived_series_feature_disabled(self): |
103 | 427 | # The form is hidden when the feature flag is not set. | 428 | # The form is hidden when the feature flag is not set. |
104 | 428 | distroseries = self.factory.makeDistroSeries() | 429 | distroseries = self.factory.makeDistroSeries() |
105 | 429 | view = create_initialized_view(distroseries, "+initseries") | 430 | view = create_initialized_view(distroseries, "+initseries") |
107 | 430 | with feature_flags(): | 431 | with FeatureFixture({}): |
108 | 431 | root = html.fromstring(view()) | 432 | root = html.fromstring(view()) |
109 | 432 | self.assertEqual( | 433 | self.assertEqual( |
110 | 433 | [], root.cssselect("#initseries-form-container")) | 434 | [], root.cssselect("#initseries-form-container")) |
111 | @@ -441,8 +442,8 @@ | |||
112 | 441 | # The form is shown when the feature flag is set. | 442 | # The form is shown when the feature flag is set. |
113 | 442 | distroseries = self.factory.makeDistroSeries() | 443 | distroseries = self.factory.makeDistroSeries() |
114 | 443 | view = create_initialized_view(distroseries, "+initseries") | 444 | view = create_initialized_view(distroseries, "+initseries") |
117 | 444 | with feature_flags(): | 445 | flags = {u"soyuz.derived_series_ui.enabled": u"true"} |
118 | 445 | set_feature_flag(u"soyuz.derived_series_ui.enabled", u"true") | 446 | with FeatureFixture(flags): |
119 | 446 | root = html.fromstring(view()) | 447 | root = html.fromstring(view()) |
120 | 447 | self.assertNotEqual( | 448 | self.assertNotEqual( |
121 | 448 | [], root.cssselect("#initseries-form-container")) | 449 | [], root.cssselect("#initseries-form-container")) |
122 | @@ -462,7 +463,6 @@ | |||
123 | 462 | self.factory.makeDistroSeries( | 463 | self.factory.makeDistroSeries( |
124 | 463 | distribution=distroseries.distribution) | 464 | distribution=distroseries.distribution) |
125 | 464 | view = create_initialized_view(distroseries, "+initseries") | 465 | view = create_initialized_view(distroseries, "+initseries") |
126 | 465 | |||
127 | 466 | self.assertTrue(view.rebuilding_allowed) | 466 | self.assertTrue(view.rebuilding_allowed) |
128 | 467 | 467 | ||
129 | 468 | def test_rebuilding_not_allowed(self): | 468 | def test_rebuilding_not_allowed(self): |
130 | @@ -473,9 +473,45 @@ | |||
131 | 473 | self.factory.makeSourcePackagePublishingHistory( | 473 | self.factory.makeSourcePackagePublishingHistory( |
132 | 474 | distroseries=another_distroseries) | 474 | distroseries=another_distroseries) |
133 | 475 | view = create_initialized_view(distroseries, "+initseries") | 475 | view = create_initialized_view(distroseries, "+initseries") |
134 | 476 | |||
135 | 477 | self.assertFalse(view.rebuilding_allowed) | 476 | self.assertFalse(view.rebuilding_allowed) |
136 | 478 | 477 | ||
137 | 478 | def test_form_hidden_when_distroseries_is_initialized(self): | ||
138 | 479 | # The form is hidden when the feature flag is set but the series has | ||
139 | 480 | # already been initialized. | ||
140 | 481 | distroseries = self.factory.makeDistroSeries() | ||
141 | 482 | self.factory.makeSourcePackagePublishingHistory( | ||
142 | 483 | distroseries=distroseries, archive=distroseries.main_archive) | ||
143 | 484 | view = create_initialized_view(distroseries, "+initseries") | ||
144 | 485 | flags = {u"soyuz.derived_series_ui.enabled": u"true"} | ||
145 | 486 | with FeatureFixture(flags): | ||
146 | 487 | root = html.fromstring(view()) | ||
147 | 488 | self.assertEqual( | ||
148 | 489 | [], root.cssselect("#initseries-form-container")) | ||
149 | 490 | # Instead an explanatory message is shown. | ||
150 | 491 | [message] = root.cssselect("p.error.message") | ||
151 | 492 | self.assertThat( | ||
152 | 493 | message.text, EqualsIgnoringWhitespace( | ||
153 | 494 | u"This series already contains source packages " | ||
154 | 495 | u"and cannot be initialized again.")) | ||
155 | 496 | |||
156 | 497 | def test_form_hidden_when_distroseries_is_being_initialized(self): | ||
157 | 498 | # The form is hidden when the feature flag is set but the series has | ||
158 | 499 | # already been derived. | ||
159 | 500 | distroseries = self.factory.makeDistroSeries() | ||
160 | 501 | getUtility(IInitialiseDistroSeriesJobSource).create( | ||
161 | 502 | distroseries, [self.factory.makeDistroSeries().id]) | ||
162 | 503 | view = create_initialized_view(distroseries, "+initseries") | ||
163 | 504 | flags = {u"soyuz.derived_series_ui.enabled": u"true"} | ||
164 | 505 | with FeatureFixture(flags): | ||
165 | 506 | root = html.fromstring(view()) | ||
166 | 507 | self.assertEqual( | ||
167 | 508 | [], root.cssselect("#initseries-form-container")) | ||
168 | 509 | # Instead an explanatory message is shown. | ||
169 | 510 | [message] = root.cssselect("p.error.message") | ||
170 | 511 | self.assertThat( | ||
171 | 512 | message.text, EqualsIgnoringWhitespace( | ||
172 | 513 | u"This series is already being initialized.")) | ||
173 | 514 | |||
174 | 479 | 515 | ||
175 | 480 | class DistroSeriesDifferenceMixin: | 516 | class DistroSeriesDifferenceMixin: |
176 | 481 | """A helper class for testing differences pages""" | 517 | """A helper class for testing differences pages""" |
177 | @@ -1701,7 +1737,7 @@ | |||
178 | 1701 | person, sp_name) | 1737 | person, sp_name) |
179 | 1702 | self._syncAndGetView( | 1738 | self._syncAndGetView( |
180 | 1703 | derived_series, person, [diff_id]) | 1739 | derived_series, person, [diff_id]) |
182 | 1704 | parent_pub = parent_series.main_archive.getPublishedSources( | 1740 | parent_series.main_archive.getPublishedSources( |
183 | 1705 | name='my-src-name', version=versions['parent'], | 1741 | name='my-src-name', version=versions['parent'], |
184 | 1706 | distroseries=parent_series).one() | 1742 | distroseries=parent_series).one() |
185 | 1707 | 1743 | ||
186 | 1708 | 1744 | ||
187 | === modified file 'lib/lp/registry/interfaces/distroseries.py' | |||
188 | --- lib/lp/registry/interfaces/distroseries.py 2011-06-08 11:06:04 +0000 | |||
189 | +++ lib/lp/registry/interfaces/distroseries.py 2011-06-10 15:18:35 +0000 | |||
190 | @@ -213,7 +213,7 @@ | |||
191 | 213 | description=_("The version string for this series."))) | 213 | description=_("The version string for this series."))) |
192 | 214 | distribution = exported( | 214 | distribution = exported( |
193 | 215 | Reference( | 215 | Reference( |
195 | 216 | Interface, # Really IDistribution, see circular import fix below. | 216 | Interface, # Really IDistribution, see circular import fix below. |
196 | 217 | title=_("Distribution"), required=True, | 217 | title=_("Distribution"), required=True, |
197 | 218 | description=_("The distribution for which this is a series."))) | 218 | description=_("The distribution for which this is a series."))) |
198 | 219 | distributionID = Attribute('The distribution ID.') | 219 | distributionID = Attribute('The distribution ID.') |
199 | @@ -239,16 +239,13 @@ | |||
200 | 239 | is_derived_series = Bool( | 239 | is_derived_series = Bool( |
201 | 240 | title=u'Is this series a derived series?', readonly=True, | 240 | title=u'Is this series a derived series?', readonly=True, |
202 | 241 | description=(u"Whether or not this series is a derived series.")) | 241 | description=(u"Whether or not this series is a derived series.")) |
203 | 242 | is_initialising = Bool( | ||
204 | 243 | title=u'Is this series initialising?', readonly=True, | ||
205 | 244 | description=(u"Whether or not this series is initialising.")) | ||
206 | 245 | datereleased = exported( | 242 | datereleased = exported( |
207 | 246 | Datetime(title=_("Date released"))) | 243 | Datetime(title=_("Date released"))) |
208 | 247 | previous_series = exported( | 244 | previous_series = exported( |
209 | 248 | ReferenceChoice( | 245 | ReferenceChoice( |
210 | 249 | title=_("Parent series"), | 246 | title=_("Parent series"), |
211 | 250 | description=_("The series from which this one was branched."), | 247 | description=_("The series from which this one was branched."), |
213 | 251 | required=True, schema=Interface, # Really IDistroSeries, see below | 248 | required=True, schema=Interface, # Really IDistroSeries |
214 | 252 | vocabulary='DistroSeries'), | 249 | vocabulary='DistroSeries'), |
215 | 253 | ("devel", dict(exported_as="previous_series")), | 250 | ("devel", dict(exported_as="previous_series")), |
216 | 254 | ("1.0", dict(exported_as="parent_series")), | 251 | ("1.0", dict(exported_as="parent_series")), |
217 | @@ -370,7 +367,7 @@ | |||
218 | 370 | 367 | ||
219 | 371 | main_archive = exported( | 368 | main_archive = exported( |
220 | 372 | Reference( | 369 | Reference( |
222 | 373 | Interface, # Really IArchive, see below for circular import fix. | 370 | Interface, # Really IArchive, see below for circular import fix. |
223 | 374 | title=_('Distribution Main Archive'))) | 371 | title=_('Distribution Main Archive'))) |
224 | 375 | 372 | ||
225 | 376 | supported = exported( | 373 | supported = exported( |
226 | @@ -418,7 +415,7 @@ | |||
227 | 418 | architectures = exported( | 415 | architectures = exported( |
228 | 419 | CollectionField( | 416 | CollectionField( |
229 | 420 | title=_("All architectures in this series."), | 417 | title=_("All architectures in this series."), |
231 | 421 | value_type=Reference(schema=Interface), # IDistroArchSeries. | 418 | value_type=Reference(schema=Interface), # IDistroArchSeries. |
232 | 422 | readonly=True)) | 419 | readonly=True)) |
233 | 423 | 420 | ||
234 | 424 | enabled_architectures = Attribute( | 421 | enabled_architectures = Attribute( |
235 | @@ -848,18 +845,18 @@ | |||
236 | 848 | 845 | ||
237 | 849 | @operation_parameters( | 846 | @operation_parameters( |
238 | 850 | parent_series=Reference( | 847 | parent_series=Reference( |
240 | 851 | schema=Interface, # IDistroSeries | 848 | schema=Interface, # IDistroSeries |
241 | 852 | title=_("The parent series to consider."), | 849 | title=_("The parent series to consider."), |
242 | 853 | required=False), | 850 | required=False), |
243 | 854 | difference_type=Choice( | 851 | difference_type=Choice( |
245 | 855 | vocabulary=DBEnumeratedType, # DistroSeriesDifferenceType | 852 | vocabulary=DBEnumeratedType, # DistroSeriesDifferenceType |
246 | 856 | title=_("Only return differences of this type."), required=False), | 853 | title=_("Only return differences of this type."), required=False), |
247 | 857 | source_package_name_filter=TextLine( | 854 | source_package_name_filter=TextLine( |
248 | 858 | title=_("Only return differences for packages matching this " | 855 | title=_("Only return differences for packages matching this " |
249 | 859 | "name."), | 856 | "name."), |
250 | 860 | required=False), | 857 | required=False), |
251 | 861 | status=Choice( | 858 | status=Choice( |
253 | 862 | vocabulary=DBEnumeratedType, # DistroSeriesDifferenceStatus | 859 | vocabulary=DBEnumeratedType, # DistroSeriesDifferenceStatus |
254 | 863 | title=_("Only return differences of this status."), | 860 | title=_("Only return differences of this status."), |
255 | 864 | required=False), | 861 | required=False), |
256 | 865 | child_version_higher=Bool( | 862 | child_version_higher=Bool( |
257 | @@ -886,6 +883,12 @@ | |||
258 | 886 | child's version is higher than the parent's version. | 883 | child's version is higher than the parent's version. |
259 | 887 | """ | 884 | """ |
260 | 888 | 885 | ||
261 | 886 | def isInitializing(): | ||
262 | 887 | """Is this series initializing?""" | ||
263 | 888 | |||
264 | 889 | def isInitialized(): | ||
265 | 890 | """Has this series been initialized?""" | ||
266 | 891 | |||
267 | 889 | 892 | ||
268 | 890 | class IDistroSeriesEditRestricted(Interface): | 893 | class IDistroSeriesEditRestricted(Interface): |
269 | 891 | """IDistroSeries properties which require launchpad.Edit.""" | 894 | """IDistroSeries properties which require launchpad.Edit.""" |
270 | @@ -1032,7 +1035,7 @@ | |||
271 | 1032 | 1035 | ||
272 | 1033 | class DerivationError(Exception): | 1036 | class DerivationError(Exception): |
273 | 1034 | """Raised when there is a problem deriving a distroseries.""" | 1037 | """Raised when there is a problem deriving a distroseries.""" |
275 | 1035 | webservice_error(400) # Bad Request | 1038 | webservice_error(400) # Bad Request |
276 | 1036 | _message_prefix = "Error deriving distro series" | 1039 | _message_prefix = "Error deriving distro series" |
277 | 1037 | 1040 | ||
278 | 1038 | 1041 | ||
279 | 1039 | 1042 | ||
280 | === modified file 'lib/lp/registry/model/distroseries.py' | |||
281 | --- lib/lp/registry/model/distroseries.py 2011-06-08 15:27:40 +0000 | |||
282 | +++ lib/lp/registry/model/distroseries.py 2011-06-10 15:18:35 +0000 | |||
283 | @@ -802,13 +802,6 @@ | |||
284 | 802 | return not self.getParentSeries() == [] | 802 | return not self.getParentSeries() == [] |
285 | 803 | 803 | ||
286 | 804 | @property | 804 | @property |
287 | 805 | def is_initialising(self): | ||
288 | 806 | """See `IDistroSeries`.""" | ||
289 | 807 | return not getUtility( | ||
290 | 808 | IInitialiseDistroSeriesJobSource).getPendingJobsForDistroseries( | ||
291 | 809 | self).is_empty() | ||
292 | 810 | |||
293 | 811 | @property | ||
294 | 812 | def bugtargetname(self): | 805 | def bugtargetname(self): |
295 | 813 | """See IBugTarget.""" | 806 | """See IBugTarget.""" |
296 | 814 | # XXX mpt 2007-07-10 bugs 113258, 113262: | 807 | # XXX mpt 2007-07-10 bugs 113258, 113262: |
297 | @@ -2037,6 +2030,17 @@ | |||
298 | 2037 | status=status, | 2030 | status=status, |
299 | 2038 | child_version_higher=child_version_higher) | 2031 | child_version_higher=child_version_higher) |
300 | 2039 | 2032 | ||
301 | 2033 | def isInitializing(self): | ||
302 | 2034 | """See `IDistroSeries`.""" | ||
303 | 2035 | job_source = getUtility(IInitialiseDistroSeriesJobSource) | ||
304 | 2036 | pending_jobs = job_source.getPendingJobsForDistroseries(self) | ||
305 | 2037 | return not pending_jobs.is_empty() | ||
306 | 2038 | |||
307 | 2039 | def isInitialized(self): | ||
308 | 2040 | """See `IDistroSeries`.""" | ||
309 | 2041 | published = self.main_archive.getPublishedSources(distroseries=self) | ||
310 | 2042 | return not published.is_empty() | ||
311 | 2043 | |||
312 | 2040 | 2044 | ||
313 | 2041 | class DistroSeriesSet: | 2045 | class DistroSeriesSet: |
314 | 2042 | implements(IDistroSeriesSet) | 2046 | implements(IDistroSeriesSet) |
315 | 2043 | 2047 | ||
316 | === modified file 'lib/lp/registry/templates/distroseries-index.pt' | |||
317 | --- lib/lp/registry/templates/distroseries-index.pt 2011-05-24 10:08:33 +0000 | |||
318 | +++ lib/lp/registry/templates/distroseries-index.pt 2011-06-10 15:18:35 +0000 | |||
319 | @@ -68,7 +68,8 @@ | |||
320 | 68 | <tal:derivation | 68 | <tal:derivation |
321 | 69 | tal:condition="request/features/soyuz.derived_series_ui.enabled"> | 69 | tal:condition="request/features/soyuz.derived_series_ui.enabled"> |
322 | 70 | <div class="yui-u" | 70 | <div class="yui-u" |
324 | 71 | tal:condition="python:context.is_derived_series or context.is_initialising"> | 71 | tal:condition="python: context.is_derived_series or |
325 | 72 | context.isInitializing()"> | ||
326 | 72 | <div tal:replace="structure context/@@+portlet-derivation" /> | 73 | <div tal:replace="structure context/@@+portlet-derivation" /> |
327 | 73 | </div> | 74 | </div> |
328 | 74 | </tal:derivation> | 75 | </tal:derivation> |
329 | 75 | 76 | ||
330 | === modified file 'lib/lp/registry/templates/distroseries-initialize.pt' | |||
331 | --- lib/lp/registry/templates/distroseries-initialize.pt 2011-06-06 09:32:14 +0000 | |||
332 | +++ lib/lp/registry/templates/distroseries-initialize.pt 2011-06-10 15:18:35 +0000 | |||
333 | @@ -12,13 +12,13 @@ | |||
334 | 12 | </metal:head-epilogue> | 12 | </metal:head-epilogue> |
335 | 13 | <body> | 13 | <body> |
336 | 14 | <div metal:fill-slot="main"> | 14 | <div metal:fill-slot="main"> |
338 | 15 | <tal:enabled condition="view/is_derived_series_feature_enabled"> | 15 | |
339 | 16 | <tal:enabled condition="view/show_derivation_form"> | ||
340 | 16 | <div class="top-portlet"> | 17 | <div class="top-portlet"> |
341 | 17 | This page allows you to initialize a distribution series. | 18 | This page allows you to initialize a distribution series. |
342 | 18 | <a href="/+help/init-series-title-help.html" | 19 | <a href="/+help/init-series-title-help.html" |
343 | 19 | target="help" class="sprite maybe"> | 20 | target="help" class="sprite maybe"> |
344 | 20 | <span class="invisible-link">Initialization help</span></a> | 21 | <span class="invisible-link">Initialization help</span></a> |
345 | 21 | |||
346 | 22 | </div> | 22 | </div> |
347 | 23 | <p class="error message javascript-disabled"> | 23 | <p class="error message javascript-disabled"> |
348 | 24 | Javascript is required to use this page. Please enable | 24 | Javascript is required to use this page. Please enable |
349 | @@ -42,7 +42,8 @@ | |||
350 | 42 | many thousands of packages is likely to take hours to complete. | 42 | many thousands of packages is likely to take hours to complete. |
351 | 43 | </p> | 43 | </p> |
352 | 44 | </tal:enabled> | 44 | </tal:enabled> |
354 | 45 | <tal:disabled condition="not:view/is_derived_series_feature_enabled"> | 45 | |
355 | 46 | <tal:disabled condition="view/show_derivation_not_yet_available"> | ||
356 | 46 | <p class="error message"> | 47 | <p class="error message"> |
357 | 47 | The Derivative Distributions feature is under development | 48 | The Derivative Distributions feature is under development |
358 | 48 | and is not yet generally available. You can read more about | 49 | and is not yet generally available. You can read more about |
359 | @@ -51,6 +52,21 @@ | |||
360 | 51 | page</a>. | 52 | page</a>. |
361 | 52 | </p> | 53 | </p> |
362 | 53 | </tal:disabled> | 54 | </tal:disabled> |
363 | 55 | |||
364 | 56 | <tal:already-initialized condition="view/show_already_initialized_message"> | ||
365 | 57 | <p class="error message"> | ||
366 | 58 | This series already contains source packages and cannot be | ||
367 | 59 | initialized again. | ||
368 | 60 | </p> | ||
369 | 61 | </tal:already-initialized> | ||
370 | 62 | |||
371 | 63 | <tal:already-initializing | ||
372 | 64 | condition="view/show_already_initializing_message"> | ||
373 | 65 | <p class="error message"> | ||
374 | 66 | This series is already being initialized. | ||
375 | 67 | </p> | ||
376 | 68 | </tal:already-initializing> | ||
377 | 69 | |||
378 | 54 | </div> | 70 | </div> |
379 | 55 | </body> | 71 | </body> |
380 | 56 | </html> | 72 | </html> |
381 | 57 | 73 | ||
382 | === modified file 'lib/lp/registry/templates/distroseries-portlet-derivation.pt' | |||
383 | --- lib/lp/registry/templates/distroseries-portlet-derivation.pt 2011-05-31 11:55:38 +0000 | |||
384 | +++ lib/lp/registry/templates/distroseries-portlet-derivation.pt 2011-06-10 15:18:35 +0000 | |||
385 | @@ -5,7 +5,7 @@ | |||
386 | 5 | id="series-derivation" class="portlet" | 5 | id="series-derivation" class="portlet" |
387 | 6 | tal:define="overview_menu context/menu:overview"> | 6 | tal:define="overview_menu context/menu:overview"> |
388 | 7 | <tal:is_derived condition="context/is_derived_series"> | 7 | <tal:is_derived condition="context/is_derived_series"> |
390 | 8 | <tal:is_initialised condition="not: context/is_initialising"> | 8 | <tal:is_initialised condition="not: context/isInitializing"> |
391 | 9 | <tal:one_parent condition="view/has_unique_parent"> | 9 | <tal:one_parent condition="view/has_unique_parent"> |
392 | 10 | <h2>Derived from <tal:name replace="view/unique_parent/displayname"/></h2> | 10 | <h2>Derived from <tal:name replace="view/unique_parent/displayname"/></h2> |
393 | 11 | </tal:one_parent> | 11 | </tal:one_parent> |
394 | @@ -61,7 +61,7 @@ | |||
395 | 61 | </tal:diffs> | 61 | </tal:diffs> |
396 | 62 | </tal:is_initialised> | 62 | </tal:is_initialised> |
397 | 63 | </tal:is_derived> | 63 | </tal:is_derived> |
399 | 64 | <tal:is_initialising condition="context/is_initialising"> | 64 | <tal:is_initialising condition="context/isInitializing"> |
400 | 65 | <h2>Series initialisation in progress</h2> | 65 | <h2>Series initialisation in progress</h2> |
401 | 66 | This series is initialising. | 66 | This series is initialising. |
402 | 67 | </tal:is_initialising> | 67 | </tal:is_initialising> |
403 | 68 | 68 | ||
404 | === modified file 'lib/lp/registry/tests/test_distroseries.py' | |||
405 | --- lib/lp/registry/tests/test_distroseries.py 2011-05-31 15:40:10 +0000 | |||
406 | +++ lib/lp/registry/tests/test_distroseries.py 2011-06-10 15:18:35 +0000 | |||
407 | @@ -222,22 +222,31 @@ | |||
408 | 222 | self.assertEquals(registrant, distroseries.registrant) | 222 | self.assertEquals(registrant, distroseries.registrant) |
409 | 223 | self.assertNotEqual(distroseries.registrant, distroseries.owner) | 223 | self.assertNotEqual(distroseries.registrant, distroseries.owner) |
410 | 224 | 224 | ||
414 | 225 | def test_is_initialising(self): | 225 | def test_isInitializing(self): |
415 | 226 | # The series is_initialising only if there is an initialisation | 226 | # The series method isInitializing() returns True only if there is an |
416 | 227 | # job with a pending status attached to this series. | 227 | # initialisation job with a pending status attached to this series. |
417 | 228 | distroseries = self.factory.makeDistroSeries() | 228 | distroseries = self.factory.makeDistroSeries() |
418 | 229 | parent_distroseries = self.factory.makeDistroSeries() | 229 | parent_distroseries = self.factory.makeDistroSeries() |
420 | 230 | self.assertEquals(False, distroseries.is_initialising) | 230 | self.assertFalse(distroseries.isInitializing()) |
421 | 231 | job_source = getUtility(IInitialiseDistroSeriesJobSource) | 231 | job_source = getUtility(IInitialiseDistroSeriesJobSource) |
422 | 232 | job = job_source.create(distroseries, [parent_distroseries.id]) | 232 | job = job_source.create(distroseries, [parent_distroseries.id]) |
424 | 233 | self.assertEquals(True, distroseries.is_initialising) | 233 | self.assertTrue(distroseries.isInitializing()) |
425 | 234 | job.start() | 234 | job.start() |
427 | 235 | self.assertEquals(True, distroseries.is_initialising) | 235 | self.assertTrue(distroseries.isInitializing()) |
428 | 236 | job.queue() | 236 | job.queue() |
430 | 237 | self.assertEquals(True, distroseries.is_initialising) | 237 | self.assertTrue(distroseries.isInitializing()) |
431 | 238 | job.start() | 238 | job.start() |
432 | 239 | job.complete() | 239 | job.complete() |
434 | 240 | self.assertEquals(False, distroseries.is_initialising) | 240 | self.assertFalse(distroseries.isInitializing()) |
435 | 241 | |||
436 | 242 | def test_isInitialized(self): | ||
437 | 243 | # The series method isInitialized() returns True once the series has | ||
438 | 244 | # been initialized. | ||
439 | 245 | distroseries = self.factory.makeDistroSeries() | ||
440 | 246 | self.assertFalse(distroseries.isInitialized()) | ||
441 | 247 | self.factory.makeSourcePackagePublishingHistory( | ||
442 | 248 | distroseries=distroseries, archive=distroseries.main_archive) | ||
443 | 249 | self.assertTrue(distroseries.isInitialized()) | ||
444 | 241 | 250 | ||
445 | 242 | 251 | ||
446 | 243 | class TestDistroSeriesPackaging(TestCaseWithFactory): | 252 | class TestDistroSeriesPackaging(TestCaseWithFactory): |
447 | 244 | 253 | ||
448 | === modified file 'lib/lp/testing/__init__.py' | |||
449 | --- lib/lp/testing/__init__.py 2011-05-19 15:15:16 +0000 | |||
450 | +++ lib/lp/testing/__init__.py 2011-06-10 15:18:35 +0000 | |||
451 | @@ -163,7 +163,6 @@ | |||
452 | 163 | ) | 163 | ) |
453 | 164 | from lp.testing.fixture import ZopeEventHandlerFixture | 164 | from lp.testing.fixture import ZopeEventHandlerFixture |
454 | 165 | from lp.testing.karma import KarmaRecorder | 165 | from lp.testing.karma import KarmaRecorder |
455 | 166 | from lp.testing.matchers import Provides | ||
456 | 167 | from lp.testing.windmill import ( | 166 | from lp.testing.windmill import ( |
457 | 168 | constants, | 167 | constants, |
458 | 169 | lpuser, | 168 | lpuser, |
459 | @@ -390,6 +389,7 @@ | |||
460 | 390 | 389 | ||
461 | 391 | def assertProvides(self, obj, interface): | 390 | def assertProvides(self, obj, interface): |
462 | 392 | """Assert 'obj' correctly provides 'interface'.""" | 391 | """Assert 'obj' correctly provides 'interface'.""" |
463 | 392 | from lp.testing.matchers import Provides | ||
464 | 393 | self.assertThat(obj, Provides(interface)) | 393 | self.assertThat(obj, Provides(interface)) |
465 | 394 | 394 | ||
466 | 395 | def assertClassImplements(self, cls, interface): | 395 | def assertClassImplements(self, cls, interface): |
467 | 396 | 396 | ||
468 | === modified file 'lib/lp/testing/matchers.py' | |||
469 | --- lib/lp/testing/matchers.py 2011-03-02 23:54:25 +0000 | |||
470 | +++ lib/lp/testing/matchers.py 2011-06-10 15:18:35 +0000 | |||
471 | @@ -8,6 +8,7 @@ | |||
472 | 8 | 'DocTestMatches', | 8 | 'DocTestMatches', |
473 | 9 | 'DoesNotCorrectlyProvide', | 9 | 'DoesNotCorrectlyProvide', |
474 | 10 | 'DoesNotProvide', | 10 | 'DoesNotProvide', |
475 | 11 | 'EqualsIgnoringWhitespace', | ||
476 | 11 | 'HasQueryCount', | 12 | 'HasQueryCount', |
477 | 12 | 'IsNotProxied', | 13 | 'IsNotProxied', |
478 | 13 | 'IsProxied', | 14 | 'IsProxied', |
479 | @@ -20,17 +21,17 @@ | |||
480 | 20 | ] | 21 | ] |
481 | 21 | 22 | ||
482 | 22 | from lazr.lifecycle.snapshot import Snapshot | 23 | from lazr.lifecycle.snapshot import Snapshot |
483 | 24 | from testtools import matchers | ||
484 | 23 | from testtools.content import Content | 25 | from testtools.content import Content |
485 | 24 | from testtools.content_type import UTF8_TEXT | 26 | from testtools.content_type import UTF8_TEXT |
486 | 25 | from testtools.matchers import ( | 27 | from testtools.matchers import ( |
487 | 28 | DocTestMatches as OriginalDocTestMatches, | ||
488 | 26 | Equals, | 29 | Equals, |
489 | 27 | DocTestMatches as OriginalDocTestMatches, | ||
490 | 28 | LessThan, | 30 | LessThan, |
491 | 29 | Matcher, | 31 | Matcher, |
492 | 30 | Mismatch, | 32 | Mismatch, |
493 | 31 | MismatchesAll, | 33 | MismatchesAll, |
494 | 32 | ) | 34 | ) |
495 | 33 | from testtools import matchers | ||
496 | 34 | from zope.interface.exceptions import ( | 35 | from zope.interface.exceptions import ( |
497 | 35 | BrokenImplementation, | 36 | BrokenImplementation, |
498 | 36 | BrokenMethodImplementation, | 37 | BrokenMethodImplementation, |
499 | @@ -44,6 +45,7 @@ | |||
500 | 44 | 45 | ||
501 | 45 | from canonical.launchpad.webapp import canonical_url | 46 | from canonical.launchpad.webapp import canonical_url |
502 | 46 | from canonical.launchpad.webapp.batching import BatchNavigator | 47 | from canonical.launchpad.webapp.batching import BatchNavigator |
503 | 48 | from lp.testing import normalize_whitespace | ||
504 | 47 | from lp.testing._login import person_logged_in | 49 | from lp.testing._login import person_logged_in |
505 | 48 | from lp.testing._webservice import QueryCollector | 50 | from lp.testing._webservice import QueryCollector |
506 | 49 | 51 | ||
507 | @@ -290,7 +292,8 @@ | |||
508 | 290 | 292 | ||
509 | 291 | :param singular: The singular header the batch should be using. | 293 | :param singular: The singular header the batch should be using. |
510 | 292 | :param plural: The plural header the batch should be using. | 294 | :param plural: The plural header the batch should be using. |
512 | 293 | :param batch_size: The batch size that should be configured by default. | 295 | :param batch_size: The batch size that should be configured by |
513 | 296 | default. | ||
514 | 294 | """ | 297 | """ |
515 | 295 | self._single = Equals(singular) | 298 | self._single = Equals(singular) |
516 | 296 | self._plural = Equals(plural) | 299 | self._plural = Equals(plural) |
517 | @@ -438,3 +441,21 @@ | |||
518 | 438 | text = widget.findAll(attrs={'class': 'yui3-activator-data-box'})[0] | 441 | text = widget.findAll(attrs={'class': 'yui3-activator-data-box'})[0] |
519 | 439 | text_matcher = DocTestMatches(extract_text(text)) | 442 | text_matcher = DocTestMatches(extract_text(text)) |
520 | 440 | return text_matcher.match(matchee) | 443 | return text_matcher.match(matchee) |
521 | 444 | |||
522 | 445 | |||
523 | 446 | class EqualsIgnoringWhitespace(Equals): | ||
524 | 447 | """Compare equality, ignoring whitespace in strings. | ||
525 | 448 | |||
526 | 449 | Whitespace in strings is normalized before comparison. All other objects | ||
527 | 450 | are compared as they come. | ||
528 | 451 | """ | ||
529 | 452 | |||
530 | 453 | def __init__(self, expected): | ||
531 | 454 | if isinstance(expected, (str, unicode)): | ||
532 | 455 | expected = normalize_whitespace(expected) | ||
533 | 456 | super(EqualsIgnoringWhitespace, self).__init__(expected) | ||
534 | 457 | |||
535 | 458 | def match(self, observed): | ||
536 | 459 | if isinstance(observed, (str, unicode)): | ||
537 | 460 | observed = normalize_whitespace(observed) | ||
538 | 461 | return super(EqualsIgnoringWhitespace, self).match(observed) | ||
539 | 441 | 462 | ||
540 | === modified file 'lib/lp/testing/tests/test_matchers.py' | |||
541 | --- lib/lp/testing/tests/test_matchers.py 2011-02-25 07:15:06 +0000 | |||
542 | +++ lib/lp/testing/tests/test_matchers.py 2011-06-10 15:18:35 +0000 | |||
543 | @@ -29,6 +29,7 @@ | |||
544 | 29 | DoesNotContain, | 29 | DoesNotContain, |
545 | 30 | DoesNotCorrectlyProvide, | 30 | DoesNotCorrectlyProvide, |
546 | 31 | DoesNotProvide, | 31 | DoesNotProvide, |
547 | 32 | EqualsIgnoringWhitespace, | ||
548 | 32 | HasQueryCount, | 33 | HasQueryCount, |
549 | 33 | IsNotProxied, | 34 | IsNotProxied, |
550 | 34 | IsProxied, | 35 | IsProxied, |
551 | @@ -274,3 +275,36 @@ | |||
552 | 274 | matcher = Contains("bar") | 275 | matcher = Contains("bar") |
553 | 275 | mismatch = matcher.match("foo") | 276 | mismatch = matcher.match("foo") |
554 | 276 | self.assertEqual("bar", mismatch.expected) | 277 | self.assertEqual("bar", mismatch.expected) |
555 | 278 | |||
556 | 279 | |||
557 | 280 | class EqualsIgnoringWhitespaceTests(TestCase): | ||
558 | 281 | |||
559 | 282 | def test_str(self): | ||
560 | 283 | matcher = EqualsIgnoringWhitespace("abc") | ||
561 | 284 | self.assertEqual("EqualsIgnoringWhitespace('abc')", str(matcher)) | ||
562 | 285 | |||
563 | 286 | def test_match_str(self): | ||
564 | 287 | matcher = EqualsIgnoringWhitespace("one \t two \n three") | ||
565 | 288 | self.assertIs(None, matcher.match(" one \r two three ")) | ||
566 | 289 | |||
567 | 290 | def test_mismatch_str(self): | ||
568 | 291 | matcher = EqualsIgnoringWhitespace("one \t two \n three") | ||
569 | 292 | mismatch = matcher.match(" one \r three ") | ||
570 | 293 | self.assertEqual( | ||
571 | 294 | "'one two three' != 'one three'", | ||
572 | 295 | mismatch.describe()) | ||
573 | 296 | |||
574 | 297 | def test_match_unicode(self): | ||
575 | 298 | matcher = EqualsIgnoringWhitespace(u"one \t two \n \u1234 ") | ||
576 | 299 | self.assertIs(None, matcher.match(u" one \r two \u1234 ")) | ||
577 | 300 | |||
578 | 301 | def test_mismatch_unicode(self): | ||
579 | 302 | matcher = EqualsIgnoringWhitespace(u"one \t two \n \u1234 ") | ||
580 | 303 | mismatch = matcher.match(u" one \r \u1234 ") | ||
581 | 304 | self.assertEqual( | ||
582 | 305 | u"u'one two \\u1234' != u'one \\u1234'", | ||
583 | 306 | mismatch.describe()) | ||
584 | 307 | |||
585 | 308 | def test_match_non_string(self): | ||
586 | 309 | matcher = EqualsIgnoringWhitespace(1234) | ||
587 | 310 | self.assertIs(None, matcher.match(1234)) |
I think you're conflating things were they don't need to be. For +initseries, you only really care if the distroseries has been initialised, not if it is a derived series.
The with featureflag(): section reads as a little messy -- I thought we had FeatureFixture for that which makes it easier?
Thank you for cleaning up the imports, but I don't think you can import DSDJ from lp.soyuz.model? Or if you can, why are you importing the whole module?