launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #28326
[Merge] ~cjwatson/launchpad:cibuild-stages-in-database into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:cibuild-stages-in-database into launchpad:master.
Commit message:
Store CIBuild stages in the DB for dispatch
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/419193
buildd-manager previously had to fetch a CI build's configuration again from git in order to calculate the `jobs` argument to dispatch, which was poor design: we should instead store everything that buildd-manager needs in the database so that it can dispatch the job immediately.
This is incompatible with previously requested builds: without DB surgery, it won't be possible to dispatch those builds. Since this feature is still rarely used and hasn't yet been announced, I think that's OK.
--
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:cibuild-stages-in-database into launchpad:master.
diff --git a/lib/lp/code/interfaces/cibuild.py b/lib/lp/code/interfaces/cibuild.py
index 470cd95..68fa19f 100644
--- a/lib/lp/code/interfaces/cibuild.py
+++ b/lib/lp/code/interfaces/cibuild.py
@@ -21,6 +21,7 @@ from zope.schema import (
Bool,
Datetime,
Int,
+ List,
TextLine,
)
@@ -115,6 +116,9 @@ class ICIBuildView(IPackageBuildView):
"The date when the build completed or is estimated to complete."),
readonly=True)
+ stages = List(
+ title=_("A list of stages in this build's configured pipeline."))
+
def getConfiguration(logger=None):
"""Fetch a CI build's .launchpad.yaml from code hosting, if possible.
@@ -159,7 +163,7 @@ class ICIBuild(ICIBuildView, ICIBuildEdit, ICIBuildAdmin, IPackageBuild):
class ICIBuildSet(ISpecificBuildFarmJobSource):
"""Utility to create and access `ICIBuild`s."""
- def new(git_repository, commit_sha1, distro_arch_series,
+ def new(git_repository, commit_sha1, distro_arch_series, stages,
date_created=DEFAULT):
"""Create an `ICIBuild`."""
@@ -171,7 +175,7 @@ class ICIBuildSet(ISpecificBuildFarmJobSource):
these Git commit IDs.
"""
- def requestBuild(git_repository, commit_sha1, distro_arch_series):
+ def requestBuild(git_repository, commit_sha1, distro_arch_series, stages):
"""Request a CI build.
This checks that the architecture is allowed and that there isn't
@@ -181,6 +185,9 @@ class ICIBuildSet(ISpecificBuildFarmJobSource):
:param commit_sha1: The Git commit ID for the new build.
:param distro_arch_series: The `IDistroArchSeries` that the new
build should run on.
+ :param stages: A list of stages in this build's pipeline according
+ to its `.launchpad.yaml`, each of which is a list of (job_name,
+ job_index) tuples.
:raises CIBuildDisallowedArchitecture: if builds on
`distro_arch_series` are not allowed.
:raises CIBuildAlreadyRequested: if a matching build was already
diff --git a/lib/lp/code/model/cibuild.py b/lib/lp/code/model/cibuild.py
index afd4830..33d8255 100644
--- a/lib/lp/code/model/cibuild.py
+++ b/lib/lp/code/model/cibuild.py
@@ -11,6 +11,7 @@ from datetime import timedelta
from lazr.lifecycle.event import ObjectCreatedEvent
import pytz
+from storm.databases.postgres import JSON
from storm.locals import (
Bool,
DateTime,
@@ -32,6 +33,7 @@ from lp.buildmaster.enums import (
BuildQueueStatus,
BuildStatus,
)
+from lp.buildmaster.interfaces.builder import CannotBuild
from lp.buildmaster.interfaces.buildfarmjob import IBuildFarmJobSource
from lp.buildmaster.model.buildfarmjob import SpecificBuildFarmJobSourceMixin
from lp.buildmaster.model.packagebuild import PackageBuildMixin
@@ -74,6 +76,22 @@ from lp.services.propertycache import cachedproperty
from lp.soyuz.model.distroarchseries import DistroArchSeries
+def get_stages(configuration):
+ """Extract the job stages for this configuration."""
+ stages = []
+ if not configuration.pipeline:
+ raise CannotBuild("No pipeline stages defined")
+ for stage in configuration.pipeline:
+ jobs = []
+ for job_name in stage:
+ if job_name not in configuration.jobs:
+ raise CannotBuild("No job definition for %r" % job_name)
+ for i in range(len(configuration.jobs[job_name])):
+ jobs.append((job_name, i))
+ stages.append(jobs)
+ return stages
+
+
def determine_DASes_to_build(configuration, logger=None):
"""Generate distroarchseries to build for this configuration."""
architectures_by_series = {}
@@ -182,8 +200,10 @@ class CIBuild(PackageBuildMixin, StormBase):
build_farm_job_id = Int(name="build_farm_job", allow_none=False)
build_farm_job = Reference(build_farm_job_id, "BuildFarmJob.id")
+ _jobs = JSON(name="jobs", allow_none=True)
+
def __init__(self, build_farm_job, git_repository, commit_sha1,
- distro_arch_series, processor, virtualized,
+ distro_arch_series, processor, virtualized, stages,
date_created=DEFAULT):
"""Construct a `CIBuild`."""
super().__init__()
@@ -193,6 +213,7 @@ class CIBuild(PackageBuildMixin, StormBase):
self.distro_arch_series = distro_arch_series
self.processor = processor
self.virtualized = virtualized
+ self._jobs = {"stages": stages}
self.date_created = date_created
self.status = BuildStatus.NEEDSBUILD
@@ -359,6 +380,13 @@ class CIBuild(PackageBuildMixin, StormBase):
"%s: %s" % (msg % self.git_repository.unique_name, e))
return parse_configuration(self.git_repository, blob)
+ @property
+ def stages(self):
+ """See `ICIBuild`."""
+ if self._jobs is None:
+ return []
+ return self._jobs.get("stages", [])
+
def getFileByName(self, filename):
"""See `ICIBuild`."""
if filename.endswith(".txt.gz"):
@@ -385,7 +413,7 @@ class CIBuild(PackageBuildMixin, StormBase):
@implementer(ICIBuildSet)
class CIBuildSet(SpecificBuildFarmJobSourceMixin):
- def new(self, git_repository, commit_sha1, distro_arch_series,
+ def new(self, git_repository, commit_sha1, distro_arch_series, stages,
date_created=DEFAULT):
"""See `ICIBuildSet`."""
store = IMasterStore(CIBuild)
@@ -393,7 +421,7 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
CIBuild.job_type, BuildStatus.NEEDSBUILD, date_created)
cibuild = CIBuild(
build_farm_job, git_repository, commit_sha1, distro_arch_series,
- distro_arch_series.processor, virtualized=True,
+ distro_arch_series.processor, virtualized=True, stages=stages,
date_created=date_created)
store.add(cibuild)
store.flush()
@@ -423,7 +451,8 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
das.getChroot(pocket=pocket) is not None
and self._isBuildableArchitectureAllowed(das))
- def requestBuild(self, git_repository, commit_sha1, distro_arch_series):
+ def requestBuild(self, git_repository, commit_sha1, distro_arch_series,
+ stages):
"""See `ICIBuildSet`."""
pocket = PackagePublishingPocket.UPDATES
if not self._isArchitectureAllowed(distro_arch_series, pocket):
@@ -437,20 +466,21 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
if not result.is_empty():
raise CIBuildAlreadyRequested
- build = self.new(git_repository, commit_sha1, distro_arch_series)
+ build = self.new(
+ git_repository, commit_sha1, distro_arch_series, stages)
build.queueBuild()
notify(ObjectCreatedEvent(build))
return build
def _tryToRequestBuild(self, git_repository, commit_sha1, configuration,
- das, logger):
+ das, stages, logger):
try:
if logger is not None:
logger.info(
"Requesting CI build for %s on %s/%s",
commit_sha1, das.distroseries.name, das.architecturetag,
)
- build = self.requestBuild(git_repository, commit_sha1, das)
+ build = self.requestBuild(git_repository, commit_sha1, das, stages)
# Create reports for each individual job in this build so that
# they show up as pending in the web UI. The job names
# generated here should match those generated by
@@ -458,18 +488,16 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
# lp.archiveuploader.ciupload looks for this report and attaches
# artifacts to it.
rsr_set = getUtility(IRevisionStatusReportSet)
- for stage in configuration.pipeline:
- for job_name in stage:
- for i in range(len(configuration.jobs.get(job_name, []))):
- # XXX cjwatson 2022-03-17: It would be better if we
- # could set some kind of meaningful description as
- # well.
- rsr_set.new(
- creator=git_repository.owner,
- title="%s:%s" % (job_name, i),
- git_repository=git_repository,
- commit_sha1=commit_sha1,
- ci_build=build)
+ for stage in stages:
+ for job_name, i in stage:
+ # XXX cjwatson 2022-03-17: It would be better if we
+ # could set some kind of meaningful description as well.
+ rsr_set.new(
+ creator=git_repository.owner,
+ title="%s:%s" % (job_name, i),
+ git_repository=git_repository,
+ commit_sha1=commit_sha1,
+ ci_build=build)
except CIBuildAlreadyRequested:
pass
except Exception as e:
@@ -498,9 +526,18 @@ class CIBuildSet(SpecificBuildFarmJobSourceMixin):
if logger is not None:
logger.error(e)
continue
+ try:
+ stages = get_stages(configuration)
+ except CannotBuild as e:
+ if logger is not None:
+ logger.error(
+ "Failed to request CI builds for %s: %s",
+ commit["sha1"], e)
+ continue
for das in determine_DASes_to_build(configuration, logger=logger):
self._tryToRequestBuild(
- git_repository, commit["sha1"], configuration, das, logger)
+ git_repository, commit["sha1"], configuration, das, stages,
+ logger)
def getByID(self, build_id):
"""See `ISpecificBuildFarmJobSource`."""
diff --git a/lib/lp/code/model/cibuildbehaviour.py b/lib/lp/code/model/cibuildbehaviour.py
index 4f9d92e..29c91cf 100644
--- a/lib/lp/code/model/cibuildbehaviour.py
+++ b/lib/lp/code/model/cibuildbehaviour.py
@@ -11,9 +11,9 @@ import json
import os
from twisted.internet import defer
-from twisted.internet.threads import deferToThread
from zope.component import adapter
from zope.interface import implementer
+from zope.security.proxy import removeSecurityProxy
from lp.buildmaster.builderproxy import BuilderProxyMixin
from lp.buildmaster.enums import BuildBaseImageType
@@ -25,11 +25,6 @@ from lp.buildmaster.model.buildfarmjobbehaviour import (
BuildFarmJobBehaviourBase,
)
from lp.code.interfaces.cibuild import ICIBuild
-from lp.services.timeout import default_timeout
-from lp.services.webapp.interaction import (
- ANONYMOUS,
- setupInteraction,
- )
from lp.soyuz.adapters.archivedependencies import (
get_sources_list_for_building,
)
@@ -71,49 +66,17 @@ class CIBuildBehaviour(BuilderProxyMixin, BuildFarmJobBehaviourBase):
def extraBuildArgs(self, logger=None):
"""Return extra builder arguments for this build."""
build = self.build
- # Preload the build's repository so that it can be accessed from
- # another thread.
- build.git_repository.id
-
- # XXX cjwatson 2022-03-24: Work around a design error. We ought to
- # have arranged to store the relevant bits of the configuration
- # (i.e. `stages` below) in the database so that we don't need to
- # fetch it again here. It isn't safe to run blocking network
- # requests in buildd-manager's main thread, since that would block
- # the Twisted reactor; defer the request to a thread for now, but
- # we'll need to work out a better fix once we have time.
- def get_configuration():
- setupInteraction(ANONYMOUS)
- with default_timeout(15.0):
- try:
- return build.getConfiguration(logger=logger)
- except Exception as e:
- raise CannotBuild(str(e))
-
- configuration = yield deferToThread(get_configuration)
- stages = []
- if not configuration.pipeline:
+ if not build.stages:
raise CannotBuild(
- "No jobs defined for %s:%s" %
+ "No stages defined for %s:%s" %
(build.git_repository.unique_name, build.commit_sha1))
- for stage in configuration.pipeline:
- jobs = []
- for job_name in stage:
- if job_name not in configuration.jobs:
- raise CannotBuild(
- "Job '%s' in pipeline for %s:%s but not in jobs" %
- (job_name,
- build.git_repository.unique_name, build.commit_sha1))
- for i in range(len(configuration.jobs[job_name])):
- jobs.append((job_name, i))
- stages.append(jobs)
args = yield super().extraBuildArgs(logger=logger)
yield self.addProxyArgs(args)
args["archives"], args["trusted_keys"] = (
yield get_sources_list_for_building(
self, build.distro_arch_series, None, logger=logger))
- args["jobs"] = stages
+ args["jobs"] = removeSecurityProxy(build.stages)
args["git_repository"] = build.git_repository.git_https_url
args["git_path"] = build.commit_sha1
args["private"] = build.is_private
diff --git a/lib/lp/code/model/tests/test_cibuild.py b/lib/lp/code/model/tests/test_cibuild.py
index e49e23e..d44a2fd 100644
--- a/lib/lp/code/model/tests/test_cibuild.py
+++ b/lib/lp/code/model/tests/test_cibuild.py
@@ -405,15 +405,17 @@ class TestCIBuildSet(TestCaseWithFactory):
repository = self.factory.makeGitRepository()
commit_sha1 = hashlib.sha1(self.factory.getUniqueBytes()).hexdigest()
das = self.factory.makeBuildableDistroArchSeries()
+ stages = [[("build", 0)]]
build = getUtility(ICIBuildSet).requestBuild(
- repository, commit_sha1, das)
+ repository, commit_sha1, das, stages)
self.assertTrue(ICIBuild.providedBy(build))
self.assertThat(build, MatchesStructure.byEquality(
git_repository=repository,
commit_sha1=commit_sha1,
distro_arch_series=das,
+ stages=stages,
status=BuildStatus.NEEDSBUILD,
))
store = Store.of(build)
@@ -432,7 +434,7 @@ class TestCIBuildSet(TestCaseWithFactory):
commit_sha1 = hashlib.sha1(self.factory.getUniqueBytes()).hexdigest()
das = self.factory.makeBuildableDistroArchSeries()
build = getUtility(ICIBuildSet).requestBuild(
- repository, commit_sha1, das)
+ repository, commit_sha1, das, [[("test", 0)]])
queue_record = build.buildqueue_record
queue_record.score()
self.assertEqual(2600, queue_record.lastscore)
@@ -447,19 +449,19 @@ class TestCIBuildSet(TestCaseWithFactory):
distroseries=distro_series)
for _ in range(2)]
old_build = getUtility(ICIBuildSet).requestBuild(
- repository, commit_sha1, arches[0])
+ repository, commit_sha1, arches[0], [[("test", 0)]])
self.assertRaises(
CIBuildAlreadyRequested, getUtility(ICIBuildSet).requestBuild,
- repository, commit_sha1, arches[0])
+ repository, commit_sha1, arches[0], [[("test", 0)]])
# We can build for a different distroarchseries.
getUtility(ICIBuildSet).requestBuild(
- repository, commit_sha1, arches[1])
+ repository, commit_sha1, arches[1], [[("test", 0)]])
# Changing the status of the old build does not allow a new build.
old_build.updateStatus(BuildStatus.BUILDING)
old_build.updateStatus(BuildStatus.FULLYBUILT)
self.assertRaises(
CIBuildAlreadyRequested, getUtility(ICIBuildSet).requestBuild,
- repository, commit_sha1, arches[0])
+ repository, commit_sha1, arches[0], [[("test", 0)]])
def test_requestBuild_virtualization(self):
# New builds are virtualized.
@@ -471,7 +473,7 @@ class TestCIBuildSet(TestCaseWithFactory):
distroseries=distro_series, supports_virtualized=True,
supports_nonvirtualized=proc_nonvirt)
build = getUtility(ICIBuildSet).requestBuild(
- repository, commit_sha1, das)
+ repository, commit_sha1, das, [[("test", 0)]])
self.assertTrue(build.virtualized)
def test_requestBuild_nonvirtualized(self):
@@ -484,7 +486,8 @@ class TestCIBuildSet(TestCaseWithFactory):
supports_nonvirtualized=True)
self.assertRaises(
CIBuildDisallowedArchitecture,
- getUtility(ICIBuildSet).requestBuild, repository, commit_sha1, das)
+ getUtility(ICIBuildSet).requestBuild,
+ repository, commit_sha1, das, [[("test", 0)]])
def test_requestBuildsForRefs_triggers_builds(self):
ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
@@ -541,6 +544,8 @@ class TestCIBuildSet(TestCaseWithFactory):
self.assertEqual(ref.commit_sha1, build.commit_sha1)
self.assertEqual("focal", build.distro_arch_series.distroseries.name)
self.assertEqual("amd64", build.distro_arch_series.architecturetag)
+ self.assertEqual(
+ [[("build", 0), ("build", 1)], [("test", 0)]], build.stages)
self.assertThat(reports, MatchesSetwise(*(
MatchesStructure.byEquality(
creator=repository.owner,
@@ -644,6 +649,78 @@ class TestCIBuildSet(TestCaseWithFactory):
logger.getLogBuffer()
)
+ def test_requestBuildsForRefs_no_pipeline_defined(self):
+ # If the job's configuration does not define any pipeline stages,
+ # requestBuildsForRefs logs an error.
+ configuration = b"pipeline: []\njobs: {}\n"
+ [ref] = self.factory.makeGitRefs()
+ encoded_commit_json = {
+ "sha1": ref.commit_sha1,
+ "blobs": {".launchpad.yaml": configuration},
+ }
+ hosting_fixture = self.useFixture(
+ GitHostingFixture(commits=[encoded_commit_json])
+ )
+ logger = BufferLogger()
+
+ getUtility(ICIBuildSet).requestBuildsForRefs(
+ ref.repository, [ref.path], logger)
+
+ self.assertEqual(
+ [((ref.repository.getInternalPath(), [ref.commit_sha1]),
+ {"filter_paths": [".launchpad.yaml"], "logger": logger})],
+ hosting_fixture.getCommits.calls
+ )
+ self.assertTrue(
+ getUtility(ICIBuildSet).findByGitRepository(
+ ref.repository).is_empty()
+ )
+ self.assertTrue(
+ getUtility(IRevisionStatusReportSet).findByRepository(
+ ref.repository).is_empty()
+ )
+ self.assertEqual(
+ "ERROR Failed to request CI builds for %s: "
+ "No pipeline stages defined\n" % ref.commit_sha1,
+ logger.getLogBuffer()
+ )
+
+ def test_requestBuildsForRefs_undefined_job(self):
+ # If the job's configuration has a pipeline that defines a job not
+ # in the jobs matrix, requestBuildsForRefs logs an error.
+ configuration = b"pipeline: [test]\njobs: {}\n"
+ [ref] = self.factory.makeGitRefs()
+ encoded_commit_json = {
+ "sha1": ref.commit_sha1,
+ "blobs": {".launchpad.yaml": configuration},
+ }
+ hosting_fixture = self.useFixture(
+ GitHostingFixture(commits=[encoded_commit_json])
+ )
+ logger = BufferLogger()
+
+ getUtility(ICIBuildSet).requestBuildsForRefs(
+ ref.repository, [ref.path], logger)
+
+ self.assertEqual(
+ [((ref.repository.getInternalPath(), [ref.commit_sha1]),
+ {"filter_paths": [".launchpad.yaml"], "logger": logger})],
+ hosting_fixture.getCommits.calls
+ )
+ self.assertTrue(
+ getUtility(ICIBuildSet).findByGitRepository(
+ ref.repository).is_empty()
+ )
+ self.assertTrue(
+ getUtility(IRevisionStatusReportSet).findByRepository(
+ ref.repository).is_empty()
+ )
+ self.assertEqual(
+ "ERROR Failed to request CI builds for %s: "
+ "No job definition for 'test'\n" % ref.commit_sha1,
+ logger.getLogBuffer()
+ )
+
def test_requestBuildsForRefs_build_already_scheduled(self):
ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
series = self.factory.makeDistroSeries(
diff --git a/lib/lp/code/model/tests/test_cibuildbehaviour.py b/lib/lp/code/model/tests/test_cibuildbehaviour.py
index 091c7be..190622a 100644
--- a/lib/lp/code/model/tests/test_cibuildbehaviour.py
+++ b/lib/lp/code/model/tests/test_cibuildbehaviour.py
@@ -8,7 +8,6 @@ from datetime import datetime
import json
import os.path
import re
-from textwrap import dedent
import time
from urllib.parse import urlsplit
import uuid
@@ -60,9 +59,7 @@ from lp.buildmaster.tests.test_buildfarmjobbehaviour import (
TestHandleStatusMixin,
TestVerifySuccessfulBuildMixin,
)
-from lp.code.errors import GitRepositoryBlobNotFound
from lp.code.model.cibuildbehaviour import CIBuildBehaviour
-from lp.code.tests.helpers import GitHostingFixture
from lp.services.config import config
from lp.services.log.logger import (
BufferLogger,
@@ -179,7 +176,7 @@ class TestAsyncCIBuildBehaviour(StatsMixin, TestCIBuildBehaviourBase):
self.addCleanup(shut_down_default_process_pool)
self.setUpStats()
- def makeJob(self, configuration=_unset, **kwargs):
+ def makeJob(self, **kwargs):
# We need a builder in these tests, in order that requesting a proxy
# token can piggyback on its reactor and pool.
job = super().makeJob(**kwargs)
@@ -188,24 +185,6 @@ class TestAsyncCIBuildBehaviour(StatsMixin, TestCIBuildBehaviourBase):
worker = self.useFixture(WorkerTestHelpers()).getClientWorker()
job.setBuilder(builder, worker)
self.addCleanup(worker.pool.closeCachedConnections)
- if configuration is _unset:
- # Skeleton configuration defining a single job.
- configuration = dedent("""\
- pipeline:
- - [test]
- jobs:
- test:
- series: {}
- architectures: [{}]
- """.format(
- job.build.distro_arch_series.distroseries.name,
- job.build.distro_arch_series.architecturetag)).encode()
- hosting_fixture = self.useFixture(
- GitHostingFixture(blob=configuration, enforce_timeout=True))
- if configuration is None:
- hosting_fixture.getBlob.failure = GitRepositoryBlobNotFound(
- job.build.git_repository.getInternalPath(), ".launchpad.yaml",
- rev=job.build.commit_sha1)
return job
@defer.inlineCallbacks
@@ -261,7 +240,7 @@ class TestAsyncCIBuildBehaviour(StatsMixin, TestCIBuildBehaviourBase):
def test_extraBuildArgs_git(self):
# extraBuildArgs returns appropriate arguments if asked to build a
# job for a Git commit.
- job = self.makeJob()
+ job = self.makeJob(stages=[[("test", 0)]])
expected_archives, expected_trusted_keys = (
yield get_sources_list_for_building(
job, job.build.distro_arch_series, None))
@@ -277,7 +256,7 @@ class TestAsyncCIBuildBehaviour(StatsMixin, TestCIBuildBehaviourBase):
"fast_cleanup": Is(True),
"git_path": Equals(job.build.commit_sha1),
"git_repository": Equals(job.build.git_repository.git_https_url),
- "jobs": Equals([[("test", 0)]]),
+ "jobs": Equals([[["test", 0]]]),
"private": Is(False),
"proxy_url": ProxyURLMatcher(job, self.now),
"revocation_endpoint": RevocationEndpointMatcher(job, self.now),
@@ -340,34 +319,11 @@ class TestAsyncCIBuildBehaviour(StatsMixin, TestCIBuildBehaviourBase):
build_request[4]["proxy_url"], ProxyURLMatcher(job, self.now))
@defer.inlineCallbacks
- def test_composeBuildRequest_unparseable(self):
- # If the job's configuration file fails to parse,
- # composeBuildRequest raises CannotBuild.
- job = self.makeJob(configuration=b"")
- expected_exception_msg = (
- r"Cannot parse \.launchpad\.yaml from .*: "
- r"Empty configuration file")
- with ExpectedException(CannotBuild, expected_exception_msg):
- yield job.composeBuildRequest(None)
-
- @defer.inlineCallbacks
- def test_composeBuildRequest_no_jobs_defined(self):
- # If the job's configuration does not define any jobs,
- # composeBuildRequest raises CannotBuild.
- job = self.makeJob(configuration=b"pipeline: []\njobs: {}\n")
- expected_exception_msg = re.escape(
- "No jobs defined for %s:%s" % (
- job.build.git_repository.unique_name, job.build.commit_sha1))
- with ExpectedException(CannotBuild, expected_exception_msg):
- yield job.composeBuildRequest(None)
-
- @defer.inlineCallbacks
- def test_composeBuildRequest_undefined_job(self):
- # If the job's configuration has a pipeline that defines a job not
- # in the jobs matrix, composeBuildRequest raises CannotBuild.
- job = self.makeJob(configuration=b"pipeline: [test]\njobs: {}\n")
+ def test_composeBuildRequest_no_stages_defined(self):
+ # If the build has no stages, composeBuildRequest raises CannotBuild.
+ job = self.makeJob(stages=[])
expected_exception_msg = re.escape(
- "Job 'test' in pipeline for %s:%s but not in jobs" % (
+ "No stages defined for %s:%s" % (
job.build.git_repository.unique_name, job.build.commit_sha1))
with ExpectedException(CannotBuild, expected_exception_msg):
yield job.composeBuildRequest(None)
@@ -416,15 +372,6 @@ class MakeCIBuildMixin:
def makeBuild(self):
build = self.factory.makeCIBuild(status=BuildStatus.BUILDING)
- das = build.distro_arch_series
- self.useFixture(GitHostingFixture(blob=dedent("""\
- pipeline:
- - [test]
- jobs:
- test:
- series: {}
- architectures: [{}]
- """.format(das.distroseries.name, das.architecturetag)).encode()))
build.queueBuild()
return build
diff --git a/lib/lp/testing/factory.py b/lib/lp/testing/factory.py
index 7c79147..f4162b1 100644
--- a/lib/lp/testing/factory.py
+++ b/lib/lp/testing/factory.py
@@ -5353,7 +5353,7 @@ class BareLaunchpadObjectFactory(ObjectFactory):
processors=processors, date_created=date_created)
def makeCIBuild(self, git_repository=None, commit_sha1=None,
- distro_arch_series=None, date_created=DEFAULT,
+ distro_arch_series=None, stages=None, date_created=DEFAULT,
status=BuildStatus.NEEDSBUILD, builder=None,
duration=None):
"""Make a new `CIBuild`."""
@@ -5363,8 +5363,10 @@ class BareLaunchpadObjectFactory(ObjectFactory):
commit_sha1 = hashlib.sha1(self.getUniqueBytes()).hexdigest()
if distro_arch_series is None:
distro_arch_series = self.makeDistroArchSeries()
+ if stages is None:
+ stages = [[("test", 0)]]
build = getUtility(ICIBuildSet).new(
- git_repository, commit_sha1, distro_arch_series,
+ git_repository, commit_sha1, distro_arch_series, stages,
date_created=date_created)
if duration is not None:
removeSecurityProxy(build).updateStatus(