Merge lp:~james-w/launchpad/more-matchers into lp:launchpad

Proposed by James Westby
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
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/testing/factory.py
      32: redefinition of unused 'os' from line 31

Thanks,

James

To post a comment you must log in.
lp:~james-w/launchpad/more-matchers updated
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.

Revision history for this message
Robert Collins (lifeless) wrote :

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.

review: Approve
lp:~james-w/launchpad/more-matchers updated
11334. By James Westby

Rename PublishedStateIsNot to PublishedStateIsWrong 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.

Revision history for this message
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/archivepublisher/tests/test_dominator.py
Text conflict in lib/lp/archiveuploader/tests/nascentupload-ddebs.txt
Text conflict in lib/lp/soyuz/doc/build-files.txt
Text conflict in lib/lp/soyuz/doc/publishing.txt
Text conflict in lib/lp/soyuz/doc/sourcepackagerelease.txt
Text conflict in lib/lp/soyuz/scripts/tests/test_changeoverride.py
Text conflict in lib/lp/soyuz/scripts/tests/test_copypackage.py
Text conflict in lib/lp/soyuz/tests/test_publish_archive_indexes.py
Text conflict in lib/lp/soyuz/tests/test_publishing.py
Text conflict in lib/lp/testing/factory.py
Text conflict in lib/lp/testing/matchers.py
Text conflict in lib/lp/testing/tests/test_factory.py
Text conflict in lib/lp/testing/tests/test_matchers.py

Please resolve these and then ping me and I'll be happy to re-try the landing.

Revision history for this message
Robert Collins (lifeless) wrote :

This clearly needs more work to be usable :(.

Revision history for this message
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 PublishedStateIsWrong 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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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)