Merge lp:~jtv/launchpad/bug-884649-branch-2 into lp:launchpad
- bug-884649-branch-2
- Merge into devel
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | j.c.sackett | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 14249 | ||||
Proposed branch: | lp:~jtv/launchpad/bug-884649-branch-2 | ||||
Merge into: | lp:launchpad | ||||
Prerequisite: | lp:~jtv/launchpad/getBinariesForDomination-bulk | ||||
Diff against target: |
676 lines (+325/-121) 2 files modified
lib/lp/archivepublisher/domination.py (+183/-93) lib/lp/archivepublisher/tests/test_dominator.py (+142/-28) |
||||
To merge this branch: | bzr merge lp:~jtv/launchpad/bug-884649-branch-2 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Julian Edwards (community) | Approve | ||
j.c.sackett (community) | Approve | ||
Review via email: mp+81049@code.launchpad.net |
This proposal supersedes a proposal from 2011-11-02.
Commit message
[r=jcsackett,
Description of the change
= Summary =
This is part of the Dominator optimization work needed for bug 884649.
Binary domination needs 2 passes. This is because publications for the "all" architecture, which are copied into each architecture, can't be superseded until all architecture-
The code for dealing with this subtlety is a bit hard to place, because it's deep down in the inner loop as a special case. The special case is applied to both binary-domination passes, although there's really no point: the first pass may conceivably supersede a few arch-all publications but it won't deal with all of them. Meanwhile we pay a hefty price for the extra check.
== Proposed fix ==
Hoist the decision of which publications must stay live out of dominatePackage. It's a policy decision for the caller; the API lets the caller specify which versions stay live.
With that, we can specialize the passes more, and cut out complexity that is slowing things down.
You'll see three new functions explaining exactly what liveness criteria each domination pass uses. The source pass is easy. The first binary pass deals with architecture-
== Pre-implementation notes ==
Discussed in detail with Julian; see bug 884649 for the plan we're following in addressing this. We should also be able to re-use liveness decisions across architectures (and probably binary packages building from the same source package).
Raphaƫl suggested another clever improvement: Steve K. has just initialized a new, denormalized column on BinaryPackagePu
By the way, note that we can't just dominate arch-all and arch-specific publications separately. Packages may vacillate between the two, producing successive releases that need to be considered together even if domination of some publications may need to be postponed.
== Implementation details ==
I had to ditch _dominatePublic
== Tests ==
{{{
./bin/test -vvc lp.archivepubli
}}}
== Demo and Q/A ==
Dominate Ubuntu and you'll see.
= Launchpad lint =
Checking for conflicts and issues in changed files.
Linting changed files:
lib/lp/
lib/lp/
lib/lp/
Julian Edwards (julian-edwards) wrote : | # |
(I reviewed this several hours ago by email and only just noticed that LP rejected my email..
Thanks for doing this Jeroen, it looks pretty good so far. I've just got a
few nits inline:
> === modified file 'lib/lp/
> --- lib/lp/
> +++ lib/lp/
> @@ -52,8 +52,12 @@
>
> __all__ = ['Dominator']
>
> +from collections import defaultdict
> from datetime import timedelta
> -from operator import itemgetter
> +from operator import (
> + attrgetter,
> + itemgetter,
> + )
>
> import apt_pkg
> from storm.expr import (
> @@ -72,6 +76,7 @@
> DecoratedResultSet,
> )
> from canonical.
> +from canonical.
> from lp.registry.
> from lp.services.
> from lp.soyuz.enums import (
> @@ -192,6 +197,104 @@
> else:
> return version_comparison
>
> + def sortPublication
> + """Sort publications from most to least current versions."""
> + # Listify; we want to iterate this twice, which won't do for a
> + # non-persistent sequence.
> + sorted_publications = list(publications)
This variable is prematurely named, I think I'd just write:
publications = list(publications)
and return publications.
> + # Batch-load associated package releases; we'll be needing them
> + # to compare versions.
> + self.load_
> + # Now sort. This is that second iteration. An in-place sort
> + # won't hurt the original, because we're working on a copy of
> + # the original iterable.
> + sorted_
> + return sorted_publications
> +
> +
> +def find_live_
> + """Find versions out of Published `publications` that should stay live.
> +
> + This particular notion of liveness applies to source domination: the
> + latest version stays live, and that's it.
> +
> + :param publications: An iterable of `SourcePackageP
> + sorted by descending package version.
> + :return: A list of live versions.
> + """
> + # Given the required sort order, the latest version is at the head
> + # of the list.
> + return [publications[
Nicely abstracted.
> +
> +
> +def get_binary_
> + """List versions for sequence of `BinaryPackageP
> + return [pub.binarypack
binary_
If you're going to do :param: and :return: for the other functions, you
should do it here too!
> +
> +
> +def find_live_
> + """Find versions out of Published `publications` that should stay live.
> +
> + This particular notion of liveness applies to first-pass binary
> + domination: the latest version stays l...
Jeroen T. Vermeulen (jtv) wrote : | # |
Jon, thanks for taking on this very tough review. It's a crying shame that Julian had also reviewed it, but the extra pair of eyeballs is still appreciated. Even if Julian knows more of the innards, it's also great to hear that someone less familiar with the intricacies of Soyuz and the archive publisher can make sense at least some of this.
Let's not do this too much though or you'll get sucked into Soyuz-related work like I did. :)
Jeroen T. Vermeulen (jtv) wrote : | # |
> Thanks for doing this Jeroen, it looks pretty good so far. I've just got a
> few nits inline:
Ah, one of those reviews that actually make my code better. Good!
> > === modified file 'lib/lp/
> > --- lib/lp/
> > +++ lib/lp/
> > @@ -192,6 +197,104 @@
> > else:
> > return version_comparison
> >
> > + def sortPublication
> > + """Sort publications from most to least current versions."""
> > + # Listify; we want to iterate this twice, which won't do for a
> > + # non-persistent sequence.
> > + sorted_publications = list(publications)
>
> This variable is prematurely named, I think I'd just write:
> publications = list(publications)
> and return publications.
Yup, you're right. Done.
(Apart from one nasty little subtlety: surprisingly, list.sort does not return the sorted list. I suppose that's to avoid obscuring the fact that it modifies the list in-place.)
> > +def get_binary_
> > + """List versions for sequence of `BinaryPackageP
> > + return [pub.binarypack
> binary_
>
> If you're going to do :param: and :return: for the other functions, you
> should do it here too!
Hoist by my own pedantard. Done.
> > +def find_live_
> > + """Find versions out of Published `publications` that should stay live.
> > +
> > + This particular notion of liveness applies to first-pass binary
> > + domination: the latest version stays live, and so do publications of
> > + binary packages for the "all" architecture.
> > +
> > + :param publications: An iterable of `BinaryPackageP
> > + sorted by descending package version.
> > + :return: A list of live versions.
> > + """
> > + publications = list(publications)
> > + latest = publications.pop(0)
> > + return get_binary_
> > + [latest] + [
> > + pub for pub in publications if not pub.architectur
>
> pub.architectur
> absolutely detest properties that do that, it encourages SQL death-by-a-
> thousand-cuts.
I spotted it through sheer luck. It's just dereferencing a Storm reference though, and I took care to bulk-load the referenced BinaryPackageRe
> > +def find_live_
> > + """Find versions out of Published `publications` that should stay live.
> > +
> > + This particular notion of liveness applies to second-pass binary
> > + domination: the latest version stays live, and architecture-
> > + publications stay live (i.e, ones that are not for the "all"
> > + architecture).
> > +
> > + More importantly, any publication for binary packages of the "all"
> > + architecture stay live if any of the non-"all" binary packages from
> > + the same source package release are still active -- even if they are
> > + f...
Julian Edwards (julian-edwards) wrote : | # |
On Friday 04 November 2011 03:58:24 you wrote:
> > Thanks for doing this Jeroen, it looks pretty good so far. I've just
> > got a
> > few nits inline:
> Ah, one of those reviews that actually make my code better. Good!
You asked for it!
> (Apart from one nasty little subtlety: surprisingly, list.sort does not
> return the sorted list. I suppose that's to avoid obscuring the fact that
> it modifies the list in-place.)
Right, that's annoying.
> That's what I keep thinking too. But the python documentation says
> otherwise. Perhaps it was some other massively useful function that got
> deprecated, or perhaps people changed their minds.
>
> Anyway, looking this up also made me find ifilter and ifilterfalse (in
> itertools). With those, I can finally say what I need here tersely and
> uniformly:
>
> arch_specific_pubs = list(ifilter(
> arch_indep_pubs = list(ifilterfal
I had not seen those before, very nice.
It would serve us some use to trawl through the Python built-ins at some
point!
> It's not very costly compared to inevitable work we're already doing: for a
> list of n publications, this does n-1 version comparisons. That's
> guaranteed to be no more than the sort operation that produced the
> publications list in the first place.
>
> The loop depends very heavily on the sort order that this verifies, and
> there are several call sites that do the sorting. It'd be nice if we could
> just re-sort to make sure of the right order, but besides being more
> expensive that also wouldn't help the liveness functions which also rely on
> sort order.
>
> Given how important it is to get this right and how easy it is to get it
> wrong, I think verifying the order is worth the small added cost.
I agree that it's important. I have an additional suggestion - rename the
method parameter so it's "sorted_
to say it *must* be sorted.
> Done. But, er... does this mean I'm condemned to another year of this? :-)
You might think so but I could not possibly comment.
Thanks!
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-04 04:07:32 +0000 | |||
4 | @@ -52,8 +52,16 @@ | |||
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 itertools import ( |
12 | 58 | ifilter, | ||
13 | 59 | ifilterfalse, | ||
14 | 60 | ) | ||
15 | 61 | from operator import ( | ||
16 | 62 | attrgetter, | ||
17 | 63 | itemgetter, | ||
18 | 64 | ) | ||
19 | 57 | 65 | ||
20 | 58 | import apt_pkg | 66 | import apt_pkg |
21 | 59 | from storm.expr import ( | 67 | from storm.expr import ( |
22 | @@ -72,6 +80,7 @@ | |||
23 | 72 | DecoratedResultSet, | 80 | DecoratedResultSet, |
24 | 73 | ) | 81 | ) |
25 | 74 | from canonical.launchpad.interfaces.lpstorm import IStore | 82 | from canonical.launchpad.interfaces.lpstorm import IStore |
26 | 83 | from canonical.launchpad.utilities.orderingcheck import OrderingCheck | ||
27 | 75 | from lp.registry.model.sourcepackagename import SourcePackageName | 84 | from lp.registry.model.sourcepackagename import SourcePackageName |
28 | 76 | from lp.services.database.bulk import load_related | 85 | from lp.services.database.bulk import load_related |
29 | 77 | from lp.soyuz.enums import ( | 86 | from lp.soyuz.enums import ( |
30 | @@ -192,6 +201,107 @@ | |||
31 | 192 | else: | 201 | else: |
32 | 193 | return version_comparison | 202 | return version_comparison |
33 | 194 | 203 | ||
34 | 204 | def sortPublications(self, publications): | ||
35 | 205 | """Sort publications from most to least current versions.""" | ||
36 | 206 | # Listify; we want to iterate this twice, which won't do for a | ||
37 | 207 | # non-persistent sequence. | ||
38 | 208 | publications = list(publications) | ||
39 | 209 | # Batch-load associated package releases; we'll be needing them | ||
40 | 210 | # to compare versions. | ||
41 | 211 | self.load_releases(publications) | ||
42 | 212 | # Now sort. This is that second iteration. An in-place sort | ||
43 | 213 | # won't hurt the original, because we're working on a copy of | ||
44 | 214 | # the original iterable. | ||
45 | 215 | publications.sort(cmp=self.compare, reverse=True) | ||
46 | 216 | return publications | ||
47 | 217 | |||
48 | 218 | |||
49 | 219 | def find_live_source_versions(publications): | ||
50 | 220 | """Find versions out of Published `publications` that should stay live. | ||
51 | 221 | |||
52 | 222 | This particular notion of liveness applies to source domination: the | ||
53 | 223 | latest version stays live, and that's it. | ||
54 | 224 | |||
55 | 225 | :param publications: An iterable of `SourcePackagePublishingHistory` | ||
56 | 226 | sorted by descending package version. | ||
57 | 227 | :return: A list of live versions. | ||
58 | 228 | """ | ||
59 | 229 | # Given the required sort order, the latest version is at the head | ||
60 | 230 | # of the list. | ||
61 | 231 | return [publications[0].sourcepackagerelease.version] | ||
62 | 232 | |||
63 | 233 | |||
64 | 234 | def get_binary_versions(binary_publications): | ||
65 | 235 | """List versions for sequence of `BinaryPackagePublishingHistory`. | ||
66 | 236 | |||
67 | 237 | :param binary_publications: An iterable of | ||
68 | 238 | `BinaryPackagePublishingHistory`. | ||
69 | 239 | :return: A list of the publications' respective versions. | ||
70 | 240 | """ | ||
71 | 241 | return [pub.binarypackagerelease.version for pub in binary_publications] | ||
72 | 242 | |||
73 | 243 | |||
74 | 244 | def find_live_binary_versions_pass_1(publications): | ||
75 | 245 | """Find versions out of Published `publications` that should stay live. | ||
76 | 246 | |||
77 | 247 | This particular notion of liveness applies to first-pass binary | ||
78 | 248 | domination: the latest version stays live, and so do publications of | ||
79 | 249 | binary packages for the "all" architecture. | ||
80 | 250 | |||
81 | 251 | :param publications: An iterable of `BinaryPackagePublishingHistory`, | ||
82 | 252 | sorted by descending package version. | ||
83 | 253 | :return: A list of live versions. | ||
84 | 254 | """ | ||
85 | 255 | publications = list(publications) | ||
86 | 256 | latest = publications.pop(0) | ||
87 | 257 | return get_binary_versions( | ||
88 | 258 | [latest] + [ | ||
89 | 259 | pub for pub in publications if not pub.architecture_specific]) | ||
90 | 260 | |||
91 | 261 | |||
92 | 262 | def find_live_binary_versions_pass_2(publications): | ||
93 | 263 | """Find versions out of Published `publications` that should stay live. | ||
94 | 264 | |||
95 | 265 | This particular notion of liveness applies to second-pass binary | ||
96 | 266 | domination: the latest version stays live, and architecture-specific | ||
97 | 267 | publications stay live (i.e, ones that are not for the "all" | ||
98 | 268 | architecture). | ||
99 | 269 | |||
100 | 270 | More importantly, any publication for binary packages of the "all" | ||
101 | 271 | architecture stay live if any of the non-"all" binary packages from | ||
102 | 272 | the same source package release are still active -- even if they are | ||
103 | 273 | for other architectures. | ||
104 | 274 | |||
105 | 275 | This is the raison d'etre for the two-pass binary domination algorithm: | ||
106 | 276 | to let us see which architecture-independent binary publications can be | ||
107 | 277 | superseded without rendering any architecture-specific binaries from the | ||
108 | 278 | same source package release uninstallable. | ||
109 | 279 | |||
110 | 280 | (Note that here, "active" includes Published publications but also | ||
111 | 281 | Pending ones. This is standard nomenclature in Soyuz. Some of the | ||
112 | 282 | domination code confuses matters by using the term "active" to mean only | ||
113 | 283 | Published publications). | ||
114 | 284 | |||
115 | 285 | :param publications: An iterable of `BinaryPackagePublishingHistory`, | ||
116 | 286 | sorted by descending package version. | ||
117 | 287 | :return: A list of live versions. | ||
118 | 288 | """ | ||
119 | 289 | publications = list(publications) | ||
120 | 290 | latest = publications.pop(0) | ||
121 | 291 | is_arch_specific = attrgetter('architecture_specific') | ||
122 | 292 | arch_specific_pubs = list(ifilter(is_arch_specific, publications)) | ||
123 | 293 | arch_indep_pubs = list(ifilterfalse(is_arch_specific, publications)) | ||
124 | 294 | |||
125 | 295 | # XXX JeroenVermeulen 2011-11-01 bug=884649: This is likely to be | ||
126 | 296 | # costly, and the result could be reused for all builds of the same | ||
127 | 297 | # source package release to all architectures. | ||
128 | 298 | reprieved_pubs = [ | ||
129 | 299 | pub | ||
130 | 300 | for pub in arch_indep_pubs | ||
131 | 301 | if pub.getOtherPublicationsForSameSource().any()] | ||
132 | 302 | |||
133 | 303 | return get_binary_versions([latest] + arch_specific_pubs + reprieved_pubs) | ||
134 | 304 | |||
135 | 195 | 305 | ||
136 | 196 | class Dominator: | 306 | class Dominator: |
137 | 197 | """Manage the process of marking packages as superseded. | 307 | """Manage the process of marking packages as superseded. |
138 | @@ -209,27 +319,6 @@ | |||
139 | 209 | self.logger = logger | 319 | self.logger = logger |
140 | 210 | self.archive = archive | 320 | self.archive = archive |
141 | 211 | 321 | ||
142 | 212 | def _checkArchIndep(self, publication): | ||
143 | 213 | """Return True if the binary publication can be superseded. | ||
144 | 214 | |||
145 | 215 | If the publication is an arch-indep binary, we can only supersede | ||
146 | 216 | it if all the binaries from the same source are also superseded, | ||
147 | 217 | else those binaries may become uninstallable. | ||
148 | 218 | See bug 34086. | ||
149 | 219 | """ | ||
150 | 220 | binary = publication.binarypackagerelease | ||
151 | 221 | if not binary.architecturespecific: | ||
152 | 222 | # getOtherPublicationsForSameSource returns PENDING in | ||
153 | 223 | # addition to PUBLISHED binaries, and we rely on this since | ||
154 | 224 | # they must also block domination. | ||
155 | 225 | others = publication.getOtherPublicationsForSameSource() | ||
156 | 226 | if others.any(): | ||
157 | 227 | # Don't dominate this arch:all binary as there are | ||
158 | 228 | # other arch-specific binaries from the same build | ||
159 | 229 | # that are still active. | ||
160 | 230 | return False | ||
161 | 231 | return True | ||
162 | 232 | |||
163 | 233 | def dominatePackage(self, publications, live_versions, generalization): | 322 | def dominatePackage(self, publications, live_versions, generalization): |
164 | 234 | """Dominate publications for a single package. | 323 | """Dominate publications for a single package. |
165 | 235 | 324 | ||
166 | @@ -247,34 +336,33 @@ | |||
167 | 247 | 336 | ||
168 | 248 | :param publications: Iterable of publications for the same package, | 337 | :param publications: Iterable of publications for the same package, |
169 | 249 | in the same archive, series, and pocket, all with status | 338 | in the same archive, series, and pocket, all with status |
176 | 250 | `PackagePublishingStatus.PUBLISHED`. | 339 | `PackagePublishingStatus.PUBLISHED`. They must be sorted from |
177 | 251 | :param live_versions: Iterable of version strings that are still | 340 | most current to least current, as would be the result of |
178 | 252 | considered live for this package. The given publications will | 341 | `generalization.sortPublications`. |
179 | 253 | remain active insofar as they represent any of these versions; | 342 | :param live_versions: Iterable of versions that are still considered |
180 | 254 | older publications will be marked as superseded and newer ones | 343 | "live" for this package. For any of these, the latest publication |
181 | 255 | as deleted. | 344 | among `publications` will remain Published. Publications for |
182 | 345 | older releases, as well as older publications of live versions, | ||
183 | 346 | will be marked as Superseded. Publications of newer versions than | ||
184 | 347 | are listed in `live_versions` are marked as Deleted. | ||
185 | 256 | :param generalization: A `GeneralizedPublication` helper representing | 348 | :param generalization: A `GeneralizedPublication` helper representing |
187 | 257 | the kind of publications these are--source or binary. | 349 | the kind of publications these are: source or binary. |
188 | 258 | """ | 350 | """ |
199 | 259 | publications = list(publications) | 351 | live_versions = frozenset(live_versions) |
190 | 260 | generalization.load_releases(publications) | ||
191 | 261 | |||
192 | 262 | # Go through publications from latest version to oldest. This | ||
193 | 263 | # makes it easy to figure out which release superseded which: | ||
194 | 264 | # the dominant is always the oldest live release that is newer | ||
195 | 265 | # than the one being superseded. In this loop, that means the | ||
196 | 266 | # dominant is always the last live publication we saw. | ||
197 | 267 | publications = sorted( | ||
198 | 268 | publications, cmp=generalization.compare, reverse=True) | ||
200 | 269 | 352 | ||
201 | 270 | self.logger.debug( | 353 | self.logger.debug( |
202 | 271 | "Package has %d live publication(s). Live versions: %s", | 354 | "Package has %d live publication(s). Live versions: %s", |
203 | 272 | len(publications), live_versions) | 355 | len(publications), live_versions) |
204 | 273 | 356 | ||
205 | 357 | # Verify that the publications are really sorted properly. | ||
206 | 358 | check_order = OrderingCheck(cmp=generalization.compare, reverse=True) | ||
207 | 359 | |||
208 | 274 | current_dominant = None | 360 | current_dominant = None |
209 | 275 | dominant_version = None | 361 | dominant_version = None |
210 | 276 | 362 | ||
211 | 277 | for pub in publications: | 363 | for pub in publications: |
212 | 364 | check_order.check(pub) | ||
213 | 365 | |||
214 | 278 | version = generalization.getPackageVersion(pub) | 366 | version = generalization.getPackageVersion(pub) |
215 | 279 | # There should never be two published releases with the same | 367 | # There should never be two published releases with the same |
216 | 280 | # version. So it doesn't matter whether this comparison is | 368 | # version. So it doesn't matter whether this comparison is |
217 | @@ -295,11 +383,6 @@ | |||
218 | 295 | current_dominant = pub | 383 | current_dominant = pub |
219 | 296 | dominant_version = version | 384 | dominant_version = version |
220 | 297 | self.logger.debug2("Keeping version %s.", version) | 385 | self.logger.debug2("Keeping version %s.", version) |
221 | 298 | elif not (generalization.is_source or self._checkArchIndep(pub)): | ||
222 | 299 | # As a special case, we keep this version live as well. | ||
223 | 300 | current_dominant = pub | ||
224 | 301 | dominant_version = version | ||
225 | 302 | self.logger.debug2("Keeping version %s.", version) | ||
226 | 303 | elif current_dominant is None: | 386 | elif current_dominant is None: |
227 | 304 | # This publication is no longer live, but there is no | 387 | # This publication is no longer live, but there is no |
228 | 305 | # newer version to supersede it either. Therefore it | 388 | # newer version to supersede it either. Therefore it |
229 | @@ -312,50 +395,32 @@ | |||
230 | 312 | pub.supersede(current_dominant, logger=self.logger) | 395 | pub.supersede(current_dominant, logger=self.logger) |
231 | 313 | self.logger.debug2("Superseding version %s.", version) | 396 | self.logger.debug2("Superseding version %s.", version) |
232 | 314 | 397 | ||
277 | 315 | def _dominatePublications(self, pubs, generalization): | 398 | def _sortPackages(self, publications, generalization): |
278 | 316 | """Perform dominations for the given publications. | 399 | """Partition publications by package name, and sort them. |
279 | 317 | 400 | ||
280 | 318 | Keep the latest published version for each package active, | 401 | The publications are sorted from most current to least current, |
281 | 319 | superseding older versions. | 402 | as required by `dominatePackage` etc. |
282 | 320 | 403 | ||
283 | 321 | :param pubs: A dict mapping names to a list of publications. Every | 404 | :param publications: An iterable of `SourcePackagePublishingHistory` |
284 | 322 | publication must be PUBLISHED or PENDING, and the first in each | 405 | or of `BinaryPackagePublishingHistory`. |
285 | 323 | list will be treated as dominant (so should be the latest). | 406 | :param generalization: A `GeneralizedPublication` helper representing |
286 | 324 | :param generalization: A `GeneralizedPublication` helper representing | 407 | the kind of publications these are: source or binary. |
287 | 325 | the kind of publications these are--source or binary. | 408 | :return: A dict mapping each package name to a sorted list of |
288 | 326 | """ | 409 | publications from `publications`. |
289 | 327 | self.logger.debug("Dominating packages...") | 410 | """ |
290 | 328 | for name, publications in pubs.iteritems(): | 411 | pubs_by_package = defaultdict(list) |
291 | 329 | assert publications, "Empty list of publications for %s." % name | 412 | for pub in publications: |
292 | 330 | # Since this always picks the latest version as the live | 413 | pubs_by_package[generalization.getPackageName(pub)].append(pub) |
293 | 331 | # one, this dominatePackage call will never result in a | 414 | |
294 | 332 | # deletion. | 415 | # Sort the publication lists. This is not an in-place sort, so |
295 | 333 | latest_version = generalization.getPackageVersion(publications[0]) | 416 | # it involves altering the dict while we iterate it. Listify |
296 | 334 | self.logger.debug2("Dominating %s" % name) | 417 | # the keys so that we can be sure that we're not altering the |
297 | 335 | self.dominatePackage( | 418 | # iteration order while iteration is underway. |
298 | 336 | publications, [latest_version], generalization) | 419 | for package in list(pubs_by_package.keys()): |
299 | 337 | 420 | pubs_by_package[package] = generalization.sortPublications( | |
300 | 338 | def _sortPackages(self, pkglist, generalization): | 421 | pubs_by_package[package]) |
301 | 339 | """Map out packages by name, and sort by descending version. | 422 | |
302 | 340 | 423 | return pubs_by_package | |
259 | 341 | :param pkglist: An iterable of `SourcePackagePublishingHistory` or | ||
260 | 342 | `BinaryPackagePublishingHistory`. | ||
261 | 343 | :param generalization: A `GeneralizedPublication` helper representing | ||
262 | 344 | the kind of publications these are--source or binary. | ||
263 | 345 | :return: A dict mapping each package name to a list of publications | ||
264 | 346 | from `pkglist`, newest first. | ||
265 | 347 | """ | ||
266 | 348 | self.logger.debug("Sorting packages...") | ||
267 | 349 | |||
268 | 350 | outpkgs = {} | ||
269 | 351 | for inpkg in pkglist: | ||
270 | 352 | key = generalization.getPackageName(inpkg) | ||
271 | 353 | outpkgs.setdefault(key, []).append(inpkg) | ||
272 | 354 | |||
273 | 355 | for package_pubs in outpkgs.itervalues(): | ||
274 | 356 | package_pubs.sort(cmp=generalization.compare, reverse=True) | ||
275 | 357 | |||
276 | 358 | return outpkgs | ||
303 | 359 | 424 | ||
304 | 360 | def _setScheduledDeletionDate(self, pub_record): | 425 | def _setScheduledDeletionDate(self, pub_record): |
305 | 361 | """Set the scheduleddeletiondate on a publishing record. | 426 | """Set the scheduleddeletiondate on a publishing record. |
306 | @@ -520,7 +585,11 @@ | |||
307 | 520 | bins = self.findBinariesForDomination(distroarchseries, pocket) | 585 | bins = self.findBinariesForDomination(distroarchseries, pocket) |
308 | 521 | sorted_packages = self._sortPackages(bins, generalization) | 586 | sorted_packages = self._sortPackages(bins, generalization) |
309 | 522 | self.logger.info("Dominating binaries...") | 587 | self.logger.info("Dominating binaries...") |
311 | 523 | self._dominatePublications(sorted_packages, generalization) | 588 | for name, pubs in sorted_packages.iteritems(): |
312 | 589 | self.logger.debug("Dominating %s" % name) | ||
313 | 590 | assert len(pubs) > 0, "Dominating zero binaries!" | ||
314 | 591 | live_versions = find_live_binary_versions_pass_1(pubs) | ||
315 | 592 | self.dominatePackage(pubs, live_versions, generalization) | ||
316 | 524 | 593 | ||
317 | 525 | # We need to make a second pass to cover the cases where: | 594 | # We need to make a second pass to cover the cases where: |
318 | 526 | # * arch-specific binaries were not all dominated before arch-all | 595 | # * arch-specific binaries were not all dominated before arch-all |
319 | @@ -534,7 +603,11 @@ | |||
320 | 534 | bins = self.findBinariesForDomination(distroarchseries, pocket) | 603 | bins = self.findBinariesForDomination(distroarchseries, pocket) |
321 | 535 | sorted_packages = self._sortPackages(bins, generalization) | 604 | sorted_packages = self._sortPackages(bins, generalization) |
322 | 536 | self.logger.info("Dominating binaries...(2nd pass)") | 605 | self.logger.info("Dominating binaries...(2nd pass)") |
324 | 537 | self._dominatePublications(sorted_packages, generalization) | 606 | for name, pubs in sorted_packages.iteritems(): |
325 | 607 | self.logger.debug("Dominating %s" % name) | ||
326 | 608 | assert len(pubs) > 0, "Dominating zero binaries in 2nd pass!" | ||
327 | 609 | live_versions = find_live_binary_versions_pass_2(pubs) | ||
328 | 610 | self.dominatePackage(pubs, live_versions, generalization) | ||
329 | 538 | 611 | ||
330 | 539 | def _composeActiveSourcePubsCondition(self, distroseries, pocket): | 612 | def _composeActiveSourcePubsCondition(self, distroseries, pocket): |
331 | 540 | """Compose ORM condition for restricting relevant source pubs.""" | 613 | """Compose ORM condition for restricting relevant source pubs.""" |
332 | @@ -550,7 +623,16 @@ | |||
333 | 550 | ) | 623 | ) |
334 | 551 | 624 | ||
335 | 552 | def findSourcesForDomination(self, distroseries, pocket): | 625 | def findSourcesForDomination(self, distroseries, pocket): |
337 | 553 | """Find binary publications that need dominating.""" | 626 | """Find binary publications that need dominating. |
338 | 627 | |||
339 | 628 | This is only for traditional domination, where the latest published | ||
340 | 629 | publication is always kept published. See `find_live_source_versions` | ||
341 | 630 | for this logic. | ||
342 | 631 | |||
343 | 632 | To optimize for that logic, `findSourcesForDomination` will ignore | ||
344 | 633 | publications that have no other publications competing for the same | ||
345 | 634 | binary package. There'd be nothing to do for those cases. | ||
346 | 635 | """ | ||
347 | 554 | # Avoid circular imports. | 636 | # Avoid circular imports. |
348 | 555 | from lp.soyuz.model.publishing import SourcePackagePublishingHistory | 637 | from lp.soyuz.model.publishing import SourcePackagePublishingHistory |
349 | 556 | 638 | ||
350 | @@ -589,11 +671,18 @@ | |||
351 | 589 | distroseries.name, pocket.title) | 671 | distroseries.name, pocket.title) |
352 | 590 | 672 | ||
353 | 591 | generalization = GeneralizedPublication(is_source=True) | 673 | generalization = GeneralizedPublication(is_source=True) |
354 | 674 | |||
355 | 675 | self.logger.debug("Finding sources...") | ||
356 | 592 | sources = self.findSourcesForDomination(distroseries, pocket) | 676 | sources = self.findSourcesForDomination(distroseries, pocket) |
357 | 677 | sorted_packages = self._sortPackages(sources, generalization) | ||
358 | 593 | 678 | ||
359 | 594 | self.logger.debug("Dominating sources...") | 679 | self.logger.debug("Dominating sources...") |
362 | 595 | self._dominatePublications( | 680 | for name, pubs in sorted_packages.iteritems(): |
363 | 596 | self._sortPackages(sources, generalization), generalization) | 681 | self.logger.debug("Dominating %s" % name) |
364 | 682 | assert len(pubs) > 0, "Dominating zero sources!" | ||
365 | 683 | live_versions = find_live_source_versions(pubs) | ||
366 | 684 | self.dominatePackage(pubs, live_versions, generalization) | ||
367 | 685 | |||
368 | 597 | flush_database_updates() | 686 | flush_database_updates() |
369 | 598 | 687 | ||
370 | 599 | def findPublishedSourcePackageNames(self, distroseries, pocket): | 688 | def findPublishedSourcePackageNames(self, distroseries, pocket): |
371 | @@ -653,6 +742,7 @@ | |||
372 | 653 | """ | 742 | """ |
373 | 654 | generalization = GeneralizedPublication(is_source=True) | 743 | generalization = GeneralizedPublication(is_source=True) |
374 | 655 | pubs = self.findPublishedSPPHs(distroseries, pocket, package_name) | 744 | pubs = self.findPublishedSPPHs(distroseries, pocket, package_name) |
375 | 745 | pubs = generalization.sortPublications(pubs) | ||
376 | 656 | self.dominatePackage(pubs, live_versions, generalization) | 746 | self.dominatePackage(pubs, live_versions, generalization) |
377 | 657 | 747 | ||
378 | 658 | def judge(self, distroseries, pocket): | 748 | def judge(self, distroseries, pocket): |
379 | 659 | 749 | ||
380 | === modified file 'lib/lp/archivepublisher/tests/test_dominator.py' | |||
381 | --- lib/lp/archivepublisher/tests/test_dominator.py 2011-11-02 10:28:31 +0000 | |||
382 | +++ lib/lp/archivepublisher/tests/test_dominator.py 2011-11-04 04:07:32 +0000 | |||
383 | @@ -16,6 +16,9 @@ | |||
384 | 16 | from canonical.testing.layers import ZopelessDatabaseLayer | 16 | from canonical.testing.layers import ZopelessDatabaseLayer |
385 | 17 | from lp.archivepublisher.domination import ( | 17 | from lp.archivepublisher.domination import ( |
386 | 18 | Dominator, | 18 | Dominator, |
387 | 19 | find_live_binary_versions_pass_1, | ||
388 | 20 | find_live_binary_versions_pass_2, | ||
389 | 21 | find_live_source_versions, | ||
390 | 19 | GeneralizedPublication, | 22 | GeneralizedPublication, |
391 | 20 | STAY_OF_EXECUTION, | 23 | STAY_OF_EXECUTION, |
392 | 21 | ) | 24 | ) |
393 | @@ -30,6 +33,7 @@ | |||
394 | 30 | StormStatementRecorder, | 33 | StormStatementRecorder, |
395 | 31 | TestCaseWithFactory, | 34 | TestCaseWithFactory, |
396 | 32 | ) | 35 | ) |
397 | 36 | from lp.testing.fakemethod import FakeMethod | ||
398 | 33 | from lp.testing.matchers import HasQueryCount | 37 | from lp.testing.matchers import HasQueryCount |
399 | 34 | 38 | ||
400 | 35 | 39 | ||
401 | @@ -72,13 +76,9 @@ | |||
402 | 72 | is_source=ISourcePackagePublishingHistory.providedBy(dominant)) | 76 | is_source=ISourcePackagePublishingHistory.providedBy(dominant)) |
403 | 73 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) | 77 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
404 | 74 | 78 | ||
412 | 75 | # The _dominate* test methods require a dictionary where the | 79 | pubs = [dominant, dominated] |
413 | 76 | # package name is the key. The key's value is a list of | 80 | live_versions = [generalization.getPackageVersion(dominant)] |
414 | 77 | # source or binary packages representing dominant, the first element | 81 | dominator.dominatePackage(pubs, live_versions, generalization) |
408 | 78 | # and dominated, the subsequents. | ||
409 | 79 | pubs = {'foo': [dominant, dominated]} | ||
410 | 80 | |||
411 | 81 | dominator._dominatePublications(pubs, generalization) | ||
415 | 82 | flush_database_updates() | 82 | flush_database_updates() |
416 | 83 | 83 | ||
417 | 84 | # The dominant version remains correctly published. | 84 | # The dominant version remains correctly published. |
418 | @@ -158,16 +158,30 @@ | |||
419 | 158 | [foo_10_source] + foo_10_binaries, | 158 | [foo_10_source] + foo_10_binaries, |
420 | 159 | PackagePublishingStatus.SUPERSEDED) | 159 | PackagePublishingStatus.SUPERSEDED) |
421 | 160 | 160 | ||
432 | 161 | def testEmptyDomination(self): | 161 | def test_dominateBinaries_rejects_empty_publication_list(self): |
433 | 162 | """Domination asserts for not empty input list.""" | 162 | """Domination asserts for non-empty input list.""" |
434 | 163 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) | 163 | package = self.factory.makeBinaryPackageName() |
435 | 164 | pubs = {'foo': []} | 164 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) |
436 | 165 | # This isn't a really good exception. It should probably be | 165 | dominator._sortPackages = FakeMethod({package.name: []}) |
437 | 166 | # something more indicative of bad input. | 166 | # This isn't a really good exception. It should probably be |
438 | 167 | self.assertRaises( | 167 | # something more indicative of bad input. |
439 | 168 | AssertionError, | 168 | self.assertRaises( |
440 | 169 | dominator._dominatePublications, | 169 | AssertionError, |
441 | 170 | pubs, GeneralizedPublication(True)) | 170 | dominator.dominateBinaries, |
442 | 171 | self.factory.makeDistroArchSeries().distroseries, | ||
443 | 172 | self.factory.getAnyPocket()) | ||
444 | 173 | |||
445 | 174 | def test_dominateSources_rejects_empty_publication_list(self): | ||
446 | 175 | """Domination asserts for non-empty input list.""" | ||
447 | 176 | package = self.factory.makeSourcePackageName() | ||
448 | 177 | dominator = Dominator(self.logger, self.ubuntutest.main_archive) | ||
449 | 178 | dominator._sortPackages = FakeMethod({package.name: []}) | ||
450 | 179 | # This isn't a really good exception. It should probably be | ||
451 | 180 | # something more indicative of bad input. | ||
452 | 181 | self.assertRaises( | ||
453 | 182 | AssertionError, | ||
454 | 183 | dominator.dominateSources, | ||
455 | 184 | self.factory.makeDistroSeries(), self.factory.getAnyPocket()) | ||
456 | 171 | 185 | ||
457 | 172 | def test_archall_domination(self): | 186 | def test_archall_domination(self): |
458 | 173 | # Arch-all binaries should not be dominated when a new source | 187 | # Arch-all binaries should not be dominated when a new source |
459 | @@ -358,6 +372,16 @@ | |||
460 | 358 | SeriesStatus.OBSOLETE) | 372 | SeriesStatus.OBSOLETE) |
461 | 359 | 373 | ||
462 | 360 | 374 | ||
463 | 375 | def remove_security_proxies(proxied_objects): | ||
464 | 376 | """Return list of `proxied_objects`, without their proxies. | ||
465 | 377 | |||
466 | 378 | The dominator runs only in scripts, where security proxies don't get | ||
467 | 379 | in the way. To test realistically for this environment, strip the | ||
468 | 380 | proxies wherever necessary and do as you will. | ||
469 | 381 | """ | ||
470 | 382 | return [removeSecurityProxy(obj) for obj in proxied_objects] | ||
471 | 383 | |||
472 | 384 | |||
473 | 361 | def make_spphs_for_versions(factory, versions): | 385 | def make_spphs_for_versions(factory, versions): |
474 | 362 | """Create publication records for each of `versions`. | 386 | """Create publication records for each of `versions`. |
475 | 363 | 387 | ||
476 | @@ -400,14 +424,15 @@ | |||
477 | 400 | archive = das.distroseries.main_archive | 424 | archive = das.distroseries.main_archive |
478 | 401 | pocket = factory.getAnyPocket() | 425 | pocket = factory.getAnyPocket() |
479 | 402 | bprs = [ | 426 | bprs = [ |
481 | 403 | factory.makeBinaryPackageRelease(binarypackagename=bpn) | 427 | factory.makeBinaryPackageRelease( |
482 | 428 | binarypackagename=bpn, version=version) | ||
483 | 404 | for version in versions] | 429 | for version in versions] |
485 | 405 | return [ | 430 | return remove_security_proxies([ |
486 | 406 | factory.makeBinaryPackagePublishingHistory( | 431 | factory.makeBinaryPackagePublishingHistory( |
487 | 407 | binarypackagerelease=bpr, binarypackagename=bpn, | 432 | binarypackagerelease=bpr, binarypackagename=bpn, |
488 | 408 | distroarchseries=das, pocket=pocket, archive=archive, | 433 | distroarchseries=das, pocket=pocket, archive=archive, |
489 | 409 | sourcepackagename=spn, status=PackagePublishingStatus.PUBLISHED) | 434 | sourcepackagename=spn, status=PackagePublishingStatus.PUBLISHED) |
491 | 410 | for bpr in bprs] | 435 | for bpr in bprs]) |
492 | 411 | 436 | ||
493 | 412 | 437 | ||
494 | 413 | def list_source_versions(spphs): | 438 | def list_source_versions(spphs): |
495 | @@ -591,9 +616,10 @@ | |||
496 | 591 | def test_dominatePackage_supersedes_older_pub_with_newer_live_pub(self): | 616 | def test_dominatePackage_supersedes_older_pub_with_newer_live_pub(self): |
497 | 592 | # When marking a package as superseded, dominatePackage | 617 | # When marking a package as superseded, dominatePackage |
498 | 593 | # designates a newer live version as the superseding version. | 618 | # designates a newer live version as the superseding version. |
499 | 619 | generalization = GeneralizedPublication(True) | ||
500 | 594 | pubs = make_spphs_for_versions(self.factory, ['1.0', '1.1']) | 620 | pubs = make_spphs_for_versions(self.factory, ['1.0', '1.1']) |
501 | 595 | self.makeDominator(pubs).dominatePackage( | 621 | self.makeDominator(pubs).dominatePackage( |
503 | 596 | pubs, ['1.1'], GeneralizedPublication(True)) | 622 | generalization.sortPublications(pubs), ['1.1'], generalization) |
504 | 597 | self.assertEqual(PackagePublishingStatus.SUPERSEDED, pubs[0].status) | 623 | self.assertEqual(PackagePublishingStatus.SUPERSEDED, pubs[0].status) |
505 | 598 | self.assertEqual(pubs[1].sourcepackagerelease, pubs[0].supersededby) | 624 | self.assertEqual(pubs[1].sourcepackagerelease, pubs[0].supersededby) |
506 | 599 | self.assertEqual(PackagePublishingStatus.PUBLISHED, pubs[1].status) | 625 | self.assertEqual(PackagePublishingStatus.PUBLISHED, pubs[1].status) |
507 | @@ -601,10 +627,11 @@ | |||
508 | 601 | def test_dominatePackage_only_supersedes_with_live_pub(self): | 627 | def test_dominatePackage_only_supersedes_with_live_pub(self): |
509 | 602 | # When marking a package as superseded, dominatePackage will | 628 | # When marking a package as superseded, dominatePackage will |
510 | 603 | # only pick a live version as the superseding one. | 629 | # only pick a live version as the superseding one. |
511 | 630 | generalization = GeneralizedPublication(True) | ||
512 | 604 | pubs = make_spphs_for_versions( | 631 | pubs = make_spphs_for_versions( |
513 | 605 | self.factory, ['1.0', '2.0', '3.0', '4.0']) | 632 | self.factory, ['1.0', '2.0', '3.0', '4.0']) |
514 | 606 | self.makeDominator(pubs).dominatePackage( | 633 | self.makeDominator(pubs).dominatePackage( |
516 | 607 | pubs, ['3.0'], GeneralizedPublication(True)) | 634 | generalization.sortPublications(pubs), ['3.0'], generalization) |
517 | 608 | self.assertEqual([ | 635 | self.assertEqual([ |
518 | 609 | pubs[2].sourcepackagerelease, | 636 | pubs[2].sourcepackagerelease, |
519 | 610 | pubs[2].sourcepackagerelease, | 637 | pubs[2].sourcepackagerelease, |
520 | @@ -616,23 +643,27 @@ | |||
521 | 616 | def test_dominatePackage_supersedes_with_oldest_newer_live_pub(self): | 643 | def test_dominatePackage_supersedes_with_oldest_newer_live_pub(self): |
522 | 617 | # When marking a package as superseded, dominatePackage picks | 644 | # When marking a package as superseded, dominatePackage picks |
523 | 618 | # the oldest of the newer, live versions as the superseding one. | 645 | # the oldest of the newer, live versions as the superseding one. |
524 | 646 | generalization = GeneralizedPublication(True) | ||
525 | 619 | pubs = make_spphs_for_versions(self.factory, ['2.7', '2.8', '2.9']) | 647 | pubs = make_spphs_for_versions(self.factory, ['2.7', '2.8', '2.9']) |
526 | 620 | self.makeDominator(pubs).dominatePackage( | 648 | self.makeDominator(pubs).dominatePackage( |
528 | 621 | pubs, ['2.8', '2.9'], GeneralizedPublication(True)) | 649 | generalization.sortPublications(pubs), ['2.8', '2.9'], |
529 | 650 | generalization) | ||
530 | 622 | self.assertEqual(pubs[1].sourcepackagerelease, pubs[0].supersededby) | 651 | self.assertEqual(pubs[1].sourcepackagerelease, pubs[0].supersededby) |
531 | 623 | 652 | ||
532 | 624 | def test_dominatePackage_only_supersedes_with_newer_live_pub(self): | 653 | def test_dominatePackage_only_supersedes_with_newer_live_pub(self): |
533 | 625 | # When marking a package as superseded, dominatePackage only | 654 | # When marking a package as superseded, dominatePackage only |
534 | 626 | # considers a newer version as the superseding one. | 655 | # considers a newer version as the superseding one. |
535 | 656 | generalization = GeneralizedPublication(True) | ||
536 | 627 | pubs = make_spphs_for_versions(self.factory, ['0.1', '0.2']) | 657 | pubs = make_spphs_for_versions(self.factory, ['0.1', '0.2']) |
537 | 628 | self.makeDominator(pubs).dominatePackage( | 658 | self.makeDominator(pubs).dominatePackage( |
539 | 629 | pubs, ['0.1'], GeneralizedPublication(True)) | 659 | generalization.sortPublications(pubs), ['0.1'], generalization) |
540 | 630 | self.assertEqual(None, pubs[1].supersededby) | 660 | self.assertEqual(None, pubs[1].supersededby) |
541 | 631 | self.assertEqual(PackagePublishingStatus.DELETED, pubs[1].status) | 661 | self.assertEqual(PackagePublishingStatus.DELETED, pubs[1].status) |
542 | 632 | 662 | ||
543 | 633 | def test_dominatePackage_supersedes_replaced_pub_for_live_version(self): | 663 | def test_dominatePackage_supersedes_replaced_pub_for_live_version(self): |
544 | 634 | # Even if a publication record is for a live version, a newer | 664 | # Even if a publication record is for a live version, a newer |
545 | 635 | # one for the same version supersedes it. | 665 | # one for the same version supersedes it. |
546 | 666 | generalization = GeneralizedPublication(True) | ||
547 | 636 | spr = self.factory.makeSourcePackageRelease() | 667 | spr = self.factory.makeSourcePackageRelease() |
548 | 637 | series = self.factory.makeDistroSeries() | 668 | series = self.factory.makeDistroSeries() |
549 | 638 | pocket = PackagePublishingPocket.RELEASE | 669 | pocket = PackagePublishingPocket.RELEASE |
550 | @@ -649,7 +680,8 @@ | |||
551 | 649 | ]) | 680 | ]) |
552 | 650 | 681 | ||
553 | 651 | self.makeDominator(pubs).dominatePackage( | 682 | self.makeDominator(pubs).dominatePackage( |
555 | 652 | pubs, [spr.version], GeneralizedPublication(True)) | 683 | generalization.sortPublications(pubs), [spr.version], |
556 | 684 | generalization) | ||
557 | 653 | self.assertEqual([ | 685 | self.assertEqual([ |
558 | 654 | PackagePublishingStatus.SUPERSEDED, | 686 | PackagePublishingStatus.SUPERSEDED, |
559 | 655 | PackagePublishingStatus.SUPERSEDED, | 687 | PackagePublishingStatus.SUPERSEDED, |
560 | @@ -661,12 +693,13 @@ | |||
561 | 661 | 693 | ||
562 | 662 | def test_dominatePackage_is_efficient(self): | 694 | def test_dominatePackage_is_efficient(self): |
563 | 663 | # dominatePackage avoids issuing too many queries. | 695 | # dominatePackage avoids issuing too many queries. |
564 | 696 | generalization = GeneralizedPublication(True) | ||
565 | 664 | versions = ["1.%s" % revision for revision in xrange(5)] | 697 | versions = ["1.%s" % revision for revision in xrange(5)] |
566 | 665 | pubs = make_spphs_for_versions(self.factory, versions) | 698 | pubs = make_spphs_for_versions(self.factory, versions) |
567 | 666 | with StormStatementRecorder() as recorder: | 699 | with StormStatementRecorder() as recorder: |
568 | 667 | self.makeDominator(pubs).dominatePackage( | 700 | self.makeDominator(pubs).dominatePackage( |
571 | 668 | pubs, versions[2:-1], | 701 | generalization.sortPublications(pubs), versions[2:-1], |
572 | 669 | GeneralizedPublication(True)) | 702 | generalization) |
573 | 670 | self.assertThat(recorder, HasQueryCount(LessThan(5))) | 703 | self.assertThat(recorder, HasQueryCount(LessThan(5))) |
574 | 671 | 704 | ||
575 | 672 | def test_dominatePackage_advanced_scenario(self): | 705 | def test_dominatePackage_advanced_scenario(self): |
576 | @@ -677,6 +710,7 @@ | |||
577 | 677 | # don't just patch up the code or this test. Create unit tests | 710 | # don't just patch up the code or this test. Create unit tests |
578 | 678 | # that specifically cover the difference, then change the code | 711 | # that specifically cover the difference, then change the code |
579 | 679 | # and/or adapt this test to return to harmony. | 712 | # and/or adapt this test to return to harmony. |
580 | 713 | generalization = GeneralizedPublication(True) | ||
581 | 680 | series = self.factory.makeDistroSeries() | 714 | series = self.factory.makeDistroSeries() |
582 | 681 | package = self.factory.makeSourcePackageName() | 715 | package = self.factory.makeSourcePackageName() |
583 | 682 | pocket = PackagePublishingPocket.RELEASE | 716 | pocket = PackagePublishingPocket.RELEASE |
584 | @@ -723,7 +757,8 @@ | |||
585 | 723 | 757 | ||
586 | 724 | all_pubs = sum(pubs_by_version.itervalues(), []) | 758 | all_pubs = sum(pubs_by_version.itervalues(), []) |
587 | 725 | Dominator(DevNullLogger(), series.main_archive).dominatePackage( | 759 | Dominator(DevNullLogger(), series.main_archive).dominatePackage( |
589 | 726 | all_pubs, live_versions, GeneralizedPublication(True)) | 760 | generalization.sortPublications(all_pubs), live_versions, |
590 | 761 | generalization) | ||
591 | 727 | 762 | ||
592 | 728 | for version in reversed(versions): | 763 | for version in reversed(versions): |
593 | 729 | pubs = pubs_by_version[version] | 764 | pubs = pubs_by_version[version] |
594 | @@ -1089,3 +1124,82 @@ | |||
595 | 1089 | published_spphs, | 1124 | published_spphs, |
596 | 1090 | dominator.findSourcesForDomination( | 1125 | dominator.findSourcesForDomination( |
597 | 1091 | spphs[0].distroseries, spphs[0].pocket)) | 1126 | spphs[0].distroseries, spphs[0].pocket)) |
598 | 1127 | |||
599 | 1128 | |||
600 | 1129 | def make_publications_arch_specific(pubs, arch_specific=True): | ||
601 | 1130 | """Set the `architecturespecific` attribute for given SPPHs. | ||
602 | 1131 | |||
603 | 1132 | :param pubs: An iterable of `BinaryPackagePublishingHistory`. | ||
604 | 1133 | :param arch_specific: Whether the binary package releases published | ||
605 | 1134 | by `pubs` are to be architecture-specific. If not, they will be | ||
606 | 1135 | treated as being for the "all" architecture. | ||
607 | 1136 | """ | ||
608 | 1137 | for pub in pubs: | ||
609 | 1138 | bpr = removeSecurityProxy(pub).binarypackagerelease | ||
610 | 1139 | bpr.architecturespecific = arch_specific | ||
611 | 1140 | |||
612 | 1141 | |||
613 | 1142 | class TestLivenessFunctions(TestCaseWithFactory): | ||
614 | 1143 | """Tests for the functions that say which versions are live.""" | ||
615 | 1144 | |||
616 | 1145 | layer = ZopelessDatabaseLayer | ||
617 | 1146 | |||
618 | 1147 | def test_find_live_source_versions_blesses_latest(self): | ||
619 | 1148 | # find_live_source_versions, assuming that you passed it | ||
620 | 1149 | # publications sorted from most current to least current | ||
621 | 1150 | # version, simply returns the most current version. | ||
622 | 1151 | spphs = make_spphs_for_versions(self.factory, ['1.2', '1.1', '1.0']) | ||
623 | 1152 | self.assertEqual(['1.2'], find_live_source_versions(spphs)) | ||
624 | 1153 | |||
625 | 1154 | def test_find_live_binary_versions_pass_1_blesses_latest(self): | ||
626 | 1155 | # find_live_binary_versions_pass_1 always includes the latest | ||
627 | 1156 | # version among the input publications in its result. | ||
628 | 1157 | bpphs = make_bpphs_for_versions(self.factory, ['1.2', '1.1', '1.0']) | ||
629 | 1158 | make_publications_arch_specific(bpphs) | ||
630 | 1159 | self.assertEqual(['1.2'], find_live_binary_versions_pass_1(bpphs)) | ||
631 | 1160 | |||
632 | 1161 | def test_find_live_binary_versions_pass_1_blesses_arch_all(self): | ||
633 | 1162 | # find_live_binary_versions_pass_1 includes any | ||
634 | 1163 | # architecture-independent publications among the input in its | ||
635 | 1164 | # result. | ||
636 | 1165 | versions = list(reversed(['1.%d' % version for version in range(3)])) | ||
637 | 1166 | bpphs = make_bpphs_for_versions(self.factory, versions) | ||
638 | 1167 | |||
639 | 1168 | # All of these publications are architecture-specific, except | ||
640 | 1169 | # the last one. This would happen if the binary package had | ||
641 | 1170 | # just changed from being architecture-specific to being | ||
642 | 1171 | # architecture-independent. | ||
643 | 1172 | make_publications_arch_specific(bpphs, True) | ||
644 | 1173 | make_publications_arch_specific(bpphs[-1:], False) | ||
645 | 1174 | self.assertEqual( | ||
646 | 1175 | versions[:1] + versions[-1:], | ||
647 | 1176 | find_live_binary_versions_pass_1(bpphs)) | ||
648 | 1177 | |||
649 | 1178 | def test_find_live_binary_versions_pass_2_blesses_latest(self): | ||
650 | 1179 | # find_live_binary_versions_pass_2 always includes the latest | ||
651 | 1180 | # version among the input publications in its result. | ||
652 | 1181 | bpphs = make_bpphs_for_versions(self.factory, ['1.2', '1.1', '1.0']) | ||
653 | 1182 | make_publications_arch_specific(bpphs, False) | ||
654 | 1183 | self.assertEqual(['1.2'], find_live_binary_versions_pass_2(bpphs)) | ||
655 | 1184 | |||
656 | 1185 | def test_find_live_binary_versions_pass_2_blesses_arch_specific(self): | ||
657 | 1186 | # find_live_binary_versions_pass_2 includes any | ||
658 | 1187 | # architecture-specific publications among the input in its | ||
659 | 1188 | # result. | ||
660 | 1189 | versions = list(reversed(['1.%d' % version for version in range(3)])) | ||
661 | 1190 | bpphs = make_bpphs_for_versions(self.factory, versions) | ||
662 | 1191 | make_publications_arch_specific(bpphs) | ||
663 | 1192 | self.assertEqual(versions, find_live_binary_versions_pass_2(bpphs)) | ||
664 | 1193 | |||
665 | 1194 | def test_find_live_binary_versions_pass_2_reprieves_arch_all(self): | ||
666 | 1195 | # An arch-all BPPH for a BPR built by an SPR that also still has | ||
667 | 1196 | # active arch-dependent BPPHs gets a reprieve: it can't be | ||
668 | 1197 | # superseded until those arch-dependent BPPHs have been | ||
669 | 1198 | # superseded. | ||
670 | 1199 | bpphs = make_bpphs_for_versions(self.factory, ['1.2', '1.1', '1.0']) | ||
671 | 1200 | make_publications_arch_specific(bpphs, False) | ||
672 | 1201 | dependent = self.factory.makeBinaryPackagePublishingHistory( | ||
673 | 1202 | binarypackagerelease=bpphs[1].binarypackagerelease) | ||
674 | 1203 | make_publications_arch_specific([dependent], True) | ||
675 | 1204 | self.assertEqual( | ||
676 | 1205 | ['1.2', '1.1'], find_live_binary_versions_pass_2(bpphs)) |
Whew. Very extensive commenting, and thanks for it or I don't think I would have followed all of that!
This is some very complex work, but I think it looks good to move forward.