Merge lp:~jtv/launchpad/bug-884649-branch-3 into lp:launchpad
- bug-884649-branch-3
- Merge into devel
Status: | Superseded | ||||
---|---|---|---|---|---|
Proposed branch: | lp:~jtv/launchpad/bug-884649-branch-3 | ||||
Merge into: | lp:launchpad | ||||
Diff against target: |
722 lines (+357/-129) 2 files modified
lib/lp/archivepublisher/domination.py (+202/-101) lib/lp/archivepublisher/tests/test_dominator.py (+155/-28) |
||||
To merge this branch: | bzr merge lp:~jtv/launchpad/bug-884649-branch-3 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Launchpad code reviewers | Pending | ||
Review via email: mp+81134@code.launchpad.net |
This proposal has been superseded by a proposal from 2011-11-03.
Commit message
Limit 2nd binary-domination pass to packages with arch-indep publications.
Description of the change
= Summary =
Domination is slow. A major reason is the two-pass domination algorithm needed for binary publications.
== Proposed fix ==
The first binary-domination pass touches only architecture-
The second pass touches only architecture-
So: keep track during the first pass of which packages have architecture-
== Pre-implementation notes ==
This was Julian's idea.
== Implementation details ==
I made the second pass iterate over the intersection of “packages with multiple live publications” and “packages that were found during the first pass to have architecture-
That's not really supposed to happen, which is to say it can be helpful to be prepared for the case but it's not worth optimizing for.
== Tests ==
All the high-level desired outcomes and integration are tested in scenario tests; those remain unchanged because this is a functionally neutral optimization.
I did add one helper function, which is short but easy to mess up and so it gets its own series of tests.
{{{
./bin/test -vvc lp.archivepubli
}}}
== Demo and Q/A ==
Run the dominator. It should be tons faster, but still dominate even architecture-
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
Preview Diff
1 | === modified file 'lib/lp/archivepublisher/domination.py' | |||
2 | --- lib/lp/archivepublisher/domination.py 2011-11-02 12:58:31 +0000 | |||
3 | +++ lib/lp/archivepublisher/domination.py 2011-11-03 11:52:37 +0000 | |||
4 | @@ -52,8 +52,12 @@ | |||
5 | 52 | 52 | ||
6 | 53 | __all__ = ['Dominator'] | 53 | __all__ = ['Dominator'] |
7 | 54 | 54 | ||
8 | 55 | from collections import defaultdict | ||
9 | 55 | from datetime import timedelta | 56 | from datetime import timedelta |
11 | 56 | from operator import itemgetter | 57 | from operator import ( |
12 | 58 | attrgetter, | ||
13 | 59 | itemgetter, | ||
14 | 60 | ) | ||
15 | 57 | 61 | ||
16 | 58 | import apt_pkg | 62 | import apt_pkg |
17 | 59 | from storm.expr import ( | 63 | from storm.expr import ( |
18 | @@ -72,6 +76,7 @@ | |||
19 | 72 | DecoratedResultSet, | 76 | DecoratedResultSet, |
20 | 73 | ) | 77 | ) |
21 | 74 | from canonical.launchpad.interfaces.lpstorm import IStore | 78 | from canonical.launchpad.interfaces.lpstorm import IStore |
22 | 79 | from canonical.launchpad.utilities.orderingcheck import OrderingCheck | ||
23 | 75 | from lp.registry.model.sourcepackagename import SourcePackageName | 80 | from lp.registry.model.sourcepackagename import SourcePackageName |
24 | 76 | from lp.services.database.bulk import load_related | 81 | from lp.services.database.bulk import load_related |
25 | 77 | from lp.soyuz.enums import ( | 82 | from lp.soyuz.enums import ( |
26 | @@ -192,6 +197,109 @@ | |||
27 | 192 | else: | 197 | else: |
28 | 193 | return version_comparison | 198 | return version_comparison |
29 | 194 | 199 | ||
30 | 200 | def sortPublications(self, publications): | ||
31 | 201 | """Sort publications from most to least current versions.""" | ||
32 | 202 | # Listify; we want to iterate this twice, which won't do for a | ||
33 | 203 | # non-persistent sequence. | ||
34 | 204 | sorted_publications = list(publications) | ||
35 | 205 | # Batch-load associated package releases; we'll be needing them | ||
36 | 206 | # to compare versions. | ||
37 | 207 | self.load_releases(sorted_publications) | ||
38 | 208 | # Now sort. This is that second iteration. An in-place sort | ||
39 | 209 | # won't hurt the original, because we're working on a copy of | ||
40 | 210 | # the original iterable. | ||
41 | 211 | sorted_publications.sort(cmp=self.compare, reverse=True) | ||
42 | 212 | return sorted_publications | ||
43 | 213 | |||
44 | 214 | |||
45 | 215 | def find_live_source_versions(publications): | ||
46 | 216 | """Find versions out of Published `publications` that should stay live. | ||
47 | 217 | |||
48 | 218 | This particular notion of liveness applies to source domination: the | ||
49 | 219 | latest version stays live, and that's it. | ||
50 | 220 | |||
51 | 221 | :param publications: An iterable of `SourcePackagePublishingHistory` | ||
52 | 222 | sorted by descending package version. | ||
53 | 223 | :return: A list of live versions. | ||
54 | 224 | """ | ||
55 | 225 | # Given the required sort order, the latest version is at the head | ||
56 | 226 | # of the list. | ||
57 | 227 | return [publications[0].sourcepackagerelease.version] | ||
58 | 228 | |||
59 | 229 | |||
60 | 230 | def get_binary_versions(binary_publications): | ||
61 | 231 | """List versions for sequence of `BinaryPackagePublishingHistory`.""" | ||
62 | 232 | return [pub.binarypackagerelease.version for pub in binary_publications] | ||
63 | 233 | |||
64 | 234 | |||
65 | 235 | def find_live_binary_versions_pass_1(publications): | ||
66 | 236 | """Find versions out of Published `publications` that should stay live. | ||
67 | 237 | |||
68 | 238 | This particular notion of liveness applies to first-pass binary | ||
69 | 239 | domination: the latest version stays live, and so do publications of | ||
70 | 240 | binary packages for the "all" architecture. | ||
71 | 241 | |||
72 | 242 | :param publications: An iterable of `BinaryPackagePublishingHistory`, | ||
73 | 243 | sorted by descending package version. | ||
74 | 244 | :return: A list of live versions. | ||
75 | 245 | """ | ||
76 | 246 | publications = list(publications) | ||
77 | 247 | latest = publications.pop(0) | ||
78 | 248 | return get_binary_versions( | ||
79 | 249 | [latest] + [ | ||
80 | 250 | pub for pub in publications if not pub.architecture_specific]) | ||
81 | 251 | |||
82 | 252 | |||
83 | 253 | def find_live_binary_versions_pass_2(publications): | ||
84 | 254 | """Find versions out of Published `publications` that should stay live. | ||
85 | 255 | |||
86 | 256 | This particular notion of liveness applies to second-pass binary | ||
87 | 257 | domination: the latest version stays live, and architecture-specific | ||
88 | 258 | publications stay live (i.e, ones that are not for the "all" | ||
89 | 259 | architecture). | ||
90 | 260 | |||
91 | 261 | More importantly, any publication for binary packages of the "all" | ||
92 | 262 | architecture stay live if any of the non-"all" binary packages from | ||
93 | 263 | the same source package release are still active -- even if they are | ||
94 | 264 | for other architectures. | ||
95 | 265 | |||
96 | 266 | This is the raison d'etre for the two-pass binary domination algorithm: | ||
97 | 267 | to let us see which architecture-independent binary publications can be | ||
98 | 268 | superseded without rendering any architecture-specific binaries from the | ||
99 | 269 | same source package release uninstallable. | ||
100 | 270 | |||
101 | 271 | (Note that here, "active" includes Published publications but also | ||
102 | 272 | Pending ones. This is standard nomenclature in Soyuz. Some of the | ||
103 | 273 | domination code confuses matters by using the term "active" to mean only | ||
104 | 274 | Published publications). | ||
105 | 275 | |||
106 | 276 | :param publications: An iterable of `BinaryPackagePublishingHistory`, | ||
107 | 277 | sorted by descending package version. | ||
108 | 278 | :return: A list of live versions. | ||
109 | 279 | """ | ||
110 | 280 | publications = list(publications) | ||
111 | 281 | latest = publications.pop(0) | ||
112 | 282 | is_arch_specific = attrgetter('architecture_specific') | ||
113 | 283 | arch_specific_pubs = filter(is_arch_specific, publications) | ||
114 | 284 | arch_indep_pubs = filter( | ||
115 | 285 | lambda pub: not is_arch_specific(pub), | ||
116 | 286 | publications) | ||
117 | 287 | |||
118 | 288 | # XXX JeroenVermeulen 2011-11-01 bug=884649: This is likely to be | ||
119 | 289 | # costly, and the result could be reused for all builds of the same | ||
120 | 290 | # source package release to all architectures. | ||
121 | 291 | reprieved_pubs = [ | ||
122 | 292 | pub | ||
123 | 293 | for pub in arch_indep_pubs | ||
124 | 294 | if pub.getOtherPublicationsForSameSource().any()] | ||
125 | 295 | |||
126 | 296 | return get_binary_versions([latest] + arch_specific_pubs + reprieved_pubs) | ||
127 | 297 | |||
128 | 298 | |||
129 | 299 | def contains_arch_indep(bpphs): | ||
130 | 300 | """Are any of the publications among `bpphs` architecture-independent?""" | ||
131 | 301 | return any(not bpph.architecture_specific for bpph in bpphs) | ||
132 | 302 | |||
133 | 195 | 303 | ||
134 | 196 | class Dominator: | 304 | class Dominator: |
135 | 197 | """Manage the process of marking packages as superseded. | 305 | """Manage the process of marking packages as superseded. |
136 | @@ -209,27 +317,6 @@ | |||
137 | 209 | self.logger = logger | 317 | self.logger = logger |
138 | 210 | self.archive = archive | 318 | self.archive = archive |
139 | 211 | 319 | ||
140 | 212 | def _checkArchIndep(self, publication): | ||
141 | 213 | """Return True if the binary publication can be superseded. | ||
142 | 214 | |||
143 | 215 | If the publication is an arch-indep binary, we can only supersede | ||
144 | 216 | it if all the binaries from the same source are also superseded, | ||
145 | 217 | else those binaries may become uninstallable. | ||
146 | 218 | See bug 34086. | ||
147 | 219 | """ | ||
148 | 220 | binary = publication.binarypackagerelease | ||
149 | 221 | if not binary.architecturespecific: | ||
150 | 222 | # getOtherPublicationsForSameSource returns PENDING in | ||
151 | 223 | # addition to PUBLISHED binaries, and we rely on this since | ||
152 | 224 | # they must also block domination. | ||
153 | 225 | others = publication.getOtherPublicationsForSameSource() | ||
154 | 226 | if others.any(): | ||
155 | 227 | # Don't dominate this arch:all binary as there are | ||
156 | 228 | # other arch-specific binaries from the same build | ||
157 | 229 | # that are still active. | ||
158 | 230 | return False | ||
159 | 231 | return True | ||
160 | 232 | |||
161 | 233 | def dominatePackage(self, publications, live_versions, generalization): | 320 | def dominatePackage(self, publications, live_versions, generalization): |
162 | 234 | """Dominate publications for a single package. | 321 | """Dominate publications for a single package. |
163 | 235 | 322 | ||
164 | @@ -247,34 +334,33 @@ | |||
165 | 247 | 334 | ||
166 | 248 | :param publications: Iterable of publications for the same package, | 335 | :param publications: Iterable of publications for the same package, |
167 | 249 | in the same archive, series, and pocket, all with status | 336 | in the same archive, series, and pocket, all with status |
174 | 250 | `PackagePublishingStatus.PUBLISHED`. | 337 | `PackagePublishingStatus.PUBLISHED`. They must be sorted from |
175 | 251 | :param live_versions: Iterable of version strings that are still | 338 | most current to least current, as would be the result of |
176 | 252 | considered live for this package. The given publications will | 339 | `generalization.sortPublications`. |
177 | 253 | remain active insofar as they represent any of these versions; | 340 | :param live_versions: Iterable of versions that are still considered |
178 | 254 | older publications will be marked as superseded and newer ones | 341 | "live" for this package. For any of these, the latest publication |
179 | 255 | as deleted. | 342 | among `publications` will remain Published. Publications for |
180 | 343 | older releases, as well as older publications of live versions, | ||
181 | 344 | will be marked as Superseded. Publications of newer versions than | ||
182 | 345 | are listed in `live_versions` are marked as Deleted. | ||
183 | 256 | :param generalization: A `GeneralizedPublication` helper representing | 346 | :param generalization: A `GeneralizedPublication` helper representing |
185 | 257 | the kind of publications these are--source or binary. | 347 | the kind of publications these are: source or binary. |
186 | 258 | """ | 348 | """ |
197 | 259 | publications = list(publications) | 349 | live_versions = frozenset(live_versions) |
188 | 260 | generalization.load_releases(publications) | ||
189 | 261 | |||
190 | 262 | # Go through publications from latest version to oldest. This | ||
191 | 263 | # makes it easy to figure out which release superseded which: | ||
192 | 264 | # the dominant is always the oldest live release that is newer | ||
193 | 265 | # than the one being superseded. In this loop, that means the | ||
194 | 266 | # dominant is always the last live publication we saw. | ||
195 | 267 | publications = sorted( | ||
196 | 268 | publications, cmp=generalization.compare, reverse=True) | ||
198 | 269 | 350 | ||
199 | 270 | self.logger.debug( | 351 | self.logger.debug( |
200 | 271 | "Package has %d live publication(s). Live versions: %s", | 352 | "Package has %d live publication(s). Live versions: %s", |
201 | 272 | len(publications), live_versions) | 353 | len(publications), live_versions) |
202 | 273 | 354 | ||
203 | 355 | # Verify that the publications are really sorted properly. | ||
204 | 356 | check_order = OrderingCheck(cmp=generalization.compare, reverse=True) | ||
205 | 357 | |||
206 | 274 | current_dominant = None | 358 | current_dominant = None |
207 | 275 | dominant_version = None | 359 | dominant_version = None |
208 | 276 | 360 | ||
209 | 277 | for pub in publications: | 361 | for pub in publications: |
210 | 362 | check_order.check(pub) | ||
211 | 363 | |||
212 | 278 | version = generalization.getPackageVersion(pub) | 364 | version = generalization.getPackageVersion(pub) |
213 | 279 | # There should never be two published releases with the same | 365 | # There should never be two published releases with the same |
214 | 280 | # version. So it doesn't matter whether this comparison is | 366 | # version. So it doesn't matter whether this comparison is |
215 | @@ -295,11 +381,6 @@ | |||
216 | 295 | current_dominant = pub | 381 | current_dominant = pub |
217 | 296 | dominant_version = version | 382 | dominant_version = version |
218 | 297 | self.logger.debug2("Keeping version %s.", version) | 383 | self.logger.debug2("Keeping version %s.", version) |
219 | 298 | elif not (generalization.is_source or self._checkArchIndep(pub)): | ||
220 | 299 | # As a special case, we keep this version live as well. | ||
221 | 300 | current_dominant = pub | ||
222 | 301 | dominant_version = version | ||
223 | 302 | self.logger.debug2("Keeping version %s.", version) | ||
224 | 303 | elif current_dominant is None: | 384 | elif current_dominant is None: |
225 | 304 | # This publication is no longer live, but there is no | 385 | # This publication is no longer live, but there is no |
226 | 305 | # newer version to supersede it either. Therefore it | 386 | # newer version to supersede it either. Therefore it |
227 | @@ -312,50 +393,32 @@ | |||
228 | 312 | pub.supersede(current_dominant, logger=self.logger) | 393 | pub.supersede(current_dominant, logger=self.logger) |
229 | 313 | self.logger.debug2("Superseding version %s.", version) | 394 | self.logger.debug2("Superseding version %s.", version) |
230 | 314 | 395 | ||
275 | 315 | def _dominatePublications(self, pubs, generalization): | 396 | def _sortPackages(self, publications, generalization): |
276 | 316 | """Perform dominations for the given publications. | 397 | """Partition publications by package name, and sort them. |
277 | 317 | 398 | ||
278 | 318 | Keep the latest published version for each package active, | 399 | The publications are sorted from most current to least current, |
279 | 319 | superseding older versions. | 400 | as required by `dominatePackage` etc. |
280 | 320 | 401 | ||
281 | 321 | :param pubs: A dict mapping names to a list of publications. Every | 402 | :param publications: An iterable of `SourcePackagePublishingHistory` |
282 | 322 | publication must be PUBLISHED or PENDING, and the first in each | 403 | or of `BinaryPackagePublishingHistory`. |
283 | 323 | list will be treated as dominant (so should be the latest). | 404 | :param generalization: A `GeneralizedPublication` helper representing |
284 | 324 | :param generalization: A `GeneralizedPublication` helper representing | 405 | the kind of publications these are: source or binary. |
285 | 325 | the kind of publications these are--source or binary. | 406 | :return: A dict mapping each package name to a sorted list of |
286 | 326 | """ | 407 | publications from `publications`. |
287 | 327 | self.logger.debug("Dominating packages...") | 408 | """ |
288 | 328 | for name, publications in pubs.iteritems(): | 409 | pubs_by_package = defaultdict(list) |
289 | 329 | assert publications, "Empty list of publications for %s." % name | 410 | for pub in publications: |
290 | 330 | # Since this always picks the latest version as the live | 411 | pubs_by_package[generalization.getPackageName(pub)].append(pub) |
291 | 331 | # one, this dominatePackage call will never result in a | 412 | |
292 | 332 | # deletion. | 413 | # Sort the publication lists. This is not an in-place sort, so |
293 | 333 | latest_version = generalization.getPackageVersion(publications[0]) | 414 | # it involves altering the dict while we iterate it. Listify |
294 | 334 | self.logger.debug2("Dominating %s" % name) | 415 | # the keys so that we can be sure that we're not altering the |
295 | 335 | self.dominatePackage( | 416 | # iteration order while iteration is underway. |
296 | 336 | publications, [latest_version], generalization) | 417 | for package in list(pubs_by_package.keys()): |
297 | 337 | 418 | pubs_by_package[package] = generalization.sortPublications( | |
298 | 338 | def _sortPackages(self, pkglist, generalization): | 419 | pubs_by_package[package]) |
299 | 339 | """Map out packages by name, and sort by descending version. | 420 | |
300 | 340 | 421 | return pubs_by_package | |
257 | 341 | :param pkglist: An iterable of `SourcePackagePublishingHistory` or | ||
258 | 342 | `BinaryPackagePublishingHistory`. | ||
259 | 343 | :param generalization: A `GeneralizedPublication` helper representing | ||
260 | 344 | the kind of publications these are--source or binary. | ||
261 | 345 | :return: A dict mapping each package name to a list of publications | ||
262 | 346 | from `pkglist`, newest first. | ||
263 | 347 | """ | ||
264 | 348 | self.logger.debug("Sorting packages...") | ||
265 | 349 | |||
266 | 350 | outpkgs = {} | ||
267 | 351 | for inpkg in pkglist: | ||
268 | 352 | key = generalization.getPackageName(inpkg) | ||
269 | 353 | outpkgs.setdefault(key, []).append(inpkg) | ||
270 | 354 | |||
271 | 355 | for package_pubs in outpkgs.itervalues(): | ||
272 | 356 | package_pubs.sort(cmp=generalization.compare, reverse=True) | ||
273 | 357 | |||
274 | 358 | return outpkgs | ||
301 | 359 | 422 | ||
302 | 360 | def _setScheduledDeletionDate(self, pub_record): | 423 | def _setScheduledDeletionDate(self, pub_record): |
303 | 361 | """Set the scheduleddeletiondate on a publishing record. | 424 | """Set the scheduleddeletiondate on a publishing record. |
304 | @@ -510,6 +573,18 @@ | |||
305 | 510 | """ | 573 | """ |
306 | 511 | generalization = GeneralizedPublication(is_source=False) | 574 | generalization = GeneralizedPublication(is_source=False) |
307 | 512 | 575 | ||
308 | 576 | # Domination happens in two passes. The first tries to | ||
309 | 577 | # supersede architecture-dependent publications; the second | ||
310 | 578 | # tries to supersede architecture-independent ones. An | ||
311 | 579 | # architecture-independent pub is kept alive as long as any | ||
312 | 580 | # architecture-dependent pubs from the same source package build | ||
313 | 581 | # are still live for any architecture, because they may depend | ||
314 | 582 | # on the architecture-independent package. | ||
315 | 583 | # Thus we limit the second pass to those packages that have | ||
316 | 584 | # published, architecture-independent publications; anything | ||
317 | 585 | # else will have completed domination in the first pass. | ||
318 | 586 | packages_w_arch_indep = set() | ||
319 | 587 | |||
320 | 513 | for distroarchseries in distroseries.architectures: | 588 | for distroarchseries in distroseries.architectures: |
321 | 514 | self.logger.info( | 589 | self.logger.info( |
322 | 515 | "Performing domination across %s/%s (%s)", | 590 | "Performing domination across %s/%s (%s)", |
323 | @@ -520,21 +595,34 @@ | |||
324 | 520 | bins = self.findBinariesForDomination(distroarchseries, pocket) | 595 | bins = self.findBinariesForDomination(distroarchseries, pocket) |
325 | 521 | sorted_packages = self._sortPackages(bins, generalization) | 596 | sorted_packages = self._sortPackages(bins, generalization) |
326 | 522 | self.logger.info("Dominating binaries...") | 597 | self.logger.info("Dominating binaries...") |
336 | 523 | self._dominatePublications(sorted_packages, generalization) | 598 | for name, pubs in sorted_packages.iteritems(): |
337 | 524 | 599 | self.logger.debug("Dominating %s" % name) | |
338 | 525 | # We need to make a second pass to cover the cases where: | 600 | assert len(pubs) > 0, "Dominating zero binaries!" |
339 | 526 | # * arch-specific binaries were not all dominated before arch-all | 601 | live_versions = find_live_binary_versions_pass_1(pubs) |
340 | 527 | # ones that depend on them | 602 | self.dominatePackage(pubs, live_versions, generalization) |
341 | 528 | # * An arch-all turned into an arch-specific, or vice-versa | 603 | if contains_arch_indep(pubs): |
342 | 529 | # * A package is completely schizophrenic and changes all of | 604 | packages_w_arch_indep.add(name) |
343 | 530 | # its binaries between arch-all and arch-any (apparently | 605 | |
344 | 531 | # occurs sometimes!) | 606 | packages_w_arch_indep = frozenset(packages_w_arch_indep) |
345 | 607 | |||
346 | 608 | # The second pass attempts to supersede arch-all publications of | ||
347 | 609 | # older versions, from source package releases that no longer | ||
348 | 610 | # have any active arch-specific publications that might depend | ||
349 | 611 | # on the arch-indep ones. | ||
350 | 612 | # (In maintaining this code, bear in mind that some or all of a | ||
351 | 613 | # source package's binary packages may switch between | ||
352 | 614 | # arch-specific and arch-indep between releases.) | ||
353 | 532 | for distroarchseries in distroseries.architectures: | 615 | for distroarchseries in distroseries.architectures: |
354 | 533 | self.logger.info("Finding binaries...(2nd pass)") | 616 | self.logger.info("Finding binaries...(2nd pass)") |
355 | 534 | bins = self.findBinariesForDomination(distroarchseries, pocket) | 617 | bins = self.findBinariesForDomination(distroarchseries, pocket) |
356 | 535 | sorted_packages = self._sortPackages(bins, generalization) | 618 | sorted_packages = self._sortPackages(bins, generalization) |
357 | 536 | self.logger.info("Dominating binaries...(2nd pass)") | 619 | self.logger.info("Dominating binaries...(2nd pass)") |
359 | 537 | self._dominatePublications(sorted_packages, generalization) | 620 | for name in packages_w_arch_indep.intersection(sorted_packages): |
360 | 621 | pubs = sorted_packages[name] | ||
361 | 622 | self.logger.debug("Dominating %s" % name) | ||
362 | 623 | assert len(pubs) > 0, "Dominating zero binaries in 2nd pass!" | ||
363 | 624 | live_versions = find_live_binary_versions_pass_2(pubs) | ||
364 | 625 | self.dominatePackage(pubs, live_versions, generalization) | ||
365 | 538 | 626 | ||
366 | 539 | def _composeActiveSourcePubsCondition(self, distroseries, pocket): | 627 | def _composeActiveSourcePubsCondition(self, distroseries, pocket): |
367 | 540 | """Compose ORM condition for restricting relevant source pubs.""" | 628 | """Compose ORM condition for restricting relevant source pubs.""" |
368 | @@ -550,7 +638,12 @@ | |||
369 | 550 | ) | 638 | ) |
370 | 551 | 639 | ||
371 | 552 | def findSourcesForDomination(self, distroseries, pocket): | 640 | def findSourcesForDomination(self, distroseries, pocket): |
373 | 553 | """Find binary publications that need dominating.""" | 641 | """Find binary publications that need dominating. |
374 | 642 | |||
375 | 643 | This is only for traditional domination, where the latest published | ||
376 | 644 | publication is always kept published. It will ignore publications | ||
377 | 645 | that have no other publications competing for the same binary package. | ||
378 | 646 | """ | ||
379 | 554 | # Avoid circular imports. | 647 | # Avoid circular imports. |
380 | 555 | from lp.soyuz.model.publishing import SourcePackagePublishingHistory | 648 | from lp.soyuz.model.publishing import SourcePackagePublishingHistory |
381 | 556 | 649 | ||
382 | @@ -589,11 +682,18 @@ | |||
383 | 589 | distroseries.name, pocket.title) | 682 | distroseries.name, pocket.title) |
384 | 590 | 683 | ||
385 | 591 | generalization = GeneralizedPublication(is_source=True) | 684 | generalization = GeneralizedPublication(is_source=True) |
386 | 685 | |||
387 | 686 | self.logger.debug("Finding sources...") | ||
388 | 592 | sources = self.findSourcesForDomination(distroseries, pocket) | 687 | sources = self.findSourcesForDomination(distroseries, pocket) |
389 | 688 | sorted_packages = self._sortPackages(sources, generalization) | ||
390 | 593 | 689 | ||
391 | 594 | self.logger.debug("Dominating sources...") | 690 | self.logger.debug("Dominating sources...") |
394 | 595 | self._dominatePublications( | 691 | for name, pubs in sorted_packages.iteritems(): |
395 | 596 | self._sortPackages(sources, generalization), generalization) | 692 | self.logger.debug("Dominating %s" % name) |
396 | 693 | assert len(pubs) > 0, "Dominating zero binaries!" | ||
397 | 694 | live_versions = find_live_source_versions(pubs) | ||
398 | 695 | self.dominatePackage(pubs, live_versions, generalization) | ||
399 | 696 | |||
400 | 597 | flush_database_updates() | 697 | flush_database_updates() |
401 | 598 | 698 | ||
402 | 599 | def findPublishedSourcePackageNames(self, distroseries, pocket): | 699 | def findPublishedSourcePackageNames(self, distroseries, pocket): |
403 | @@ -653,6 +753,7 @@ | |||
404 | 653 | """ | 753 | """ |
405 | 654 | generalization = GeneralizedPublication(is_source=True) | 754 | generalization = GeneralizedPublication(is_source=True) |
406 | 655 | pubs = self.findPublishedSPPHs(distroseries, pocket, package_name) | 755 | pubs = self.findPublishedSPPHs(distroseries, pocket, package_name) |
407 | 756 | pubs = generalization.sortPublications(pubs) | ||
408 | 656 | self.dominatePackage(pubs, live_versions, generalization) | 757 | self.dominatePackage(pubs, live_versions, generalization) |
409 | 657 | 758 | ||
410 | 658 | def judge(self, distroseries, pocket): | 759 | def judge(self, distroseries, pocket): |
411 | 659 | 760 | ||
412 | === modified file 'lib/lp/archivepublisher/tests/test_dominator.py' | |||
413 | --- lib/lp/archivepublisher/tests/test_dominator.py 2011-11-02 10:28:31 +0000 | |||
414 | +++ lib/lp/archivepublisher/tests/test_dominator.py 2011-11-03 11:52:37 +0000 | |||
415 | @@ -15,7 +15,11 @@ | |||
416 | 15 | from canonical.database.sqlbase import flush_database_updates | 15 | from canonical.database.sqlbase import flush_database_updates |
417 | 16 | from canonical.testing.layers import ZopelessDatabaseLayer | 16 | from canonical.testing.layers import ZopelessDatabaseLayer |
418 | 17 | from lp.archivepublisher.domination import ( | 17 | from lp.archivepublisher.domination import ( |
419 | 18 | contains_arch_indep, | ||
420 | 18 | Dominator, | 19 | Dominator, |
421 | 20 | find_live_binary_versions_pass_1, | ||
422 | 21 | find_live_binary_versions_pass_2, | ||
423 | 22 | find_live_source_versions, | ||
424 | 19 | GeneralizedPublication, | 23 | GeneralizedPublication, |
425 | 20 | STAY_OF_EXECUTION, | 24 | STAY_OF_EXECUTION, |
426 | 21 | ) | 25 | ) |
427 | @@ -30,6 +34,7 @@ | |||
428 | 30 | StormStatementRecorder, | 34 | StormStatementRecorder, |
429 | 31 | TestCaseWithFactory, | 35 | TestCaseWithFactory, |
430 | 32 | ) | 36 | ) |
431 | 37 | from lp.testing.fakemethod import FakeMethod | ||
432 | 33 | from lp.testing.matchers import HasQueryCount | 38 | from lp.testing.matchers import HasQueryCount |
433 | 34 | 39 | ||
434 | 35 | 40 | ||
435 | @@ -72,13 +77,9 @@ | |||
436 | 72 | is_source=ISourcePackagePublishingHistory.providedBy(dominant)) | 77 | is_source=ISourcePackagePublishingHistory.providedBy(dominant)) |
437 | 73 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) | 78 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
438 | 74 | 79 | ||
446 | 75 | # The _dominate* test methods require a dictionary where the | 80 | pubs = [dominant, dominated] |
447 | 76 | # package name is the key. The key's value is a list of | 81 | live_versions = [generalization.getPackageVersion(dominant)] |
448 | 77 | # source or binary packages representing dominant, the first element | 82 | dominator.dominatePackage(pubs, live_versions, generalization) |
442 | 78 | # and dominated, the subsequents. | ||
443 | 79 | pubs = {'foo': [dominant, dominated]} | ||
444 | 80 | |||
445 | 81 | dominator._dominatePublications(pubs, generalization) | ||
449 | 82 | flush_database_updates() | 83 | flush_database_updates() |
450 | 83 | 84 | ||
451 | 84 | # The dominant version remains correctly published. | 85 | # The dominant version remains correctly published. |
452 | @@ -158,16 +159,30 @@ | |||
453 | 158 | [foo_10_source] + foo_10_binaries, | 159 | [foo_10_source] + foo_10_binaries, |
454 | 159 | PackagePublishingStatus.SUPERSEDED) | 160 | PackagePublishingStatus.SUPERSEDED) |
455 | 160 | 161 | ||
466 | 161 | def testEmptyDomination(self): | 162 | def test_dominateBinaries_rejects_empty_publication_list(self): |
467 | 162 | """Domination asserts for not empty input list.""" | 163 | """Domination asserts for non-empty input list.""" |
468 | 163 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) | 164 | package = self.factory.makeBinaryPackageName() |
469 | 164 | pubs = {'foo': []} | 165 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
470 | 165 | # This isn't a really good exception. It should probably be | 166 | dominator._sortPackages = FakeMethod({package.name: []}) |
471 | 166 | # something more indicative of bad input. | 167 | # This isn't a really good exception. It should probably be |
472 | 167 | self.assertRaises( | 168 | # something more indicative of bad input. |
473 | 168 | AssertionError, | 169 | self.assertRaises( |
474 | 169 | dominator._dominatePublications, | 170 | AssertionError, |
475 | 170 | pubs, GeneralizedPublication(True)) | 171 | dominator.dominateBinaries, |
476 | 172 | self.factory.makeDistroArchSeries().distroseries, | ||
477 | 173 | self.factory.getAnyPocket()) | ||
478 | 174 | |||
479 | 175 | def test_dominateSources_rejects_empty_publication_list(self): | ||
480 | 176 | """Domination asserts for non-empty input list.""" | ||
481 | 177 | package = self.factory.makeSourcePackageName() | ||
482 | 178 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) | ||
483 | 179 | dominator._sortPackages = FakeMethod({package.name: []}) | ||
484 | 180 | # This isn't a really good exception. It should probably be | ||
485 | 181 | # something more indicative of bad input. | ||
486 | 182 | self.assertRaises( | ||
487 | 183 | AssertionError, | ||
488 | 184 | dominator.dominateSources, | ||
489 | 185 | self.factory.makeDistroSeries(), self.factory.getAnyPocket()) | ||
490 | 171 | 186 | ||
491 | 172 | def test_archall_domination(self): | 187 | def test_archall_domination(self): |
492 | 173 | # Arch-all binaries should not be dominated when a new source | 188 | # Arch-all binaries should not be dominated when a new source |
493 | @@ -358,6 +373,16 @@ | |||
494 | 358 | SeriesStatus.OBSOLETE) | 373 | SeriesStatus.OBSOLETE) |
495 | 359 | 374 | ||
496 | 360 | 375 | ||
497 | 376 | def remove_security_proxies(proxied_objects): | ||
498 | 377 | """Return list of `proxied_objects`, without their proxies. | ||
499 | 378 | |||
500 | 379 | The dominator runs only in scripts, where security proxies don't get | ||
501 | 380 | in the way. To test realistically for this environment, strip the | ||
502 | 381 | proxies wherever necessary and do as you will. | ||
503 | 382 | """ | ||
504 | 383 | return [removeSecurityProxy(obj) for obj in proxied_objects] | ||
505 | 384 | |||
506 | 385 | |||
507 | 361 | def make_spphs_for_versions(factory, versions): | 386 | def make_spphs_for_versions(factory, versions): |
508 | 362 | """Create publication records for each of `versions`. | 387 | """Create publication records for each of `versions`. |
509 | 363 | 388 | ||
510 | @@ -400,14 +425,15 @@ | |||
511 | 400 | archive = das.distroseries.main_archive | 425 | archive = das.distroseries.main_archive |
512 | 401 | pocket = factory.getAnyPocket() | 426 | pocket = factory.getAnyPocket() |
513 | 402 | bprs = [ | 427 | bprs = [ |
515 | 403 | factory.makeBinaryPackageRelease(binarypackagename=bpn) | 428 | factory.makeBinaryPackageRelease( |
516 | 429 | binarypackagename=bpn, version=version) | ||
517 | 404 | for version in versions] | 430 | for version in versions] |
519 | 405 | return [ | 431 | return remove_security_proxies([ |
520 | 406 | factory.makeBinaryPackagePublishingHistory( | 432 | factory.makeBinaryPackagePublishingHistory( |
521 | 407 | binarypackagerelease=bpr, binarypackagename=bpn, | 433 | binarypackagerelease=bpr, binarypackagename=bpn, |
522 | 408 | distroarchseries=das, pocket=pocket, archive=archive, | 434 | distroarchseries=das, pocket=pocket, archive=archive, |
523 | 409 | sourcepackagename=spn, status=PackagePublishingStatus.PUBLISHED) | 435 | sourcepackagename=spn, status=PackagePublishingStatus.PUBLISHED) |
525 | 410 | for bpr in bprs] | 436 | for bpr in bprs]) |
526 | 411 | 437 | ||
527 | 412 | 438 | ||
528 | 413 | def list_source_versions(spphs): | 439 | def list_source_versions(spphs): |
529 | @@ -591,9 +617,10 @@ | |||
530 | 591 | def test_dominatePackage_supersedes_older_pub_with_newer_live_pub(self): | 617 | def test_dominatePackage_supersedes_older_pub_with_newer_live_pub(self): |
531 | 592 | # When marking a package as superseded, dominatePackage | 618 | # When marking a package as superseded, dominatePackage |
532 | 593 | # designates a newer live version as the superseding version. | 619 | # designates a newer live version as the superseding version. |
533 | 620 | generalization = GeneralizedPublication(True) | ||
534 | 594 | pubs = make_spphs_for_versions(self.factory, ['1.0', '1.1']) | 621 | pubs = make_spphs_for_versions(self.factory, ['1.0', '1.1']) |
535 | 595 | self.makeDominator(pubs).dominatePackage( | 622 | self.makeDominator(pubs).dominatePackage( |
537 | 596 | pubs, ['1.1'], GeneralizedPublication(True)) | 623 | generalization.sortPublications(pubs), ['1.1'], generalization) |
538 | 597 | self.assertEqual(PackagePublishingStatus.SUPERSEDED, pubs[0].status) | 624 | self.assertEqual(PackagePublishingStatus.SUPERSEDED, pubs[0].status) |
539 | 598 | self.assertEqual(pubs[1].sourcepackagerelease, pubs[0].supersededby) | 625 | self.assertEqual(pubs[1].sourcepackagerelease, pubs[0].supersededby) |
540 | 599 | self.assertEqual(PackagePublishingStatus.PUBLISHED, pubs[1].status) | 626 | self.assertEqual(PackagePublishingStatus.PUBLISHED, pubs[1].status) |
541 | @@ -601,10 +628,11 @@ | |||
542 | 601 | def test_dominatePackage_only_supersedes_with_live_pub(self): | 628 | def test_dominatePackage_only_supersedes_with_live_pub(self): |
543 | 602 | # When marking a package as superseded, dominatePackage will | 629 | # When marking a package as superseded, dominatePackage will |
544 | 603 | # only pick a live version as the superseding one. | 630 | # only pick a live version as the superseding one. |
545 | 631 | generalization = GeneralizedPublication(True) | ||
546 | 604 | pubs = make_spphs_for_versions( | 632 | pubs = make_spphs_for_versions( |
547 | 605 | self.factory, ['1.0', '2.0', '3.0', '4.0']) | 633 | self.factory, ['1.0', '2.0', '3.0', '4.0']) |
548 | 606 | self.makeDominator(pubs).dominatePackage( | 634 | self.makeDominator(pubs).dominatePackage( |
550 | 607 | pubs, ['3.0'], GeneralizedPublication(True)) | 635 | generalization.sortPublications(pubs), ['3.0'], generalization) |
551 | 608 | self.assertEqual([ | 636 | self.assertEqual([ |
552 | 609 | pubs[2].sourcepackagerelease, | 637 | pubs[2].sourcepackagerelease, |
553 | 610 | pubs[2].sourcepackagerelease, | 638 | pubs[2].sourcepackagerelease, |
554 | @@ -616,23 +644,27 @@ | |||
555 | 616 | def test_dominatePackage_supersedes_with_oldest_newer_live_pub(self): | 644 | def test_dominatePackage_supersedes_with_oldest_newer_live_pub(self): |
556 | 617 | # When marking a package as superseded, dominatePackage picks | 645 | # When marking a package as superseded, dominatePackage picks |
557 | 618 | # the oldest of the newer, live versions as the superseding one. | 646 | # the oldest of the newer, live versions as the superseding one. |
558 | 647 | generalization = GeneralizedPublication(True) | ||
559 | 619 | pubs = make_spphs_for_versions(self.factory, ['2.7', '2.8', '2.9']) | 648 | pubs = make_spphs_for_versions(self.factory, ['2.7', '2.8', '2.9']) |
560 | 620 | self.makeDominator(pubs).dominatePackage( | 649 | self.makeDominator(pubs).dominatePackage( |
562 | 621 | pubs, ['2.8', '2.9'], GeneralizedPublication(True)) | 650 | generalization.sortPublications(pubs), ['2.8', '2.9'], |
563 | 651 | generalization) | ||
564 | 622 | self.assertEqual(pubs[1].sourcepackagerelease, pubs[0].supersededby) | 652 | self.assertEqual(pubs[1].sourcepackagerelease, pubs[0].supersededby) |
565 | 623 | 653 | ||
566 | 624 | def test_dominatePackage_only_supersedes_with_newer_live_pub(self): | 654 | def test_dominatePackage_only_supersedes_with_newer_live_pub(self): |
567 | 625 | # When marking a package as superseded, dominatePackage only | 655 | # When marking a package as superseded, dominatePackage only |
568 | 626 | # considers a newer version as the superseding one. | 656 | # considers a newer version as the superseding one. |
569 | 657 | generalization = GeneralizedPublication(True) | ||
570 | 627 | pubs = make_spphs_for_versions(self.factory, ['0.1', '0.2']) | 658 | pubs = make_spphs_for_versions(self.factory, ['0.1', '0.2']) |
571 | 628 | self.makeDominator(pubs).dominatePackage( | 659 | self.makeDominator(pubs).dominatePackage( |
573 | 629 | pubs, ['0.1'], GeneralizedPublication(True)) | 660 | generalization.sortPublications(pubs), ['0.1'], generalization) |
574 | 630 | self.assertEqual(None, pubs[1].supersededby) | 661 | self.assertEqual(None, pubs[1].supersededby) |
575 | 631 | self.assertEqual(PackagePublishingStatus.DELETED, pubs[1].status) | 662 | self.assertEqual(PackagePublishingStatus.DELETED, pubs[1].status) |
576 | 632 | 663 | ||
577 | 633 | def test_dominatePackage_supersedes_replaced_pub_for_live_version(self): | 664 | def test_dominatePackage_supersedes_replaced_pub_for_live_version(self): |
578 | 634 | # Even if a publication record is for a live version, a newer | 665 | # Even if a publication record is for a live version, a newer |
579 | 635 | # one for the same version supersedes it. | 666 | # one for the same version supersedes it. |
580 | 667 | generalization = GeneralizedPublication(True) | ||
581 | 636 | spr = self.factory.makeSourcePackageRelease() | 668 | spr = self.factory.makeSourcePackageRelease() |
582 | 637 | series = self.factory.makeDistroSeries() | 669 | series = self.factory.makeDistroSeries() |
583 | 638 | pocket = PackagePublishingPocket.RELEASE | 670 | pocket = PackagePublishingPocket.RELEASE |
584 | @@ -649,7 +681,8 @@ | |||
585 | 649 | ]) | 681 | ]) |
586 | 650 | 682 | ||
587 | 651 | self.makeDominator(pubs).dominatePackage( | 683 | self.makeDominator(pubs).dominatePackage( |
589 | 652 | pubs, [spr.version], GeneralizedPublication(True)) | 684 | generalization.sortPublications(pubs), [spr.version], |
590 | 685 | generalization) | ||
591 | 653 | self.assertEqual([ | 686 | self.assertEqual([ |
592 | 654 | PackagePublishingStatus.SUPERSEDED, | 687 | PackagePublishingStatus.SUPERSEDED, |
593 | 655 | PackagePublishingStatus.SUPERSEDED, | 688 | PackagePublishingStatus.SUPERSEDED, |
594 | @@ -661,12 +694,13 @@ | |||
595 | 661 | 694 | ||
596 | 662 | def test_dominatePackage_is_efficient(self): | 695 | def test_dominatePackage_is_efficient(self): |
597 | 663 | # dominatePackage avoids issuing too many queries. | 696 | # dominatePackage avoids issuing too many queries. |
598 | 697 | generalization = GeneralizedPublication(True) | ||
599 | 664 | versions = ["1.%s" % revision for revision in xrange(5)] | 698 | versions = ["1.%s" % revision for revision in xrange(5)] |
600 | 665 | pubs = make_spphs_for_versions(self.factory, versions) | 699 | pubs = make_spphs_for_versions(self.factory, versions) |
601 | 666 | with StormStatementRecorder() as recorder: | 700 | with StormStatementRecorder() as recorder: |
602 | 667 | self.makeDominator(pubs).dominatePackage( | 701 | self.makeDominator(pubs).dominatePackage( |
605 | 668 | pubs, versions[2:-1], | 702 | generalization.sortPublications(pubs), versions[2:-1], |
606 | 669 | GeneralizedPublication(True)) | 703 | generalization) |
607 | 670 | self.assertThat(recorder, HasQueryCount(LessThan(5))) | 704 | self.assertThat(recorder, HasQueryCount(LessThan(5))) |
608 | 671 | 705 | ||
609 | 672 | def test_dominatePackage_advanced_scenario(self): | 706 | def test_dominatePackage_advanced_scenario(self): |
610 | @@ -677,6 +711,7 @@ | |||
611 | 677 | # don't just patch up the code or this test. Create unit tests | 711 | # don't just patch up the code or this test. Create unit tests |
612 | 678 | # that specifically cover the difference, then change the code | 712 | # that specifically cover the difference, then change the code |
613 | 679 | # and/or adapt this test to return to harmony. | 713 | # and/or adapt this test to return to harmony. |
614 | 714 | generalization = GeneralizedPublication(True) | ||
615 | 680 | series = self.factory.makeDistroSeries() | 715 | series = self.factory.makeDistroSeries() |
616 | 681 | package = self.factory.makeSourcePackageName() | 716 | package = self.factory.makeSourcePackageName() |
617 | 682 | pocket = PackagePublishingPocket.RELEASE | 717 | pocket = PackagePublishingPocket.RELEASE |
618 | @@ -723,7 +758,8 @@ | |||
619 | 723 | 758 | ||
620 | 724 | all_pubs = sum(pubs_by_version.itervalues(), []) | 759 | all_pubs = sum(pubs_by_version.itervalues(), []) |
621 | 725 | Dominator(DevNullLogger(), series.main_archive).dominatePackage( | 760 | Dominator(DevNullLogger(), series.main_archive).dominatePackage( |
623 | 726 | all_pubs, live_versions, GeneralizedPublication(True)) | 761 | generalization.sortPublications(all_pubs), live_versions, |
624 | 762 | generalization) | ||
625 | 727 | 763 | ||
626 | 728 | for version in reversed(versions): | 764 | for version in reversed(versions): |
627 | 729 | pubs = pubs_by_version[version] | 765 | pubs = pubs_by_version[version] |
628 | @@ -1089,3 +1125,94 @@ | |||
629 | 1089 | published_spphs, | 1125 | published_spphs, |
630 | 1090 | dominator.findSourcesForDomination( | 1126 | dominator.findSourcesForDomination( |
631 | 1091 | spphs[0].distroseries, spphs[0].pocket)) | 1127 | spphs[0].distroseries, spphs[0].pocket)) |
632 | 1128 | |||
633 | 1129 | |||
634 | 1130 | def make_publications_arch_specific(pubs, arch_specific=True): | ||
635 | 1131 | """Set the `architecturespecific` attribute for given SPPHs. | ||
636 | 1132 | |||
637 | 1133 | :param pubs: An iterable of `BinaryPackagePublishingHistory`. | ||
638 | 1134 | :param arch_specific: Whether the binary package releases published | ||
639 | 1135 | by `pubs` are to be architecture-specific. If not, they will be | ||
640 | 1136 | treated as being for the "all" architecture. | ||
641 | 1137 | """ | ||
642 | 1138 | for pub in pubs: | ||
643 | 1139 | bpr = removeSecurityProxy(pub).binarypackagerelease | ||
644 | 1140 | bpr.architecturespecific = arch_specific | ||
645 | 1141 | |||
646 | 1142 | |||
647 | 1143 | class TestLivenessFunctions(TestCaseWithFactory): | ||
648 | 1144 | """Tests for the functions that say which versions are live.""" | ||
649 | 1145 | |||
650 | 1146 | layer = ZopelessDatabaseLayer | ||
651 | 1147 | |||
652 | 1148 | def test_find_live_source_versions_blesses_latest(self): | ||
653 | 1149 | spphs = make_spphs_for_versions(self.factory, ['1.2', '1.1', '1.0']) | ||
654 | 1150 | self.assertEqual(['1.2'], find_live_source_versions(spphs)) | ||
655 | 1151 | |||
656 | 1152 | def test_find_live_binary_versions_pass_1_blesses_latest(self): | ||
657 | 1153 | bpphs = make_bpphs_for_versions(self.factory, ['1.2', '1.1', '1.0']) | ||
658 | 1154 | make_publications_arch_specific(bpphs) | ||
659 | 1155 | self.assertEqual(['1.2'], find_live_binary_versions_pass_1(bpphs)) | ||
660 | 1156 | |||
661 | 1157 | def test_find_live_binary_versions_pass_1_blesses_arch_all(self): | ||
662 | 1158 | versions = list(reversed(['1.%d' % version for version in range(3)])) | ||
663 | 1159 | bpphs = make_bpphs_for_versions(self.factory, versions) | ||
664 | 1160 | |||
665 | 1161 | # All of these publications are architecture-specific, except | ||
666 | 1162 | # the last one. This would happen if the binary package had | ||
667 | 1163 | # just changed from being architecture-specific to being | ||
668 | 1164 | # architecture-independent. | ||
669 | 1165 | make_publications_arch_specific(bpphs, True) | ||
670 | 1166 | make_publications_arch_specific(bpphs[-1:], False) | ||
671 | 1167 | self.assertEqual( | ||
672 | 1168 | versions[:1] + versions[-1:], | ||
673 | 1169 | find_live_binary_versions_pass_1(bpphs)) | ||
674 | 1170 | |||
675 | 1171 | def test_find_live_binary_versions_pass_2_blesses_latest(self): | ||
676 | 1172 | bpphs = make_bpphs_for_versions(self.factory, ['1.2', '1.1', '1.0']) | ||
677 | 1173 | make_publications_arch_specific(bpphs, False) | ||
678 | 1174 | self.assertEqual(['1.2'], find_live_binary_versions_pass_2(bpphs)) | ||
679 | 1175 | |||
680 | 1176 | def test_find_live_binary_versions_pass_2_blesses_arch_specific(self): | ||
681 | 1177 | versions = list(reversed(['1.%d' % version for version in range(3)])) | ||
682 | 1178 | bpphs = make_bpphs_for_versions(self.factory, versions) | ||
683 | 1179 | make_publications_arch_specific(bpphs) | ||
684 | 1180 | self.assertEqual(versions, find_live_binary_versions_pass_2(bpphs)) | ||
685 | 1181 | |||
686 | 1182 | def test_find_live_binary_versions_pass_2_reprieves_arch_all(self): | ||
687 | 1183 | # An arch-all BPPH for a BPR built by an SPR that also still has | ||
688 | 1184 | # active arch-dependent BPPHs gets a reprieve: it can't be | ||
689 | 1185 | # superseded until those arch-dependent BPPHs have been | ||
690 | 1186 | # superseded. | ||
691 | 1187 | bpphs = make_bpphs_for_versions(self.factory, ['1.2', '1.1', '1.0']) | ||
692 | 1188 | make_publications_arch_specific(bpphs, False) | ||
693 | 1189 | dependent = self.factory.makeBinaryPackagePublishingHistory( | ||
694 | 1190 | binarypackagerelease=bpphs[1].binarypackagerelease) | ||
695 | 1191 | make_publications_arch_specific([dependent], True) | ||
696 | 1192 | self.assertEqual( | ||
697 | 1193 | ['1.2', '1.1'], find_live_binary_versions_pass_2(bpphs)) | ||
698 | 1194 | |||
699 | 1195 | |||
700 | 1196 | class TestDominationHelpers(TestCaseWithFactory): | ||
701 | 1197 | """Test lightweight helpers for the `Dominator`.""" | ||
702 | 1198 | |||
703 | 1199 | layer = ZopelessDatabaseLayer | ||
704 | 1200 | |||
705 | 1201 | def test_contains_arch_indep_says_True_for_arch_indep(self): | ||
706 | 1202 | bpphs = [self.factory.makeBinaryPackagePublishingHistory()] | ||
707 | 1203 | make_publications_arch_specific(bpphs, False) | ||
708 | 1204 | self.assertTrue(contains_arch_indep(bpphs)) | ||
709 | 1205 | |||
710 | 1206 | def test_contains_arch_indep_says_False_for_arch_specific(self): | ||
711 | 1207 | bpphs = [self.factory.makeBinaryPackagePublishingHistory()] | ||
712 | 1208 | make_publications_arch_specific(bpphs, True) | ||
713 | 1209 | self.assertFalse(contains_arch_indep(bpphs)) | ||
714 | 1210 | |||
715 | 1211 | def test_contains_arch_indep_says_True_for_combination(self): | ||
716 | 1212 | bpphs = make_bpphs_for_versions(self.factory, ['1.1', '1.0']) | ||
717 | 1213 | make_publications_arch_specific(bpphs[:1], True) | ||
718 | 1214 | make_publications_arch_specific(bpphs[1:], False) | ||
719 | 1215 | self.assertTrue(contains_arch_indep(bpphs)) | ||
720 | 1216 | |||
721 | 1217 | def test_contains_arch_indep_says_False_for_empty_list(self): | ||
722 | 1218 | self.assertFalse(contains_arch_indep([])) |