launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #29491
[Merge] ~andrey-fedoseev/launchpad:bug-task-channel into launchpad:master
Andrey Fedoseev has proposed merging ~andrey-fedoseev/launchpad:bug-task-channel into launchpad:master.
Commit message:
WIP: Add `channel` field to `BugTask` and `SourcePackage`
This should also make a `SourcePackage` with a channel a valid target for a `BugTask`, and some work has been done in that direction, but it may not be 100% complete.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~andrey-fedoseev/launchpad/+git/launchpad/+merge/434693
Currently, it is confirmed to pass all tests in `lp.{bugs,soyuz,registry}` (other packages hasn't been checked)
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~andrey-fedoseev/launchpad:bug-task-channel into launchpad:master.
diff --git a/lib/lp/bugs/configure.zcml b/lib/lp/bugs/configure.zcml
index 6eaa58c..12bfdd7 100644
--- a/lib/lp/bugs/configure.zcml
+++ b/lib/lp/bugs/configure.zcml
@@ -211,6 +211,8 @@
distribution
distroseries
milestone
+ _channel
+ channel
_status
status
status_explanation
diff --git a/lib/lp/bugs/interfaces/bugsummary.py b/lib/lp/bugs/interfaces/bugsummary.py
index 7cb55ef..be83775 100644
--- a/lib/lp/bugs/interfaces/bugsummary.py
+++ b/lib/lp/bugs/interfaces/bugsummary.py
@@ -9,7 +9,7 @@ __all__ = [
]
from zope.interface import Interface
-from zope.schema import Bool, Choice, Int, Object, Text
+from zope.schema import Bool, Choice, Int, Object, Text, TextLine
from lp import _
from lp.bugs.interfaces.bugtask import BugTaskImportance, BugTaskStatusSearch
@@ -51,6 +51,8 @@ class IBugSummary(Interface):
ociproject_id = Int(readonly=True)
ociproject = Object(IOCIProject, readonly=True)
+ channel = TextLine(readonly=True)
+
milestone_id = Int(readonly=True)
milestone = Object(IMilestone, readonly=True)
diff --git a/lib/lp/bugs/interfaces/bugtask.py b/lib/lp/bugs/interfaces/bugtask.py
index a8c4e45..d2586f7 100644
--- a/lib/lp/bugs/interfaces/bugtask.py
+++ b/lib/lp/bugs/interfaces/bugtask.py
@@ -475,6 +475,7 @@ class IBugTask(IHasBug, IBugTaskDelete):
title=_("Series"), required=False, vocabulary="DistroSeries"
)
distroseries_id = Attribute("The distroseries ID")
+ channel = TextLine(title=_("Channel"), required=False)
milestone = exported(
ReferenceChoice(
title=_("Milestone"),
diff --git a/lib/lp/bugs/model/bugsummary.py b/lib/lp/bugs/model/bugsummary.py
index 9084e0e..2caabf6 100644
--- a/lib/lp/bugs/model/bugsummary.py
+++ b/lib/lp/bugs/model/bugsummary.py
@@ -9,6 +9,8 @@ __all__ = [
"get_bugsummary_filter_for_user",
]
+from typing import Optional
+
from storm.base import Storm
from storm.expr import SQL, And, Or, Select
from storm.properties import Bool, Int, Unicode
@@ -32,9 +34,10 @@ from lp.registry.model.product import Product
from lp.registry.model.productseries import ProductSeries
from lp.registry.model.sourcepackagename import SourcePackageName
from lp.registry.model.teammembership import TeamParticipation
+from lp.services.channels import channel_list_to_string
from lp.services.database.enumcol import DBEnum
from lp.services.database.interfaces import IStore
-from lp.services.database.stormexpr import WithMaterialized
+from lp.services.database.stormexpr import ImmutablePgJSON, WithMaterialized
@implementer(IBugSummary)
@@ -64,6 +67,8 @@ class BugSummary(Storm):
ociproject_id = Int(name="ociproject")
ociproject = Reference(ociproject_id, "OCIProject.id")
+ _channel = ImmutablePgJSON(name="channel")
+
milestone_id = Int(name="milestone")
milestone = Reference(milestone_id, Milestone.id)
@@ -80,6 +85,12 @@ class BugSummary(Storm):
has_patch = Bool()
+ @property
+ def channel(self) -> Optional[str]:
+ if self._channel is None:
+ return None
+ return channel_list_to_string(*self._channel)
+
@implementer(IBugSummaryDimension)
class CombineBugSummaryConstraint:
diff --git a/lib/lp/bugs/model/bugtask.py b/lib/lp/bugs/model/bugtask.py
index c006618..8955720 100644
--- a/lib/lp/bugs/model/bugtask.py
+++ b/lib/lp/bugs/model/bugtask.py
@@ -21,6 +21,7 @@ import re
from collections import defaultdict
from itertools import chain, repeat
from operator import attrgetter, itemgetter
+from typing import Optional
import pytz
from lazr.lifecycle.event import ObjectDeletedEvent
@@ -98,6 +99,7 @@ from lp.registry.interfaces.sourcepackagename import ISourcePackageNameSet
from lp.registry.model.pillar import pillar_sort_key
from lp.registry.model.sourcepackagename import SourcePackageName
from lp.services import features
+from lp.services.channels import channel_list_to_string, channel_string_to_list
from lp.services.database.bulk import create, load, load_related
from lp.services.database.constants import UTC_NOW
from lp.services.database.decoratedresultset import DecoratedResultSet
@@ -111,6 +113,7 @@ from lp.services.database.sqlbase import (
sqlvalues,
)
from lp.services.database.stormbase import StormBase
+from lp.services.database.stormexpr import ImmutablePgJSON
from lp.services.helpers import shortlist
from lp.services.propertycache import get_property_cache
from lp.services.searchbuilder import any
@@ -171,6 +174,7 @@ def bug_target_from_key(
distroseries,
sourcepackagename,
ociproject,
+ channel,
):
"""Returns the IBugTarget defined by the given DB column values."""
if ociproject:
@@ -189,7 +193,7 @@ def bug_target_from_key(
return distribution
elif distroseries:
if sourcepackagename:
- return distroseries.getSourcePackage(sourcepackagename)
+ return distroseries.getSourcePackage(sourcepackagename, channel)
else:
return distroseries
else:
@@ -205,6 +209,7 @@ def bug_target_to_key(target):
distroseries=None,
sourcepackagename=None,
ociproject=None,
+ channel=None,
)
if IProduct.providedBy(target):
values["product"] = target
@@ -220,6 +225,7 @@ def bug_target_to_key(target):
elif ISourcePackage.providedBy(target):
values["distroseries"] = target.distroseries
values["sourcepackagename"] = target.sourcepackagename
+ values["channel"] = target.channel
elif IOCIProject.providedBy(target):
# De-normalize the ociproject, including also the ociproject's
# pillar (distribution or product).
@@ -499,6 +505,8 @@ class BugTask(StormBase):
distroseries_id = Int(name="distroseries", allow_none=True)
distroseries = Reference(distroseries_id, "DistroSeries.id")
+ _channel = ImmutablePgJSON(name="channel", allow_none=True)
+
milestone_id = Int(
name="milestone",
allow_none=True,
@@ -613,6 +621,19 @@ class BugTask(StormBase):
)
@property
+ def channel(self) -> Optional[str]:
+ if self._channel is None:
+ return None
+ return channel_list_to_string(*self._channel)
+
+ @channel.setter
+ def channel(self, value: str) -> None:
+ if value is None:
+ self._channel = None
+ else:
+ self._channel = channel_string_to_list(value)
+
+ @property
def status(self):
if self._status in DB_INCOMPLETE_BUGTASK_STATUSES:
return BugTaskStatus.INCOMPLETE
@@ -652,6 +673,7 @@ class BugTask(StormBase):
self.distroseries,
self.sourcepackagename,
self.ociproject,
+ self.channel,
)
@property
@@ -1863,6 +1885,9 @@ class BugTaskSet:
key["distroseries"],
key["sourcepackagename"],
key["ociproject"],
+ channel_string_to_list(key["channel"])
+ if key["channel"]
+ else None,
status,
importance,
assignee,
@@ -1880,6 +1905,7 @@ class BugTaskSet:
BugTask.distroseries,
BugTask.sourcepackagename,
BugTask.ociproject,
+ BugTask._channel,
BugTask._status,
BugTask.importance,
BugTask.assignee,
diff --git a/lib/lp/bugs/model/bugtaskflat.py b/lib/lp/bugs/model/bugtaskflat.py
index b25a440..b636ecf 100644
--- a/lib/lp/bugs/model/bugtaskflat.py
+++ b/lib/lp/bugs/model/bugtaskflat.py
@@ -1,6 +1,5 @@
# Copyright 2012-2020 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
-
from storm.locals import Bool, DateTime, Int, List, Reference, Storm
from lp.app.enums import InformationType
@@ -10,6 +9,7 @@ from lp.bugs.interfaces.bugtask import (
BugTaskStatusSearch,
)
from lp.services.database.enumcol import DBEnum
+from lp.services.database.stormexpr import ImmutablePgJSON
class BugTaskFlat(Storm):
@@ -42,6 +42,7 @@ class BugTaskFlat(Storm):
sourcepackagename = Reference(sourcepackagename_id, "SourcePackageName.id")
ociproject_id = Int(name="ociproject")
ociproject = Reference(ociproject_id, "OCIProject.id")
+ channel = ImmutablePgJSON()
status = DBEnum(enum=(BugTaskStatus, BugTaskStatusSearch))
importance = DBEnum(enum=BugTaskImportance)
assignee_id = Int(name="assignee")
diff --git a/lib/lp/bugs/model/tests/test_bugtask.py b/lib/lp/bugs/model/tests/test_bugtask.py
index c3eb248..ecb258f 100644
--- a/lib/lp/bugs/model/tests/test_bugtask.py
+++ b/lib/lp/bugs/model/tests/test_bugtask.py
@@ -3145,6 +3145,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
distroseries=None,
sourcepackagename=None,
ociproject=None,
+ channel=None,
),
)
@@ -3159,6 +3160,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
distroseries=None,
sourcepackagename=None,
ociproject=None,
+ channel=None,
),
)
@@ -3173,6 +3175,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
distroseries=None,
sourcepackagename=None,
ociproject=None,
+ channel=None,
),
)
@@ -3187,6 +3190,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
distroseries=distroseries,
sourcepackagename=None,
ociproject=None,
+ channel=None,
),
)
@@ -3201,6 +3205,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
distroseries=None,
sourcepackagename=dsp.sourcepackagename,
ociproject=None,
+ channel=None,
),
)
@@ -3215,6 +3220,22 @@ class TestBugTargetKeys(TestCaseWithFactory):
distroseries=sp.distroseries,
sourcepackagename=sp.sourcepackagename,
ociproject=None,
+ channel=None,
+ ),
+ )
+
+ def test_sourcepackage_with_channel(self):
+ sp = self.factory.makeSourcePackage(channel="stable")
+ self.assertTargetKeyWorks(
+ sp,
+ dict(
+ product=None,
+ productseries=None,
+ distribution=None,
+ distroseries=sp.distroseries,
+ sourcepackagename=sp.sourcepackagename,
+ ociproject=None,
+ channel="stable",
),
)
@@ -3230,6 +3251,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
distroseries=None,
sourcepackagename=None,
ociproject=ociproject,
+ channel=None,
),
)
@@ -3245,6 +3267,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
distroseries=None,
sourcepackagename=None,
ociproject=ociproject,
+ channel=None,
),
)
@@ -3263,6 +3286,7 @@ class TestBugTargetKeys(TestCaseWithFactory):
None,
None,
None,
+ None,
)
diff --git a/lib/lp/bugs/scripts/bugsummaryrebuild.py b/lib/lp/bugs/scripts/bugsummaryrebuild.py
index 21003b4..cf9ae8a 100644
--- a/lib/lp/bugs/scripts/bugsummaryrebuild.py
+++ b/lib/lp/bugs/scripts/bugsummaryrebuild.py
@@ -24,6 +24,7 @@ from lp.registry.model.ociproject import OCIProject
from lp.registry.model.product import Product
from lp.registry.model.productseries import ProductSeries
from lp.registry.model.sourcepackagename import SourcePackageName
+from lp.services.channels import channel_string_to_list
from lp.services.database.bulk import create
from lp.services.database.interfaces import IStore
from lp.services.database.stormexpr import Unnest
@@ -109,7 +110,7 @@ def load_target(pid, psid, did, dsid, spnid, ociproject_id):
(pid, psid, did, dsid, spnid, ociproject_id),
),
)
- return bug_target_from_key(p, ps, d, ds, spn, ociproject)
+ return bug_target_from_key(p, ps, d, ds, spn, ociproject, None)
def format_target(target):
@@ -130,7 +131,15 @@ def format_target(target):
def _get_bugsummary_constraint_bits(target):
raw_key = bug_target_to_key(target)
# Map to ID columns to work around Storm bug #682989.
- return {"%s_id" % k: v.id if v else None for (k, v) in raw_key.items()}
+ constraint_bits = {}
+ for name, value in raw_key.items():
+ if name == "channel":
+ constraint_bits["_channel"] = (
+ channel_string_to_list(value) if value else None
+ )
+ else:
+ constraint_bits["{}_id".format(name)] = value.id if value else None
+ return constraint_bits
def get_bugsummary_constraint(target, cls=RawBugSummary):
@@ -154,10 +163,19 @@ def get_bugtaskflat_constraint(target):
if IProduct.providedBy(target):
del raw_key["ociproject"]
# Map to ID columns to work around Storm bug #682989.
- return [
- getattr(BugTaskFlat, "%s_id" % k) == (v.id if v else None)
- for (k, v) in raw_key.items()
- ]
+ constraint_bits = []
+ for name, value in raw_key.items():
+ if name == "channel":
+ constraint_bits.append(
+ getattr(BugTaskFlat, name)
+ == (channel_string_to_list(value) if value else None)
+ )
+ else:
+ constraint_bits.append(
+ getattr(BugTaskFlat, "{}_id".format(name))
+ == (value.id if value else None)
+ )
+ return constraint_bits
def get_bugsummary_rows(target):
@@ -167,6 +185,7 @@ def get_bugsummary_rows(target):
with BugSummary which is actually combinedbugsummary, a view over
bugsummary and bugsummaryjournal.
"""
+ constraint = get_bugsummary_constraint(target)
return IStore(RawBugSummary).find(
(
RawBugSummary.status,
@@ -178,7 +197,7 @@ def get_bugsummary_rows(target):
RawBugSummary.access_policy_id,
RawBugSummary.count,
),
- *get_bugsummary_constraint(target),
+ *constraint,
)
@@ -191,7 +210,7 @@ def get_bugsummaryjournal_rows(target):
def calculate_bugsummary_changes(old, new):
- """Calculate the changes between between the new and old dicts.
+ """Calculate the changes between the new and old dicts.
Takes {key: int} dicts, returns items from the new dict that differ
from the old one.
@@ -219,18 +238,14 @@ def calculate_bugsummary_changes(old, new):
def apply_bugsummary_changes(target, added, updated, removed):
"""Apply a set of BugSummary changes to the DB."""
bits = _get_bugsummary_constraint_bits(target)
- target_key = tuple(
- map(
- bits.get,
- (
- "product_id",
- "productseries_id",
- "distribution_id",
- "distroseries_id",
- "sourcepackagename_id",
- "ociproject_id",
- ),
- )
+ target_key = (
+ bits["product_id"],
+ bits["productseries_id"],
+ bits["distribution_id"],
+ bits["distroseries_id"],
+ bits["sourcepackagename_id"],
+ bits["ociproject_id"],
+ bits["_channel"],
)
target_cols = (
RawBugSummary.product_id,
@@ -239,6 +254,7 @@ def apply_bugsummary_changes(target, added, updated, removed):
RawBugSummary.distroseries_id,
RawBugSummary.sourcepackagename_id,
RawBugSummary.ociproject_id,
+ RawBugSummary._channel,
)
key_cols = (
RawBugSummary.status,
diff --git a/lib/lp/bugs/scripts/bugtasktargetnamecaches.py b/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
index 70a82d4..dcbf424 100644
--- a/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
+++ b/lib/lp/bugs/scripts/bugtasktargetnamecaches.py
@@ -103,10 +103,10 @@ class BugTaskTargetNameCachesTunableLoop:
self.offset += 1
# Resolve the IDs to objects, and get the actual IBugTarget.
# If the ID is None, don't even try to get an object.
- target_objects = (
+ target_objects = [
(store.get(cls, id) if id is not None else None)
for cls, id in zip(target_classes, target_bits)
- )
+ ] + [None]
target = bug_target_from_key(*target_objects)
new_name = target.bugtargetdisplayname
cached_names.discard(new_name)
diff --git a/lib/lp/registry/interfaces/distroseries.py b/lib/lp/registry/interfaces/distroseries.py
index 03e6d68..5a718c2 100644
--- a/lib/lp/registry/interfaces/distroseries.py
+++ b/lib/lp/registry/interfaces/distroseries.py
@@ -667,7 +667,7 @@ class IDistroSeriesPublic(
@operation_returns_entry(ISourcePackage)
@export_read_operation()
@operation_for_version("beta")
- def getSourcePackage(name):
+ def getSourcePackage(name, channel=None):
"""Return a source package in this distro series by name.
The name given may be a string or an ISourcePackageName-providing
diff --git a/lib/lp/registry/interfaces/sourcepackage.py b/lib/lp/registry/interfaces/sourcepackage.py
index d8f776e..a4505c0 100644
--- a/lib/lp/registry/interfaces/sourcepackage.py
+++ b/lib/lp/registry/interfaces/sourcepackage.py
@@ -130,6 +130,15 @@ class ISourcePackagePublic(
sourcepackagename = Attribute("SourcePackageName")
+ channel = exported(
+ TextLine(
+ title=_("Channel"),
+ required=False,
+ readonly=True,
+ description=_("The channel for this source package."),
+ ),
+ )
+
# This is really a reference to an IProductSeries.
productseries = exported(
ReferenceChoice(
@@ -362,11 +371,12 @@ class ISourcePackage(ISourcePackagePublic, ISourcePackageEdit):
class ISourcePackageFactory(Interface):
"""A creator of source packages."""
- def new(sourcepackagename, distroseries):
+ def new(sourcepackagename, distroseries, channel=None):
"""Create a new `ISourcePackage`.
:param sourcepackagename: An `ISourcePackageName`.
:param distroseries: An `IDistroSeries`.
+ :param channel: A channel name or None.
:return: `ISourcePackage`.
"""
diff --git a/lib/lp/registry/model/distroseries.py b/lib/lp/registry/model/distroseries.py
index 7844249..a128ed5 100644
--- a/lib/lp/registry/model/distroseries.py
+++ b/lib/lp/registry/model/distroseries.py
@@ -13,6 +13,7 @@ __all__ = [
import collections
from io import BytesIO
from operator import itemgetter
+from typing import Optional
import apt_pkg
from lazr.delegates import delegate_to
@@ -1036,7 +1037,7 @@ class DistroSeries(
self.messagecount = messagecount
ztm.commit()
- def getSourcePackage(self, name):
+ def getSourcePackage(self, name, channel: Optional[str] = None):
"""See `IDistroSeries`."""
if not ISourcePackageName.providedBy(name):
try:
@@ -1044,7 +1045,7 @@ class DistroSeries(
except SQLObjectNotFound:
return None
return getUtility(ISourcePackageFactory).new(
- sourcepackagename=name, distroseries=self
+ sourcepackagename=name, distroseries=self, channel=channel
)
def getBinaryPackage(self, name):
diff --git a/lib/lp/registry/model/sourcepackage.py b/lib/lp/registry/model/sourcepackage.py
index 8a0280b..5aff937 100644
--- a/lib/lp/registry/model/sourcepackage.py
+++ b/lib/lp/registry/model/sourcepackage.py
@@ -8,7 +8,9 @@ __all__ = [
"SourcePackageQuestionTargetMixin",
]
+import json
from operator import attrgetter, itemgetter
+from typing import Optional
from storm.locals import And, Desc, Join, Store
from zope.component import getUtility
@@ -42,6 +44,7 @@ from lp.registry.interfaces.sourcepackage import (
from lp.registry.model.hasdrivers import HasDriversMixin
from lp.registry.model.packaging import Packaging
from lp.registry.model.suitesourcepackage import SuiteSourcePackage
+from lp.services.channels import channel_string_to_list
from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.database.interfaces import IStore
from lp.services.database.sqlbase import flush_database_updates, sqlvalues
@@ -204,7 +207,9 @@ class SourcePackage(
to the relevant database objects.
"""
- def __init__(self, sourcepackagename, distroseries):
+ def __init__(
+ self, sourcepackagename, distroseries, channel: Optional[str] = None
+ ):
# We store the ID of the sourcepackagename and distroseries
# simply because Storm can break when accessing them
# with implicit flush is blocked (like in a permission check when
@@ -213,11 +218,14 @@ class SourcePackage(
self.sourcepackagename = sourcepackagename
self.distroseries = distroseries
self.distroseriesID = distroseries.id
+ self.channel = channel
@classmethod
- def new(cls, sourcepackagename, distroseries):
+ def new(
+ cls, sourcepackagename, distroseries, channel: Optional[str] = None
+ ):
"""See `ISourcePackageFactory`."""
- return cls(sourcepackagename, distroseries)
+ return cls(sourcepackagename, distroseries, channel=channel)
def __repr__(self):
return "<%s %r %r %r>" % (
@@ -242,6 +250,12 @@ class SourcePackage(
== self.sourcepackagename,
SourcePackagePublishingHistory.distroseries
== self.distroseries,
+ SourcePackagePublishingHistory._channel
+ == (
+ None
+ if self.channel is None
+ else channel_string_to_list(self.channel)
+ ),
SourcePackagePublishingHistory.archiveID.is_in(
self.distribution.all_distro_archive_ids
),
@@ -296,24 +310,27 @@ class SourcePackage(
)
@property
+ def series_name(self):
+ series_name = self.distroseries.fullseriesname
+ if self.channel is not None:
+ series_name = "%s, %s" % (series_name, self.channel)
+ return series_name
+
+ @property
def display_name(self):
- return "%s in %s %s" % (
- self.sourcepackagename.name,
- self.distribution.displayname,
- self.distroseries.displayname,
- )
+ return "%s in %s" % (self.sourcepackagename.name, self.series_name)
displayname = display_name
@property
def bugtargetdisplayname(self):
"""See IBugTarget."""
- return "%s (%s)" % (self.name, self.distroseries.fullseriesname)
+ return "%s (%s)" % (self.name, self.series_name)
@property
def bugtargetname(self):
"""See `IBugTarget`."""
- return "%s (%s)" % (self.name, self.distroseries.fullseriesname)
+ return "%s (%s)" % (self.name, self.series_name)
@property
def bugtarget_parent(self):
@@ -551,6 +568,12 @@ class SourcePackage(
return And(
BugSummary.distroseries == self.distroseries,
BugSummary.sourcepackagename == self.sourcepackagename,
+ BugSummary._channel
+ == (
+ None
+ if self.channel is None
+ else channel_string_to_list(self.channel)
+ ),
)
def setPackaging(self, productseries, owner):
@@ -616,7 +639,11 @@ class SourcePackage(
def __hash__(self):
"""See `ISourcePackage`."""
- return hash(self.distroseriesID) ^ hash(self.sourcepackagenameID)
+ return (
+ hash(self.distroseriesID)
+ ^ hash(self.sourcepackagenameID)
+ ^ hash(self.channel)
+ )
def __eq__(self, other):
"""See `ISourcePackage`."""
@@ -624,6 +651,7 @@ class SourcePackage(
(ISourcePackage.providedBy(other))
and (self.distroseries.id == other.distroseries.id)
and (self.sourcepackagename.id == other.sourcepackagename.id)
+ and (self.channel == other.channel)
)
def __ne__(self, other):
@@ -672,6 +700,16 @@ class SourcePackage(
)
]
+ if self.channel is None:
+ condition_clauses.append(
+ "SourcePackagePublishingHistory.channel IS NULL"
+ )
+ else:
+ condition_clauses.append(
+ "SourcePackagePublishingHistory.channel = '%s'::jsonb"
+ % json.dumps(channel_string_to_list(self.channel))
+ )
+
# We re-use the optional-parameter handling provided by BuildSet
# here, but pass None for the name argument as we've already
# matched on exact source package name.
@@ -882,16 +920,26 @@ class SourcePackage(
def weight_function(bugtask):
if bugtask.sourcepackagename_id == sourcepackagenameID:
- if bugtask.distroseries_id == seriesID:
+ if (
+ bugtask.distroseries_id == seriesID
+ and bugtask.channel == self.channel
+ ):
return OrderedBugTask(1, bugtask.id, bugtask)
- elif bugtask.distribution_id == distributionID:
+ elif bugtask.distroseries_id == seriesID:
return OrderedBugTask(2, bugtask.id, bugtask)
+ elif bugtask.distribution_id == distributionID:
+ return OrderedBugTask(3, bugtask.id, bugtask)
+ elif (
+ bugtask.distroseries_id == seriesID
+ and bugtask.channel == self.channel
+ ):
+ return OrderedBugTask(4, bugtask.id, bugtask)
elif bugtask.distroseries_id == seriesID:
- return OrderedBugTask(3, bugtask.id, bugtask)
+ return OrderedBugTask(5, bugtask.id, bugtask)
elif bugtask.distribution_id == distributionID:
- return OrderedBugTask(4, bugtask.id, bugtask)
+ return OrderedBugTask(6, bugtask.id, bugtask)
# Catch the default case, and where there is a task for the same
# sourcepackage on a different distro.
- return OrderedBugTask(5, bugtask.id, bugtask)
+ return OrderedBugTask(7, bugtask.id, bugtask)
return weight_function
diff --git a/lib/lp/registry/stories/webservice/xx-source-package.rst b/lib/lp/registry/stories/webservice/xx-source-package.rst
index d09785d..b5c17bc 100644
--- a/lib/lp/registry/stories/webservice/xx-source-package.rst
+++ b/lib/lp/registry/stories/webservice/xx-source-package.rst
@@ -32,6 +32,7 @@ distribution series.
>>> pprint_entry(evolution)
bug_reported_acknowledgement: None
bug_reporting_guidelines: None
+ channel: None
displayname: 'evolution in My-distro My-series'
distribution_link: 'http://.../my-distro'
distroseries_link: 'http://.../my-distro/my-series'
diff --git a/lib/lp/registry/tests/test_distributionsourcepackage.py b/lib/lp/registry/tests/test_distributionsourcepackage.py
index d8dffd9..6262cb4 100644
--- a/lib/lp/registry/tests/test_distributionsourcepackage.py
+++ b/lib/lp/registry/tests/test_distributionsourcepackage.py
@@ -49,7 +49,7 @@ class TestDistributionSourcePackage(TestCaseWithFactory):
registrant=self.factory.makePerson(),
)
naked_distribution = removeSecurityProxy(distribution)
- self.factory.makeSourcePackage(distroseries=distribution)
+ self.factory.makeDistributionSourcePackage(distribution=distribution)
dsp = naked_distribution.getSourcePackage(name="pmount")
self.assertEqual(None, dsp.summary)
diff --git a/lib/lp/registry/tests/test_distroseries.py b/lib/lp/registry/tests/test_distroseries.py
index ab3c6bf..168386f 100644
--- a/lib/lp/registry/tests/test_distroseries.py
+++ b/lib/lp/registry/tests/test_distroseries.py
@@ -449,6 +449,12 @@ class TestDistroSeries(TestCaseWithFactory):
]
)
+ def test_getSourcePackage_channel(self):
+ distroseries = self.factory.makeDistroSeries()
+ spn = self.factory.makeSourcePackageName()
+ source_package = distroseries.getSourcePackage(spn, channel="stable")
+ self.assertEqual("stable", source_package.channel)
+
class TestDistroSeriesPackaging(TestCaseWithFactory):
diff --git a/lib/lp/registry/tests/test_sourcepackage.py b/lib/lp/registry/tests/test_sourcepackage.py
index b839a18..a74f23a 100644
--- a/lib/lp/registry/tests/test_sourcepackage.py
+++ b/lib/lp/registry/tests/test_sourcepackage.py
@@ -506,6 +506,66 @@ class TestSourcePackage(TestCaseWithFactory):
sourcepackage.personHasDriverRights(distroseries.owner)
)
+ def test_channel(self):
+ source_package = self.factory.makeSourcePackage(channel="stable")
+ self.assertEqual("stable", source_package.channel)
+
+ def test_hash(self):
+ spn = self.factory.makeSourcePackageName()
+ distroseries = self.factory.makeDistroSeries()
+ self.assertEqual(
+ hash(
+ self.factory.makeSourcePackage(
+ sourcepackagename=spn, distroseries=distroseries
+ )
+ ),
+ hash(
+ self.factory.makeSourcePackage(
+ sourcepackagename=spn, distroseries=distroseries
+ )
+ ),
+ )
+ self.assertNotEqual(
+ hash(
+ self.factory.makeSourcePackage(
+ sourcepackagename=spn,
+ distroseries=distroseries,
+ channel="stable",
+ )
+ ),
+ hash(
+ self.factory.makeSourcePackage(
+ sourcepackagename=spn,
+ distroseries=distroseries,
+ channel="beta",
+ )
+ ),
+ )
+
+ def test_eq(self):
+ spn = self.factory.makeSourcePackageName()
+ distroseries = self.factory.makeDistroSeries()
+ self.assertEqual(
+ self.factory.makeSourcePackage(
+ sourcepackagename=spn, distroseries=distroseries
+ ),
+ self.factory.makeSourcePackage(
+ sourcepackagename=spn, distroseries=distroseries
+ ),
+ )
+ self.assertNotEqual(
+ self.factory.makeSourcePackage(
+ sourcepackagename=spn,
+ distroseries=distroseries,
+ channel="stable",
+ ),
+ self.factory.makeSourcePackage(
+ sourcepackagename=spn,
+ distroseries=distroseries,
+ channel="beta",
+ ),
+ )
+
class TestSourcePackageWebService(WebServiceTestCase):
def test_setPackaging(self):
diff --git a/lib/lp/soyuz/tests/test_binarypackagebuild.py b/lib/lp/soyuz/tests/test_binarypackagebuild.py
index ef513d3..939e6c1 100644
--- a/lib/lp/soyuz/tests/test_binarypackagebuild.py
+++ b/lib/lp/soyuz/tests/test_binarypackagebuild.py
@@ -433,13 +433,19 @@ class BaseTestCaseWithThreeBuilds(TestCaseWithFactory):
"""Publish some builds for the test archive."""
super().setUp()
self.ds = self.factory.makeDistroSeries()
+ self.builds = self.makeBuilds()
+ self.sources = [
+ build.current_source_publication for build in self.builds
+ ]
+
+ def makeBuilds(self):
i386_das = self.factory.makeDistroArchSeries(
distroseries=self.ds, architecturetag="i386"
)
hppa_das = self.factory.makeDistroArchSeries(
distroseries=self.ds, architecturetag="hppa"
)
- self.builds = [
+ return [
self.factory.makeBinaryPackageBuild(
archive=self.ds.main_archive, distroarchseries=i386_das
),
@@ -449,12 +455,10 @@ class BaseTestCaseWithThreeBuilds(TestCaseWithFactory):
pocket=PackagePublishingPocket.PROPOSED,
),
self.factory.makeBinaryPackageBuild(
- archive=self.ds.main_archive, distroarchseries=hppa_das
+ archive=self.ds.main_archive,
+ distroarchseries=hppa_das,
),
]
- self.sources = [
- build.current_source_publication for build in self.builds
- ]
class TestBuildSet(TestCaseWithFactory):
diff --git a/lib/lp/soyuz/tests/test_hasbuildrecords.py b/lib/lp/soyuz/tests/test_hasbuildrecords.py
index b0b8e8c..07c5c26 100644
--- a/lib/lp/soyuz/tests/test_hasbuildrecords.py
+++ b/lib/lp/soyuz/tests/test_hasbuildrecords.py
@@ -2,7 +2,7 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
"""Test implementations of the IHasBuildRecords interface."""
-
+from testscenarios import WithScenarios
from zope.component import getUtility
from zope.security.proxy import removeSecurityProxy
@@ -14,12 +14,11 @@ from lp.buildmaster.interfaces.buildfarmjob import (
)
from lp.registry.interfaces.person import IPersonSet
from lp.registry.interfaces.pocket import PackagePublishingPocket
+from lp.registry.interfaces.sourcepackage import SourcePackageType
from lp.registry.model.sourcepackage import SourcePackage
-from lp.services.database.interfaces import IStore
from lp.soyuz.enums import ArchivePurpose
from lp.soyuz.interfaces.binarypackagebuild import IBinaryPackageBuild
from lp.soyuz.interfaces.buildrecords import IHasBuildRecords
-from lp.soyuz.model.publishing import SourcePackagePublishingHistory
from lp.soyuz.tests.test_binarypackagebuild import BaseTestCaseWithThreeBuilds
from lp.soyuz.tests.test_publishing import SoyuzTestPublisher
from lp.testing import TestCaseWithFactory, person_logged_in
@@ -256,30 +255,64 @@ class TestBuilderHasBuildRecords(TestHasBuildRecordsInterface):
)
-class TestSourcePackageHasBuildRecords(TestHasBuildRecordsInterface):
+class TestSourcePackageHasBuildRecords(
+ WithScenarios, TestHasBuildRecordsInterface
+):
"""Test the SourcePackage implementation of IHasBuildRecords."""
+ scenarios = [
+ (
+ "channel",
+ {"channel": "stable", "format": SourcePackageType.CI_BUILD},
+ ),
+ ("no_channel", {"channel": None, "format": None}),
+ ]
+
def setUp(self):
super().setUp()
gedit_name = self.builds[0].source_package_release.sourcepackagename
self.context = SourcePackage(
- gedit_name, self.builds[0].distro_arch_series.distroseries
+ gedit_name,
+ self.builds[0].distro_arch_series.distroseries,
+ channel=self.channel,
)
- # Convert the other two builds to be builds of
- # gedit as well so that the one source package (gedit) will have
- # three builds.
- for build in self.builds[1:3]:
- spr = build.source_package_release
- removeSecurityProxy(spr).sourcepackagename = gedit_name
- IStore(SourcePackagePublishingHistory).find(
- SourcePackagePublishingHistory, sourcepackagerelease=spr
- ).set(sourcepackagenameID=gedit_name.id)
-
+ def makeBuilds(self):
+ i386_das = self.factory.makeDistroArchSeries(
+ distroseries=self.ds, architecturetag="i386"
+ )
+ hppa_das = self.factory.makeDistroArchSeries(
+ distroseries=self.ds, architecturetag="hppa"
+ )
+ spn = self.factory.makeSourcePackageName()
+ builds = [
+ self.factory.makeBinaryPackageBuild(
+ sourcepackagename=spn,
+ archive=self.ds.main_archive,
+ distroarchseries=i386_das,
+ channel=self.channel,
+ format=self.format,
+ ),
+ self.factory.makeBinaryPackageBuild(
+ sourcepackagename=spn,
+ archive=self.ds.main_archive,
+ distroarchseries=i386_das,
+ channel=self.channel,
+ format=self.format,
+ ),
+ self.factory.makeBinaryPackageBuild(
+ sourcepackagename=spn,
+ archive=self.ds.main_archive,
+ distroarchseries=hppa_das,
+ channel=self.channel,
+ format=self.format,
+ ),
+ ]
# Set them as successfully built
- for build in self.builds:
+ for build in builds:
build.updateStatus(BuildStatus.BUILDING)
build.updateStatus(BuildStatus.FULLYBUILT)
+ return builds
def test_get_build_records(self):
# We can fetch builds records from a SourcePackage.
@@ -290,7 +323,7 @@ class TestSourcePackageHasBuildRecords(TestHasBuildRecordsInterface):
builds = self.context.getBuildRecords(
pocket=PackagePublishingPocket.RELEASE
).count()
- self.assertEqual(2, builds)
+ self.assertEqual(3, builds)
builds = self.context.getBuildRecords(
pocket=PackagePublishingPocket.UPDATES
).count()
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index 40a959c..b83a946 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -31,6 +31,7 @@ from functools import wraps
from io import BytesIO
from itertools import count
from textwrap import dedent
+from typing import Optional
import pytz
import six
@@ -2385,6 +2386,7 @@ class LaunchpadObjectFactory(ObjectFactory):
self.makeSourcePackagePublishingHistory(
distroseries=target.distroseries,
sourcepackagename=target.sourcepackagename,
+ channel=target.channel,
)
if IDistributionSourcePackage.providedBy(target):
if publish:
@@ -4575,6 +4577,7 @@ class LaunchpadObjectFactory(ObjectFactory):
distroseries=None,
publish=False,
owner=None,
+ channel: Optional[str] = None,
):
"""Make an `ISourcePackage`.
@@ -4590,7 +4593,9 @@ class LaunchpadObjectFactory(ObjectFactory):
distroseries = self.makeDistroSeries(owner=owner)
if publish:
self.makeSourcePackagePublishingHistory(
- distroseries=distroseries, sourcepackagename=sourcepackagename
+ distroseries=distroseries,
+ sourcepackagename=sourcepackagename,
+ channel=channel,
)
with dbuser("statistician"):
DistributionSourcePackageCache(
@@ -4599,7 +4604,9 @@ class LaunchpadObjectFactory(ObjectFactory):
archive=distroseries.main_archive,
name=sourcepackagename.name,
)
- return distroseries.getSourcePackage(sourcepackagename)
+ return distroseries.getSourcePackage(
+ sourcepackagename, channel=channel
+ )
def getAnySourcePackageUrgency(self):
return SourcePackageUrgency.MEDIUM
@@ -4892,6 +4899,8 @@ class LaunchpadObjectFactory(ObjectFactory):
processor=None,
sourcepackagename=None,
arch_indep=None,
+ channel=None,
+ format=None,
):
"""Create a BinaryPackageBuild.
@@ -4950,12 +4959,14 @@ class LaunchpadObjectFactory(ObjectFactory):
component=multiverse,
distroseries=distroarchseries.distroseries,
sourcepackagename=sourcepackagename,
+ format=format,
)
self.makeSourcePackagePublishingHistory(
distroseries=distroarchseries.distroseries,
archive=archive,
sourcepackagerelease=source_package_release,
pocket=pocket,
+ channel=channel,
)
if status is None:
status = BuildStatus.NEEDSBUILD