Merge lp:~james-w/launchpad/more-matchers into lp:launchpad
- more-matchers
- Merge into devel
Status: | Work in progress |
---|---|
Proposed branch: | lp:~james-w/launchpad/more-matchers |
Merge into: | lp:launchpad |
Prerequisite: | lp:~james-w/launchpad/improve-makeDistroArchSeries |
Diff against target: |
745 lines (+559/-31) 7 files modified
lib/lp/soyuz/testing/matchers.py (+137/-0) lib/lp/soyuz/testing/tests/test_matchers.py (+244/-0) lib/lp/soyuz/tests/test_publishing.py (+7/-24) lib/lp/testing/factory.py (+1/-1) lib/lp/testing/matchers.py (+61/-3) lib/lp/testing/tests/test_factory.py (+10/-0) lib/lp/testing/tests/test_matchers.py (+99/-3) |
To merge this branch: | bzr merge lp:~james-w/launchpad/more-matchers |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert Collins (community) | Approve | ||
Review via email: mp+32057@code.launchpad.net |
Commit message
[r=lifeless][no-qa] Add some more matchers, particularly soyuz-specific ones.
Description of the change
Hi,
This ports some Soyuz custom assertion methods to matchers.
I did this so that tests can make use of them without having to
subclass what is a fairly heavyweight test class.
I could have added a new TestCase subclass with the custom assertion
methods only and slotted it in to the existing hierarchy, but matchers
are better.
Lint:
./lib/lp/
32: redefinition of unused 'os' from line 31
Thanks,
James
- 11332. By James Westby
-
Dummy revision to force diff regeneration.
- 11333. By James Westby
-
Fix the docstrings given that it's not publications that superseded publications.
- 11334. By James Westby
-
Rename PublishedStateIsNot to PublishedStateI
sWrong as the first sounds like a matcher. - 11335. By James Westby
-
Use a normal function, not an inline one for title_of.
- 11336. By James Westby
-
Fix matcher import.
- 11337. By James Westby
-
Allow publishing records in the superseded check.
Graham Binns (gmb) wrote : | # |
Trying to run this through EC2 I got a mass of conflicts:
Warning: criss-cross merge encountered. See bzr help criss-cross.
[...]
Text conflict in lib/lp/
Text conflict in lib/lp/
Text conflict in lib/lp/
Text conflict in lib/lp/
Text conflict in lib/lp/
Text conflict in lib/lp/
Text conflict in lib/lp/
Text conflict in lib/lp/
Text conflict in lib/lp/
Text conflict in lib/lp/
Text conflict in lib/lp/
Text conflict in lib/lp/
Text conflict in lib/lp/
Please resolve these and then ping me and I'll be happy to re-try the landing.
Robert Collins (lifeless) wrote : | # |
This clearly needs more work to be usable :(.
Brad Crittenden (bac) wrote : | # |
This branch depends on two pre-requisite branches that have been marked as 'Work In Progress', so this one cannot progress until they do. I am therefore marking this branch WIP too.
Unmerged revisions
- 11337. By James Westby
-
Allow publishing records in the superseded check.
- 11336. By James Westby
-
Fix matcher import.
- 11335. By James Westby
-
Use a normal function, not an inline one for title_of.
- 11334. By James Westby
-
Rename PublishedStateIsNot to PublishedStateI
sWrong as the first sounds like a matcher. - 11333. By James Westby
-
Fix the docstrings given that it's not publications that superseded publications.
- 11332. By James Westby
-
Dummy revision to force diff regeneration.
- 11331. By James Westby
-
Clean up some lint.
- 11330. By James Westby
-
Replace checkSuperseded with the new matcher.
- 11329. By James Westby
-
Add an IsSupersededBy matcher.
- 11328. By James Westby
-
Have test_publishing use the new matcher.
Preview Diff
1 | === added directory 'lib/lp/soyuz/testing' |
2 | === added file 'lib/lp/soyuz/testing/__init__.py' |
3 | === added file 'lib/lp/soyuz/testing/matchers.py' |
4 | --- lib/lp/soyuz/testing/matchers.py 1970-01-01 00:00:00 +0000 |
5 | +++ lib/lp/soyuz/testing/matchers.py 2010-08-09 15:59:49 +0000 |
6 | @@ -0,0 +1,137 @@ |
7 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
8 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
9 | + |
10 | +__metaclass__ = type |
11 | + |
12 | +__all__ = [ |
13 | + 'IsSupersededBy', |
14 | + 'PublishedStateIs', |
15 | + 'PublishedStateIsWrong', |
16 | +] |
17 | + |
18 | +from zope.security.proxy import isinstance |
19 | + |
20 | +from testtools.matchers import Annotate, Equals, Matcher, Mismatch |
21 | + |
22 | +from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
23 | +from lp.soyuz.model.publishing import ( |
24 | + BinaryPackagePublishingHistory, SourcePackagePublishingHistory) |
25 | +from lp.testing.matchers import DateIsInPast |
26 | + |
27 | + |
28 | +class PublishedStateIsWrong(Mismatch): |
29 | + |
30 | + def __init__(self, publication, status): |
31 | + """Create a PublishedStateIsWrong Mismatch. |
32 | + |
33 | + :param publication: the publication that has the wrong state. |
34 | + :param status: the status that the publication should have had. |
35 | + """ |
36 | + self.publication = publication |
37 | + self.status = status |
38 | + |
39 | + def describe(self): |
40 | + return "Publication '%s' was '%s' instead of '%s'." % ( |
41 | + self.publication.displayname, self.publication.status.name, |
42 | + self.status.name) |
43 | + |
44 | + |
45 | +class PublishedStateIs(Matcher): |
46 | + """Check the state of publication(s) is a certain value.""" |
47 | + |
48 | + def __init__(self, status): |
49 | + """Create a PublishedState matcher. |
50 | + |
51 | + :param status: the status the publication(s) should have. |
52 | + """ |
53 | + self.status = status |
54 | + |
55 | + def __str__(self): |
56 | + return "Published state is %s" % (self.status.name, ) |
57 | + |
58 | + def match(self, publications): |
59 | + """Match the status of publication(s) against the expected. |
60 | + |
61 | + :param publications: a publication or iterable of publications |
62 | + of which to check the status. |
63 | + :return: An instance of `PublishedStateIsNot` if any of the |
64 | + publications have a different status, otherwise None if they |
65 | + all match. |
66 | + """ |
67 | + try: |
68 | + list(publications) |
69 | + except TypeError: |
70 | + publications = [publications] |
71 | + for publication in publications: |
72 | + if publication.status != self.status: |
73 | + return PublishedStateIsWrong(publication, self.status) |
74 | + return None |
75 | + |
76 | + |
77 | +def title_of(obj): |
78 | + if obj is None: |
79 | + return None |
80 | + else: |
81 | + return obj.title |
82 | + |
83 | + |
84 | +class IsSupersededBy(Matcher): |
85 | + """Check that superseded publishing record(s) have correct values. |
86 | + |
87 | + A superseded publishing record should have: |
88 | + status = PackagePublishingStatus.SUPERSEDED |
89 | + datesuperseded in the past |
90 | + superseded_by a particular object or None |
91 | + """ |
92 | + |
93 | + def __init__(self, superseded_by): |
94 | + """Create an IsSupersededBy Matcher. |
95 | + |
96 | + :param superseded_by: the object (`SourcePackageRelease` for |
97 | + source publications, or `PackageBuild` for binary publications) |
98 | + that should have superseded the checked publications, or |
99 | + None if they shouldn't have been superseded by another |
100 | + object. You can also pass a `SourcePackagePublishingHistory` |
101 | + or `BinaryPackagePublishingHistory` to have their associated |
102 | + `SourcePackageRelease` or `PackageBuild` used instead. |
103 | + """ |
104 | + self.superseded_by = superseded_by |
105 | + |
106 | + def __str__(self): |
107 | + by = None |
108 | + if self.superseded_by is not None: |
109 | + by = "'%s'" % self.superseded_by.displayname |
110 | + return "Is correctly superseded (by %s)." % by |
111 | + |
112 | + def match(self, publications): |
113 | + mismatch = PublishedStateIs( |
114 | + PackagePublishingStatus.SUPERSEDED).match(publications) |
115 | + if mismatch is not None: |
116 | + return mismatch |
117 | + try: |
118 | + list(publications) |
119 | + except TypeError: |
120 | + publications = [publications] |
121 | + for publication in publications: |
122 | + matcher = Annotate( |
123 | + "'%s' has a datesuperseded in the future." % ( |
124 | + publication.displayname, ), |
125 | + DateIsInPast()) |
126 | + mismatch = matcher.match(publication.datesuperseded) |
127 | + if mismatch is not None: |
128 | + return mismatch |
129 | + superseded_by = self.superseded_by |
130 | + if superseded_by is not None: |
131 | + if isinstance(superseded_by, BinaryPackagePublishingHistory): |
132 | + superseded_by = superseded_by.binarypackagerelease.build |
133 | + elif isinstance(superseded_by, SourcePackagePublishingHistory): |
134 | + superseded_by = superseded_by.sourcepackagerelease |
135 | + matcher = Annotate( |
136 | + "'%s' has the wrong supersededby, expected '%s', got '%s'" |
137 | + % (publication.displayname, title_of(superseded_by), |
138 | + title_of(publication.supersededby)), |
139 | + Equals(superseded_by)) |
140 | + mismatch = matcher.match(publication.supersededby) |
141 | + if mismatch is not None: |
142 | + return mismatch |
143 | + return None |
144 | |
145 | === added directory 'lib/lp/soyuz/testing/tests' |
146 | === added file 'lib/lp/soyuz/testing/tests/__init__.py' |
147 | === added file 'lib/lp/soyuz/testing/tests/test_matchers.py' |
148 | --- lib/lp/soyuz/testing/tests/test_matchers.py 1970-01-01 00:00:00 +0000 |
149 | +++ lib/lp/soyuz/testing/tests/test_matchers.py 2010-08-09 15:59:49 +0000 |
150 | @@ -0,0 +1,244 @@ |
151 | +# Copyright 2010 Canonical Ltd. This software is licensed under the |
152 | +# GNU Affero General Public License version 3 (see the file LICENSE). |
153 | + |
154 | +__metaclass__ = type |
155 | + |
156 | +from datetime import datetime, timedelta |
157 | + |
158 | +import pytz |
159 | + |
160 | +from testtools.matchers import AnnotatedMismatch |
161 | + |
162 | +from canonical.testing.layers import DatabaseFunctionalLayer |
163 | +from lp.soyuz.interfaces.publishing import PackagePublishingStatus |
164 | +from lp.soyuz.testing.matchers import ( |
165 | + IsSupersededBy, PublishedStateIs, PublishedStateIsWrong) |
166 | +from lp.testing import celebrity_logged_in, TestCaseWithFactory |
167 | +from lp.testing.matchers import DateIsNotInPast |
168 | + |
169 | + |
170 | +class PublishedStateIsNotTests(TestCaseWithFactory): |
171 | + |
172 | + layer = DatabaseFunctionalLayer |
173 | + |
174 | + def test_describe_binary(self): |
175 | + bpph = self.factory.makeBinaryPackagePublishingHistory( |
176 | + status = PackagePublishingStatus.PENDING) |
177 | + status = PackagePublishingStatus.PUBLISHED |
178 | + mismatch = PublishedStateIsWrong(bpph, status) |
179 | + self.assertEqual( |
180 | + "Publication '%s' was '%s' instead of '%s'." % ( |
181 | + bpph.displayname, bpph.status.name, status.name), |
182 | + mismatch.describe()) |
183 | + |
184 | + def test_describe_source(self): |
185 | + spph = self.factory.makeSourcePackagePublishingHistory( |
186 | + status = PackagePublishingStatus.PENDING) |
187 | + status = PackagePublishingStatus.PUBLISHED |
188 | + mismatch = PublishedStateIsWrong(spph, status) |
189 | + self.assertEqual( |
190 | + "Publication '%s' was '%s' instead of '%s'." % ( |
191 | + spph.displayname, spph.status.name, status.name), |
192 | + mismatch.describe()) |
193 | + |
194 | + |
195 | +class PublishedStateIsTests(TestCaseWithFactory): |
196 | + |
197 | + layer = DatabaseFunctionalLayer |
198 | + |
199 | + def test_str(self): |
200 | + status = PackagePublishingStatus.PUBLISHED |
201 | + matcher = PublishedStateIs(status) |
202 | + self.assertEqual( |
203 | + "Published state is %s" % (status.name, ), |
204 | + str(matcher)) |
205 | + |
206 | + def getMatcherResultSingle(self, status): |
207 | + spph = self.factory.makeSourcePackagePublishingHistory( |
208 | + status=PackagePublishingStatus.PENDING) |
209 | + matcher = PublishedStateIs(status) |
210 | + return matcher.match(spph), spph |
211 | + |
212 | + def getMatcherResultList(self, status): |
213 | + pubs = [ |
214 | + self.factory.makeSourcePackagePublishingHistory( |
215 | + status=status), |
216 | + self.factory.makeSourcePackagePublishingHistory( |
217 | + status=PackagePublishingStatus.PENDING), |
218 | + ] |
219 | + matcher = PublishedStateIs(status) |
220 | + return matcher.match(pubs), pubs |
221 | + |
222 | + def test_match_single(self): |
223 | + mismatch, publication = self.getMatcherResultSingle( |
224 | + PackagePublishingStatus.PENDING) |
225 | + self.assertIs(None, mismatch) |
226 | + |
227 | + def test_match_list(self): |
228 | + mismatch, publications = self.getMatcherResultList( |
229 | + PackagePublishingStatus.PENDING) |
230 | + self.assertIs(None, mismatch) |
231 | + |
232 | + def test_mismatch_single(self): |
233 | + mismatch, publication = self.getMatcherResultSingle( |
234 | + PackagePublishingStatus.PUBLISHED) |
235 | + self.assertIsInstance(mismatch, PublishedStateIsWrong) |
236 | + |
237 | + def test_mismatch_list(self): |
238 | + mismatch, publications = self.getMatcherResultList( |
239 | + PackagePublishingStatus.PUBLISHED) |
240 | + self.assertIsInstance(mismatch, PublishedStateIsWrong) |
241 | + |
242 | + def test_mismatch_sets_publication_single(self): |
243 | + mismatch, publication = self.getMatcherResultSingle( |
244 | + PackagePublishingStatus.PUBLISHED) |
245 | + self.assertEqual(publication, mismatch.publication) |
246 | + |
247 | + def test_mismatch_sets_publication_list(self): |
248 | + mismatch, publications = self.getMatcherResultList( |
249 | + PackagePublishingStatus.PUBLISHED) |
250 | + self.assertIsInstance(mismatch, PublishedStateIsWrong) |
251 | + self.assertEqual(publications[1], mismatch.publication) |
252 | + |
253 | + def test_mismatch_sets_status(self): |
254 | + status = PackagePublishingStatus.PUBLISHED |
255 | + mismatch, publication = self.getMatcherResultSingle(status) |
256 | + self.assertEqual(status, mismatch.status) |
257 | + |
258 | + |
259 | +class IsSupersededByTests(TestCaseWithFactory): |
260 | + |
261 | + layer = DatabaseFunctionalLayer |
262 | + |
263 | + def test_str_with_superseded_by(self): |
264 | + pub = self.factory.makeSourcePackagePublishingHistory() |
265 | + matcher = IsSupersededBy(pub) |
266 | + self.assertEqual( |
267 | + "Is correctly superseded (by '%s')." % pub.displayname, |
268 | + str(matcher)) |
269 | + |
270 | + def test_str_with_None(self): |
271 | + matcher = IsSupersededBy(None) |
272 | + self.assertEqual( |
273 | + "Is correctly superseded (by None).", str(matcher)) |
274 | + |
275 | + def makeSupersededPublishing(self, superseded_by=None, |
276 | + datesuperseded=None): |
277 | + pub = self.factory.makeSourcePackagePublishingHistory( |
278 | + status=PackagePublishingStatus.PUBLISHED) |
279 | + pub.supersede(dominant=superseded_by) |
280 | + if datesuperseded is not None: |
281 | + with celebrity_logged_in('admin'): |
282 | + pub.datesuperseded = datesuperseded |
283 | + return pub |
284 | + |
285 | + def test_match_single_superseded_by_not_None(self): |
286 | + superseded_by = self.factory.makeSourcePackagePublishingHistory() |
287 | + matcher = IsSupersededBy(superseded_by.sourcepackagerelease) |
288 | + past = datetime.now(pytz.UTC) - timedelta(days=4) |
289 | + pub = self.makeSupersededPublishing( |
290 | + superseded_by=superseded_by, datesuperseded=past) |
291 | + mismatch = matcher.match(pub) |
292 | + self.assertIs(None, mismatch) |
293 | + |
294 | + def test_match_single_superseded_by_None(self): |
295 | + matcher = IsSupersededBy(None) |
296 | + past = datetime.now(pytz.UTC) - timedelta(days=4) |
297 | + pub = self.makeSupersededPublishing( |
298 | + superseded_by=None, datesuperseded=past) |
299 | + mismatch = matcher.match(pub) |
300 | + self.assertIs(None, mismatch) |
301 | + |
302 | + def test_match_list_superseded_by_not_None(self): |
303 | + superseded_by = self.factory.makeSourcePackagePublishingHistory() |
304 | + matcher = IsSupersededBy(superseded_by.sourcepackagerelease) |
305 | + past = datetime.now(pytz.UTC) - timedelta(days=4) |
306 | + pubs = [ |
307 | + self.makeSupersededPublishing( |
308 | + superseded_by=superseded_by, datesuperseded=past), |
309 | + self.makeSupersededPublishing( |
310 | + superseded_by=superseded_by, datesuperseded=past), |
311 | + ] |
312 | + mismatch = matcher.match(pubs) |
313 | + self.assertIs(None, mismatch) |
314 | + |
315 | + def test_match_list_superseded_by_None(self): |
316 | + matcher = IsSupersededBy(None) |
317 | + past = datetime.now(pytz.UTC) - timedelta(days=4) |
318 | + pubs = [ |
319 | + self.makeSupersededPublishing( |
320 | + superseded_by=None, datesuperseded=past), |
321 | + self.makeSupersededPublishing( |
322 | + superseded_by=None, datesuperseded=past), |
323 | + ] |
324 | + mismatch = matcher.match(pubs) |
325 | + self.assertIs(None, mismatch) |
326 | + |
327 | + def test_mismatch_single_wrong_status(self): |
328 | + matcher = IsSupersededBy(None) |
329 | + past = datetime.now(pytz.UTC) - timedelta(days=4) |
330 | + pub = self.makeSupersededPublishing( |
331 | + superseded_by=None, datesuperseded=past) |
332 | + with celebrity_logged_in('admin'): |
333 | + pub.status = PackagePublishingStatus.PUBLISHED |
334 | + mismatch = matcher.match(pub) |
335 | + self.assertIsInstance(mismatch, PublishedStateIsWrong) |
336 | + |
337 | + def test_mismatch_list_wrong_status(self): |
338 | + matcher = IsSupersededBy(None) |
339 | + past = datetime.now(pytz.UTC) - timedelta(days=4) |
340 | + pubs = [ |
341 | + self.makeSupersededPublishing( |
342 | + superseded_by=None, datesuperseded=past), |
343 | + self.makeSupersededPublishing( |
344 | + superseded_by=None, datesuperseded=past), |
345 | + ] |
346 | + with celebrity_logged_in('admin'): |
347 | + pubs[1].status = PackagePublishingStatus.PUBLISHED |
348 | + mismatch = matcher.match(pubs) |
349 | + self.assertIsInstance(mismatch, PublishedStateIsWrong) |
350 | + |
351 | + def test_mismatch_single_wrong_date(self): |
352 | + matcher = IsSupersededBy(None) |
353 | + future = datetime.now(pytz.UTC) + timedelta(days=4) |
354 | + pub = self.makeSupersededPublishing( |
355 | + superseded_by=None, datesuperseded=future) |
356 | + mismatch = matcher.match(pub) |
357 | + self.assertIsInstance(mismatch, AnnotatedMismatch) |
358 | + self.assertIsInstance(mismatch.mismatch, DateIsNotInPast) |
359 | + |
360 | + def test_mismatch_list_wrong_date(self): |
361 | + matcher = IsSupersededBy(None) |
362 | + past = datetime.now(pytz.UTC) - timedelta(days=4) |
363 | + future = datetime.now(pytz.UTC) + timedelta(days=4) |
364 | + pubs = [ |
365 | + self.makeSupersededPublishing( |
366 | + superseded_by=None, datesuperseded=past), |
367 | + self.makeSupersededPublishing( |
368 | + superseded_by=None, datesuperseded=future), |
369 | + ] |
370 | + mismatch = matcher.match(pubs) |
371 | + self.assertIsInstance(mismatch, AnnotatedMismatch) |
372 | + self.assertIsInstance(mismatch.mismatch, DateIsNotInPast) |
373 | + |
374 | + def test_mismatch_single_wrong_superseded_by(self): |
375 | + superseded_by = self.factory.makeSourcePackagePublishingHistory() |
376 | + matcher = IsSupersededBy(None) |
377 | + past = datetime.now(pytz.UTC) - timedelta(days=4) |
378 | + pub = self.makeSupersededPublishing( |
379 | + superseded_by=superseded_by, datesuperseded=past) |
380 | + mismatch = matcher.match(pub) |
381 | + self.assertIsInstance(mismatch, AnnotatedMismatch) |
382 | + |
383 | + def test_mismatch_list_wrong_superseded_by(self): |
384 | + superseded_by = self.factory.makeSourcePackagePublishingHistory() |
385 | + matcher = IsSupersededBy(None) |
386 | + past = datetime.now(pytz.UTC) - timedelta(days=4) |
387 | + pubs = [ |
388 | + self.makeSupersededPublishing( |
389 | + superseded_by=None, datesuperseded=past), |
390 | + self.makeSupersededPublishing( |
391 | + superseded_by=superseded_by, datesuperseded=past), |
392 | + ] |
393 | + mismatch = matcher.match(pubs) |
394 | + self.assertIsInstance(mismatch, AnnotatedMismatch) |
395 | |
396 | === modified file 'lib/lp/soyuz/tests/test_publishing.py' |
397 | --- lib/lp/soyuz/tests/test_publishing.py 2010-08-09 15:59:47 +0000 |
398 | +++ lib/lp/soyuz/tests/test_publishing.py 2010-08-09 15:59:49 +0000 |
399 | @@ -12,7 +12,7 @@ |
400 | |
401 | import pytz |
402 | from zope.component import getUtility |
403 | -from zope.security.proxy import isinstance, removeSecurityProxy |
404 | +from zope.security.proxy import removeSecurityProxy |
405 | |
406 | from canonical.config import config |
407 | from canonical.database.constants import UTC_NOW |
408 | @@ -29,8 +29,6 @@ |
409 | from lp.registry.interfaces.sourcepackage import SourcePackageUrgency |
410 | from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet |
411 | from lp.soyuz.model.processor import ProcessorFamily |
412 | -from lp.soyuz.model.publishing import ( |
413 | - BinaryPackagePublishingHistory) |
414 | from lp.soyuz.interfaces.archive import ArchivePurpose |
415 | from lp.soyuz.interfaces.archivearch import IArchiveArchSet |
416 | from lp.soyuz.interfaces.binarypackagerelease import BinaryPackageFormat |
417 | @@ -38,8 +36,10 @@ |
418 | from lp.soyuz.interfaces.publishing import ( |
419 | IPublishingSet, PackagePublishingPriority, PackagePublishingStatus) |
420 | from lp.soyuz.interfaces.queue import PackageUploadStatus |
421 | +from lp.soyuz.testing.matchers import IsSupersededBy, PublishedStateIs |
422 | from canonical.launchpad.scripts import FakeLogger |
423 | from lp.testing import TestCaseWithFactory |
424 | +from lp.testing.matchers import DateIsInPast |
425 | from lp.testing.factory import LaunchpadObjectFactory |
426 | from lp.testing.sampledata import UBUNTU_DEVELOPER_ADMIN_NAME |
427 | from lp.testing.fakemethod import FakeMethod |
428 | @@ -499,17 +499,14 @@ |
429 | |
430 | def checkPublication(self, pub, status): |
431 | """Assert the publication has the given status.""" |
432 | - self.assertEqual( |
433 | - pub.status, status, "%s is not %s (%s)" % ( |
434 | - pub.displayname, status.name, pub.status.name)) |
435 | + self.checkPublications(pub, status) |
436 | |
437 | def checkPublications(self, pubs, status): |
438 | """Assert the given publications have the given status. |
439 | |
440 | See `checkPublication`. |
441 | """ |
442 | - for pub in pubs: |
443 | - self.checkPublication(pub, status) |
444 | + self.assertThat(pubs, PublishedStateIs(status)) |
445 | |
446 | def checkPastDate(self, date, lag=None): |
447 | """Assert given date is older than 'now'. |
448 | @@ -517,24 +514,10 @@ |
449 | Optionally the user can pass a 'lag' which will be added to 'now' |
450 | before comparing. |
451 | """ |
452 | - UTC = pytz.timezone("UTC") |
453 | - limit = datetime.datetime.now(UTC) |
454 | - if lag is not None: |
455 | - limit = limit + lag |
456 | - self.assertTrue(date < limit, "%s >= %s" % (date, limit)) |
457 | + self.assertThat(date, DateIsInPast(lag=lag)) |
458 | |
459 | def checkSuperseded(self, pubs, supersededby=None): |
460 | - self.checkPublications(pubs, PackagePublishingStatus.SUPERSEDED) |
461 | - for pub in pubs: |
462 | - self.checkPastDate(pub.datesuperseded) |
463 | - if supersededby is not None: |
464 | - if isinstance(pub, BinaryPackagePublishingHistory): |
465 | - dominant = supersededby.binarypackagerelease.build |
466 | - else: |
467 | - dominant = supersededby.sourcepackagerelease |
468 | - self.assertEquals(dominant, pub.supersededby) |
469 | - else: |
470 | - self.assertIs(None, pub.supersededby) |
471 | + self.assertThat(pubs, IsSupersededBy(supersededby)) |
472 | |
473 | |
474 | class TestNativePublishing(TestNativePublishingBase): |
475 | |
476 | === modified file 'lib/lp/testing/factory.py' |
477 | --- lib/lp/testing/factory.py 2010-08-09 15:59:47 +0000 |
478 | +++ lib/lp/testing/factory.py 2010-08-09 15:59:49 +0000 |
479 | @@ -2243,7 +2243,7 @@ |
480 | def makeSourcePackageName(self, name=None): |
481 | """Make an `ISourcePackageName`.""" |
482 | if name is None: |
483 | - name = self.getUniqueString() |
484 | + name = self.getUniqueString('sourcepackagename') |
485 | return getUtility(ISourcePackageNameSet).new(name) |
486 | |
487 | def getOrMakeSourcePackageName(self, name=None): |
488 | |
489 | === modified file 'lib/lp/testing/matchers.py' |
490 | --- lib/lp/testing/matchers.py 2010-08-09 15:59:47 +0000 |
491 | +++ lib/lp/testing/matchers.py 2010-08-09 15:59:49 +0000 |
492 | @@ -3,6 +3,8 @@ |
493 | |
494 | __metaclass__ = type |
495 | __all__ = [ |
496 | + 'DateIsInPast', |
497 | + 'DateIsNotInPast', |
498 | 'DoesNotCorrectlyProvide', |
499 | 'DoesNotProvide', |
500 | 'DoesNotStartWith', |
501 | @@ -14,6 +16,9 @@ |
502 | 'StartsWith', |
503 | ] |
504 | |
505 | +from datetime import datetime |
506 | + |
507 | +import pytz |
508 | from zope.interface.verify import verifyObject |
509 | from zope.interface.exceptions import ( |
510 | BrokenImplementation, BrokenMethodImplementation, DoesNotImplement) |
511 | @@ -123,15 +128,15 @@ |
512 | self.query_collector = query_collector |
513 | |
514 | def describe(self): |
515 | - return "queries do not match: %s" % (self.count_mismatch.describe(),) |
516 | + return "queries do not match: %s" % (self.count_mismatch.describe(), ) |
517 | |
518 | def get_details(self): |
519 | result = [] |
520 | for query in self.query_collector.queries: |
521 | result.append(unicode(query).encode('utf8')) |
522 | return {'queries': Content(ContentType('text', 'plain', |
523 | - {'charset': 'utf8'}), lambda:['\n'.join(result)])} |
524 | - |
525 | + {'charset': 'utf8'}), lambda: ['\n'.join(result)])} |
526 | + |
527 | |
528 | class IsNotProxied(Mismatch): |
529 | """An object is not proxied.""" |
530 | @@ -213,3 +218,56 @@ |
531 | if not matchee.startswith(self.expected): |
532 | return DoesNotStartWith(matchee, self.expected) |
533 | return None |
534 | + |
535 | + |
536 | +class DateIsNotInPast(Mismatch): |
537 | + |
538 | + def __init__(self, date, limit): |
539 | + """Create a DateIsNotInPast Mismatch. |
540 | + |
541 | + :param date: the date that was checked. |
542 | + :param limit: the date that it should have been before. |
543 | + """ |
544 | + self.date = date |
545 | + self.limit = limit |
546 | + |
547 | + def describe(self): |
548 | + return "%s >= %s" % (self.date, self.limit) |
549 | + |
550 | + |
551 | +class DateIsInPast(Matcher): |
552 | + """A matcher that checks a datetime is in the past.""" |
553 | + |
554 | + def __init__(self, current_date=None, lag=None): |
555 | + """Create a DateIsInPast Matcher. |
556 | + |
557 | + :param current_date: the `datetime.datetime` that the date should be |
558 | + before, or None for the current time when the check is done. |
559 | + :param lag: a `datetime.timedelta` to add to the current time |
560 | + before comparing, or None for no change. |
561 | + """ |
562 | + self.current_date = current_date |
563 | + self.lag = lag |
564 | + |
565 | + def __str__(self): |
566 | + if self.current_date is None: |
567 | + start_str = 'UTC_NOW' |
568 | + if self.lag is not None: |
569 | + start_str += ' plus %s' % self.lag |
570 | + else: |
571 | + start = self.current_date |
572 | + if self.lag is not None: |
573 | + start += self.lag |
574 | + start_str = str(start) |
575 | + return "Date is before %s." % start_str |
576 | + |
577 | + def match(self, date): |
578 | + if self.current_date is None: |
579 | + limit = datetime.now(pytz.UTC) |
580 | + else: |
581 | + limit = self.current_date |
582 | + if self.lag is not None: |
583 | + limit += self.lag |
584 | + if date >= limit: |
585 | + return DateIsNotInPast(date, limit) |
586 | + return None |
587 | |
588 | === modified file 'lib/lp/testing/tests/test_factory.py' |
589 | --- lib/lp/testing/tests/test_factory.py 2010-08-09 15:59:47 +0000 |
590 | +++ lib/lp/testing/tests/test_factory.py 2010-08-09 15:59:49 +0000 |
591 | @@ -20,6 +20,7 @@ |
592 | from lp.registry.interfaces.distribution import IDistribution |
593 | from lp.registry.interfaces.distroseries import IDistroSeries |
594 | from lp.registry.interfaces.sourcepackage import SourcePackageFileType |
595 | +from lp.registry.interfaces.sourcepackagename import ISourcePackageName |
596 | from lp.registry.interfaces.suitesourcepackage import ISuiteSourcePackage |
597 | from lp.services.worlddata.interfaces.language import ILanguage |
598 | from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuild |
599 | @@ -401,6 +402,15 @@ |
600 | # And name is constructed from code as 'Language %(code)s'. |
601 | self.assertEquals('Test language', language.englishname) |
602 | |
603 | + # makeSourcePackageName |
604 | + def test_makeSourcePackageName_returns_proxied_ISPN(self): |
605 | + spn = self.factory.makeSourcePackageName() |
606 | + self.assertThat(spn, ProvidesAndIsProxied(ISourcePackageName)) |
607 | + |
608 | + def test_makeSourcePackageName_created_has_useful_prefix(self): |
609 | + spn = self.factory.makeSourcePackageName() |
610 | + self.assertThat(spn.name, StartsWith("sourcepackagename")) |
611 | + |
612 | # makeSourcePackagePublishingHistory |
613 | def test_makeSourcePackagePublishingHistory_returns_ISPPH(self): |
614 | spph = self.factory.makeSourcePackagePublishingHistory() |
615 | |
616 | === modified file 'lib/lp/testing/tests/test_matchers.py' |
617 | --- lib/lp/testing/tests/test_matchers.py 2010-08-09 15:59:47 +0000 |
618 | +++ lib/lp/testing/tests/test_matchers.py 2010-08-09 15:59:49 +0000 |
619 | @@ -3,6 +3,9 @@ |
620 | |
621 | __metaclass__ = type |
622 | |
623 | +from datetime import datetime, timedelta |
624 | + |
625 | +import pytz |
626 | from zope.interface import implements, Interface |
627 | from zope.interface.verify import verifyObject |
628 | from zope.interface.exceptions import BrokenImplementation |
629 | @@ -11,8 +14,9 @@ |
630 | |
631 | from lp.testing import TestCase |
632 | from lp.testing.matchers import ( |
633 | - DoesNotCorrectlyProvide, DoesNotProvide, DoesNotStartWith, HasQueryCount, |
634 | - IsNotProxied, IsProxied, Provides, ProvidesAndIsProxied, StartsWith) |
635 | + DateIsInPast, DateIsNotInPast, DoesNotCorrectlyProvide, DoesNotProvide, |
636 | + DoesNotStartWith, HasQueryCount, IsNotProxied, IsProxied, Provides, |
637 | + ProvidesAndIsProxied, StartsWith) |
638 | from lp.testing._webservice import QueryCollector |
639 | |
640 | from testtools.matchers import Is, Not, LessThan |
641 | @@ -208,7 +212,7 @@ |
642 | self.assertEqual(["('foo', 'bar')\n('baaz', 'quux')"], |
643 | lines) |
644 | self.assertEqual( |
645 | - "queries do not match: %s" % (LessThan(2).match(2).describe(),), |
646 | + "queries do not match: %s" % (LessThan(2).match(2).describe(), ), |
647 | mismatch.describe()) |
648 | |
649 | |
650 | @@ -243,3 +247,95 @@ |
651 | matcher = StartsWith("bar") |
652 | mismatch = matcher.match("foo") |
653 | self.assertEqual("bar", mismatch.expected) |
654 | + |
655 | + |
656 | +class DateIsNotInPastTests(TestCase): |
657 | + |
658 | + def test_describe(self): |
659 | + date = datetime(2008, 01, 01, 01, 01, 01, 01, pytz.UTC) |
660 | + limit = datetime(2000, 02, 02, 02, 02, 02, 02, pytz.UTC) |
661 | + mismatch = DateIsNotInPast(date, limit) |
662 | + self.assertEqual("%s >= %s" % (date, limit), mismatch.describe()) |
663 | + |
664 | + |
665 | +class DateIsInPastTests(TestCase): |
666 | + |
667 | + def test_str_now_no_lag(self): |
668 | + matcher = DateIsInPast() |
669 | + self.assertEqual("Date is before UTC_NOW.", str(matcher)) |
670 | + |
671 | + def test_str_now_with_lag(self): |
672 | + lag = timedelta(seconds=60) |
673 | + matcher = DateIsInPast(lag=lag) |
674 | + self.assertEqual( |
675 | + "Date is before UTC_NOW plus %s." % lag, str(matcher)) |
676 | + |
677 | + def test_str_current_date_no_lag(self): |
678 | + date = datetime(2008, 01, 01, 01, 01, 01, 01, pytz.UTC) |
679 | + matcher = DateIsInPast(current_date=date) |
680 | + self.assertEqual("Date is before %s." % date, str(matcher)) |
681 | + |
682 | + def test_str_current_date_with_lag(self): |
683 | + date = datetime(2008, 01, 01, 01, 01, 01, 01, pytz.UTC) |
684 | + lag = timedelta(seconds=60) |
685 | + matcher = DateIsInPast(current_date=date, lag=lag) |
686 | + self.assertEqual("Date is before %s." % (date + lag, ), str(matcher)) |
687 | + |
688 | + def test_match_now_no_lag(self): |
689 | + date = datetime.now(pytz.UTC) - timedelta(days=3) |
690 | + matcher = DateIsInPast() |
691 | + self.assertEqual(None, matcher.match(date)) |
692 | + |
693 | + def test_match_now_with_lag(self): |
694 | + date = datetime.now(pytz.UTC) + timedelta(days=3) |
695 | + lag = timedelta(days=4) |
696 | + matcher = DateIsInPast(lag=lag) |
697 | + self.assertEqual(None, matcher.match(date)) |
698 | + |
699 | + def test_current_date_no_lag(self): |
700 | + current_date = datetime(2008, 01, 01, 01, 01, 01, 01, pytz.UTC) |
701 | + date = current_date - timedelta(days=3) |
702 | + matcher = DateIsInPast(current_date=current_date) |
703 | + self.assertEqual(None, matcher.match(date)) |
704 | + |
705 | + def test_current_date_with_lag(self): |
706 | + current_date = datetime(2008, 01, 01, 01, 01, 01, 01, pytz.UTC) |
707 | + date = current_date + timedelta(days=3) |
708 | + lag = timedelta(days=4) |
709 | + matcher = DateIsInPast(current_date=current_date, lag=lag) |
710 | + self.assertEqual(None, matcher.match(date)) |
711 | + |
712 | + def test_mismatch_now_no_lag(self): |
713 | + date = datetime.now(pytz.UTC) + timedelta(days=3) |
714 | + matcher = DateIsInPast() |
715 | + self.assertIsInstance(matcher.match(date), DateIsNotInPast) |
716 | + |
717 | + def test_mismatch_now_with_lag(self): |
718 | + date = datetime.now(pytz.UTC) + timedelta(days=3) |
719 | + lag = timedelta(days=2) |
720 | + matcher = DateIsInPast(lag=lag) |
721 | + self.assertIsInstance(matcher.match(date), DateIsNotInPast) |
722 | + |
723 | + def test_mismatch_current_date_no_lag(self): |
724 | + current_date = datetime(2008, 01, 01, 01, 01, 01, 01, pytz.UTC) |
725 | + date = current_date + timedelta(days=3) |
726 | + matcher = DateIsInPast(current_date=current_date) |
727 | + self.assertIsInstance(matcher.match(date), DateIsNotInPast) |
728 | + |
729 | + def test_mismatch_current_date_with_lag(self): |
730 | + current_date = datetime(2008, 01, 01, 01, 01, 01, 01, pytz.UTC) |
731 | + date = current_date + timedelta(days=3) |
732 | + lag = timedelta(days=2) |
733 | + matcher = DateIsInPast(current_date=current_date, lag=lag) |
734 | + self.assertIsInstance(matcher.match(date), DateIsNotInPast) |
735 | + |
736 | + def test_mismatch_sets_date(self): |
737 | + date = datetime.now(pytz.UTC) + timedelta(days=3) |
738 | + matcher = DateIsInPast() |
739 | + self.assertEqual(date, matcher.match(date).date) |
740 | + |
741 | + def test_mismatch_sets_limit(self): |
742 | + current_date = datetime.now(pytz.UTC) |
743 | + date = current_date + timedelta(days=3) |
744 | + matcher = DateIsInPast(current_date=current_date) |
745 | + self.assertEqual(current_date, matcher.match(date).limit) |
PublishedStateIsNot is a little weird to export, being a mismatch - might want to think about the clarity for users there.
PublishedStateIs would add more debug value if it included all the failing elements, not just the first.
Typo 'supecseded'
This could be a regular method, not inline.
+ def spr_title(spr):
DateIsInPast would be good to do upstream - its not launchpad domain specific at all.
Please consider these tweaks and recommendations.