Merge lp:~cjwatson/launchpad/snap-auto-build-code into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18118
Proposed branch: lp:~cjwatson/launchpad/snap-auto-build-code
Merge into: lp:launchpad
Prerequisite: lp:~cjwatson/launchpad/snap-auto-build-model
Diff against target: 464 lines (+179/-18)
15 files modified
cronscripts/request_daily_builds.py (+7/-3)
database/schema/security.cfg (+7/-0)
lib/lp/code/configure.zcml (+3/-0)
lib/lp/code/interfaces/branch.py (+4/-1)
lib/lp/code/interfaces/gitrepository.py (+10/-2)
lib/lp/code/model/branch.py (+6/-2)
lib/lp/code/model/gitrepository.py (+7/-2)
lib/lp/code/model/tests/test_gitrepository.py (+38/-0)
lib/lp/code/scripts/tests/test_request_daily_builds.py (+31/-3)
lib/lp/code/subscribers/git.py (+1/-0)
lib/lp/codehosting/scanner/bzrsync.py (+5/-1)
lib/lp/codehosting/scanner/tests/test_bzrsync.py (+27/-0)
lib/lp/snappy/interfaces/snap.py (+7/-2)
lib/lp/snappy/model/snap.py (+5/-2)
lib/lp/snappy/tests/test_snap.py (+21/-0)
To merge this branch: bzr merge lp:~cjwatson/launchpad/snap-auto-build-code
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+297957@code.launchpad.net

Commit message

Mark snap packages stale when their source branches changed, and dispatch automatic builds for them.

Description of the change

Mark snap packages stale when their source branches changed, and dispatch automatic builds for them.

It's almost like recipes, only not quite because of model differences and an expected desire to have builds appear a bit more responsively. But it's close enough that we can reuse most of the infrastructure.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'cronscripts/request_daily_builds.py'
--- cronscripts/request_daily_builds.py 2013-01-07 02:40:55 +0000
+++ cronscripts/request_daily_builds.py 2016-06-29 16:28:24 +0000
@@ -1,9 +1,9 @@
1#!/usr/bin/python -S1#!/usr/bin/python -S
2#2#
3# Copyright 2010 Canonical Ltd. This software is licensed under the3# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
4# GNU Affero General Public License version 3 (see the file LICENSE).4# GNU Affero General Public License version 3 (see the file LICENSE).
55
6"""Request builds for stale daily build recipes."""6"""Request builds for stale daily build recipes and snap packages."""
77
8__metaclass__ = type8__metaclass__ = type
99
@@ -18,6 +18,7 @@
18from lp.services.config import config18from lp.services.config import config
19from lp.services.scripts.base import LaunchpadCronScript19from lp.services.scripts.base import LaunchpadCronScript
20from lp.services.webapp.errorlog import globalErrorUtility20from lp.services.webapp.errorlog import globalErrorUtility
21from lp.snappy.interfaces.snap import ISnapSet
2122
2223
23class RequestDailyBuilds(LaunchpadCronScript):24class RequestDailyBuilds(LaunchpadCronScript):
@@ -32,7 +33,10 @@
32 globalErrorUtility.configure(self.name)33 globalErrorUtility.configure(self.name)
33 source = getUtility(ISourcePackageRecipeBuildSource)34 source = getUtility(ISourcePackageRecipeBuildSource)
34 builds = source.makeDailyBuilds(self.logger)35 builds = source.makeDailyBuilds(self.logger)
35 self.logger.info('Requested %d daily builds.' % len(builds))36 self.logger.info('Requested %d daily recipe builds.' % len(builds))
37 builds = getUtility(ISnapSet).makeAutoBuilds(self.logger)
38 self.logger.info(
39 'Requested %d automatic snap package builds.' % len(builds))
36 transaction.commit()40 transaction.commit()
3741
3842
3943
=== modified file 'database/schema/security.cfg'
--- database/schema/security.cfg 2016-06-14 12:40:30 +0000
+++ database/schema/security.cfg 2016-06-29 16:28:24 +0000
@@ -708,6 +708,8 @@
708public.revisionparent = SELECT, INSERT708public.revisionparent = SELECT, INSERT
709public.revisionproperty = SELECT, INSERT709public.revisionproperty = SELECT, INSERT
710public.seriessourcepackagebranch = SELECT710public.seriessourcepackagebranch = SELECT
711public.snap = SELECT, UPDATE
712public.snapbuild = SELECT
711public.sourcepackagename = SELECT713public.sourcepackagename = SELECT
712public.sourcepackagerecipe = SELECT, UPDATE714public.sourcepackagerecipe = SELECT, UPDATE
713public.sourcepackagerecipedata = SELECT715public.sourcepackagerecipedata = SELECT
@@ -827,8 +829,13 @@
827public.distroarchseries = SELECT829public.distroarchseries = SELECT
828public.distroseries = SELECT830public.distroseries = SELECT
829public.job = SELECT, INSERT831public.job = SELECT, INSERT
832public.libraryfilealias = SELECT
830public.person = SELECT833public.person = SELECT
834public.pocketchroot = SELECT
831public.processor = SELECT835public.processor = SELECT
836public.snap = SELECT, UPDATE
837public.snaparch = SELECT
838public.snapbuild = SELECT, INSERT
832public.sourcepackagename = SELECT839public.sourcepackagename = SELECT
833public.sourcepackagerecipe = SELECT, UPDATE840public.sourcepackagerecipe = SELECT, UPDATE
834public.sourcepackagerecipebuild = SELECT, INSERT841public.sourcepackagerecipebuild = SELECT, INSERT
835842
=== modified file 'lib/lp/code/configure.zcml'
--- lib/lp/code/configure.zcml 2016-02-28 19:33:42 +0000
+++ lib/lp/code/configure.zcml 2016-06-29 16:28:24 +0000
@@ -423,6 +423,9 @@
423 handler="lp.codehosting.scanner.bzrsync.update_recipes"/>423 handler="lp.codehosting.scanner.bzrsync.update_recipes"/>
424 <subscriber424 <subscriber
425 for="lp.codehosting.scanner.events.ITipChanged"425 for="lp.codehosting.scanner.events.ITipChanged"
426 handler="lp.codehosting.scanner.bzrsync.update_snaps"/>
427 <subscriber
428 for="lp.codehosting.scanner.events.ITipChanged"
426 handler="lp.codehosting.scanner.bzrsync.trigger_webhooks"/>429 handler="lp.codehosting.scanner.bzrsync.trigger_webhooks"/>
427 <subscriber430 <subscriber
428 for="lp.codehosting.scanner.events.IRevisionsRemoved"431 for="lp.codehosting.scanner.events.IRevisionsRemoved"
429432
=== modified file 'lib/lp/code/interfaces/branch.py'
--- lib/lp/code/interfaces/branch.py 2015-10-12 12:58:32 +0000
+++ lib/lp/code/interfaces/branch.py 2016-06-29 16:28:24 +0000
@@ -1,4 +1,4 @@
1# Copyright 2009-2015 Canonical Ltd. This software is licensed under the1# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Branch interfaces."""4"""Branch interfaces."""
@@ -674,6 +674,9 @@
674 def markRecipesStale():674 def markRecipesStale():
675 """Mark all recipes associated with this branch as stale."""675 """Mark all recipes associated with this branch as stale."""
676676
677 def markSnapsStale():
678 """Mark all snap packages associated with this branch as stale."""
679
677 def getStackedBranches():680 def getStackedBranches():
678 """The branches that are stacked on this one."""681 """The branches that are stacked on this one."""
679682
680683
=== modified file 'lib/lp/code/interfaces/gitrepository.py'
--- lib/lp/code/interfaces/gitrepository.py 2016-05-14 09:54:32 +0000
+++ lib/lp/code/interfaces/gitrepository.py 2016-06-29 16:28:24 +0000
@@ -520,8 +520,16 @@
520 """Mark recipes associated with this repository as stale.520 """Mark recipes associated with this repository as stale.
521521
522 :param paths: A list of reference paths. Any recipes that include522 :param paths: A list of reference paths. Any recipes that include
523 an entry that points to this repository and that has a `revspec`523 an entry that points to this repository and that have a
524 that is one of these paths will be marked as stale.524 `revspec` that is one of these paths will be marked as stale.
525 """
526
527 def markSnapsStale(paths):
528 """Mark snap packages associated with this repository as stale.
529
530 :param paths: A list of reference paths. Any snap packages that
531 include an entry that points to this repository and that are
532 based on one of these paths will be marked as stale.
525 """533 """
526534
527 def detectMerges(paths, logger=None):535 def detectMerges(paths, logger=None):
528536
=== modified file 'lib/lp/code/model/branch.py'
--- lib/lp/code/model/branch.py 2016-06-01 17:24:35 +0000
+++ lib/lp/code/model/branch.py 2016-06-29 16:28:24 +0000
@@ -184,6 +184,7 @@
184from lp.services.webapp.interfaces import ILaunchBag184from lp.services.webapp.interfaces import ILaunchBag
185from lp.services.webhooks.interfaces import IWebhookSet185from lp.services.webhooks.interfaces import IWebhookSet
186from lp.services.webhooks.model import WebhookTargetMixin186from lp.services.webhooks.model import WebhookTargetMixin
187from lp.snappy.interfaces.snap import ISnapSet
187188
188189
189@implementer(IBranch, IPrivacy, IInformationType)190@implementer(IBranch, IPrivacy, IInformationType)
@@ -667,6 +668,11 @@
667 for recipe in self._recipes:668 for recipe in self._recipes:
668 recipe.is_stale = True669 recipe.is_stale = True
669670
671 def markSnapsStale(self):
672 """See `IBranch`."""
673 for snap in getUtility(ISnapSet).findByBranch(self):
674 snap.is_stale = True
675
670 def addToLaunchBag(self, launchbag):676 def addToLaunchBag(self, launchbag):
671 """See `IBranch`."""677 """See `IBranch`."""
672 launchbag.add(self.product)678 launchbag.add(self.product)
@@ -821,8 +827,6 @@
821 As well as the dictionaries, this method returns two list of callables827 As well as the dictionaries, this method returns two list of callables
822 that may be called to perform the alterations and deletions needed.828 that may be called to perform the alterations and deletions needed.
823 """829 """
824 from lp.snappy.interfaces.snap import ISnapSet
825
826 alteration_operations = []830 alteration_operations = []
827 deletion_operations = []831 deletion_operations = []
828 # Merge proposals require their source and target branches to exist.832 # Merge proposals require their source and target branches to exist.
829833
=== modified file 'lib/lp/code/model/gitrepository.py'
--- lib/lp/code/model/gitrepository.py 2016-05-14 09:54:32 +0000
+++ lib/lp/code/model/gitrepository.py 2016-06-29 16:28:24 +0000
@@ -152,6 +152,7 @@
152from lp.services.webapp.authorization import available_with_permission152from lp.services.webapp.authorization import available_with_permission
153from lp.services.webhooks.interfaces import IWebhookSet153from lp.services.webhooks.interfaces import IWebhookSet
154from lp.services.webhooks.model import WebhookTargetMixin154from lp.services.webhooks.model import WebhookTargetMixin
155from lp.snappy.interfaces.snap import ISnapSet
155156
156157
157object_type_map = {158object_type_map = {
@@ -1004,6 +1005,12 @@
1004 for recipe in self._getRecipes(paths):1005 for recipe in self._getRecipes(paths):
1005 recipe.is_stale = True1006 recipe.is_stale = True
10061007
1008 def markSnapsStale(self, paths):
1009 """See `IGitRepository`."""
1010 snap_set = getUtility(ISnapSet)
1011 for snap in snap_set.findByGitRepository(self, paths=paths):
1012 snap.is_stale = True
1013
1007 def _markProposalMerged(self, proposal, merged_revision_id, logger=None):1014 def _markProposalMerged(self, proposal, merged_revision_id, logger=None):
1008 if logger is not None:1015 if logger is not None:
1009 logger.info(1016 logger.info(
@@ -1058,8 +1065,6 @@
1058 As well as the dictionaries, this method returns two list of callables1065 As well as the dictionaries, this method returns two list of callables
1059 that may be called to perform the alterations and deletions needed.1066 that may be called to perform the alterations and deletions needed.
1060 """1067 """
1061 from lp.snappy.interfaces.snap import ISnapSet
1062
1063 alteration_operations = []1068 alteration_operations = []
1064 deletion_operations = []1069 deletion_operations = []
1065 # Merge proposals require their source and target repositories to1070 # Merge proposals require their source and target repositories to
10661071
=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
--- lib/lp/code/model/tests/test_gitrepository.py 2016-05-13 10:58:59 +0000
+++ lib/lp/code/model/tests/test_gitrepository.py 2016-06-29 16:28:24 +0000
@@ -1946,6 +1946,44 @@
1946 self.assertFalse(recipe.is_stale)1946 self.assertFalse(recipe.is_stale)
19471947
19481948
1949class TestGitRepositoryMarkSnapsStale(TestCaseWithFactory):
1950
1951 layer = ZopelessDatabaseLayer
1952
1953 def setUp(self):
1954 super(TestGitRepositoryMarkSnapsStale, self).setUp()
1955 self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
1956
1957 def test_same_repository(self):
1958 # On ref changes, snap packages using this ref become stale.
1959 [ref] = self.factory.makeGitRefs()
1960 snap = self.factory.makeSnap(git_ref=ref)
1961 removeSecurityProxy(snap).is_stale = False
1962 ref.repository.createOrUpdateRefs(
1963 {ref.path: {u"sha1": u"0" * 40, u"type": GitObjectType.COMMIT}})
1964 self.assertTrue(snap.is_stale)
1965
1966 def test_same_repository_different_ref(self):
1967 # On ref changes, snap packages using a different ref in the same
1968 # repository are left alone.
1969 ref1, ref2 = self.factory.makeGitRefs(
1970 paths=[u"refs/heads/a", u"refs/heads/b"])
1971 snap = self.factory.makeSnap(git_ref=ref1)
1972 removeSecurityProxy(snap).is_stale = False
1973 ref1.repository.createOrUpdateRefs(
1974 {ref2.path: {u"sha1": u"0" * 40, u"type": GitObjectType.COMMIT}})
1975 self.assertFalse(snap.is_stale)
1976
1977 def test_different_repository(self):
1978 # On ref changes, unrelated snap packages are left alone.
1979 [ref] = self.factory.makeGitRefs()
1980 snap = self.factory.makeSnap(git_ref=self.factory.makeGitRefs()[0])
1981 removeSecurityProxy(snap).is_stale = False
1982 ref.repository.createOrUpdateRefs(
1983 {ref.path: {u"sha1": u"0" * 40, u"type": GitObjectType.COMMIT}})
1984 self.assertFalse(snap.is_stale)
1985
1986
1949class TestGitRepositoryDetectMerges(TestCaseWithFactory):1987class TestGitRepositoryDetectMerges(TestCaseWithFactory):
19501988
1951 layer = LaunchpadZopelessLayer1989 layer = LaunchpadZopelessLayer
19521990
=== modified file 'lib/lp/code/scripts/tests/test_request_daily_builds.py'
--- lib/lp/code/scripts/tests/test_request_daily_builds.py 2012-06-29 14:36:44 +0000
+++ lib/lp/code/scripts/tests/test_request_daily_builds.py 2016-06-29 16:28:24 +0000
@@ -1,11 +1,13 @@
1# Copyright 2010-2012 Canonical Ltd. This software is licensed under the1# Copyright 2010-2016 Canonical Ltd. This software is licensed under the
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4"""Test the request_daily_builds script"""4"""Test the request_daily_builds script"""
55
6import transaction6import transaction
77
8from lp.services.features.testing import FeatureFixture
8from lp.services.scripts.tests import run_script9from lp.services.scripts.tests import run_script
10from lp.snappy.interfaces.snap import SNAP_TESTING_FLAGS
9from lp.soyuz.enums import ArchivePurpose11from lp.soyuz.enums import ArchivePurpose
10from lp.testing import TestCaseWithFactory12from lp.testing import TestCaseWithFactory
11from lp.testing.layers import ZopelessAppServerLayer13from lp.testing.layers import ZopelessAppServerLayer
@@ -15,24 +17,49 @@
1517
16 layer = ZopelessAppServerLayer18 layer = ZopelessAppServerLayer
1719
20 def setUp(self):
21 super(TestRequestDailyBuilds, self).setUp()
22 self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
23
18 def test_request_daily_builds(self):24 def test_request_daily_builds(self):
19 """Ensure the request_daily_builds script requests daily builds."""25 """Ensure the request_daily_builds script requests daily builds."""
26 processor = self.factory.makeProcessor(supports_virtualized=True)
27 distroarchseries = self.factory.makeDistroArchSeries(
28 processor=processor)
29 fake_chroot = self.factory.makeLibraryFileAlias(
30 filename="fake_chroot.tar.gz", db_only=True)
31 distroarchseries.addOrUpdateChroot(fake_chroot)
20 prod_branch = self.factory.makeProductBranch()32 prod_branch = self.factory.makeProductBranch()
21 prod_recipe = self.factory.makeSourcePackageRecipe(33 prod_recipe = self.factory.makeSourcePackageRecipe(
22 build_daily=True, is_stale=True, branches=[prod_branch])34 build_daily=True, is_stale=True, branches=[prod_branch])
35 prod_snap = self.factory.makeSnap(
36 distroseries=distroarchseries.distroseries,
37 processors=[distroarchseries.processor],
38 auto_build=True, is_stale=True, branch=prod_branch)
23 pack_branch = self.factory.makePackageBranch()39 pack_branch = self.factory.makePackageBranch()
24 pack_recipe = self.factory.makeSourcePackageRecipe(40 pack_recipe = self.factory.makeSourcePackageRecipe(
25 build_daily=True, is_stale=True, branches=[pack_branch])41 build_daily=True, is_stale=True, branches=[pack_branch])
42 pack_snap = self.factory.makeSnap(
43 distroseries=distroarchseries.distroseries,
44 processors=[distroarchseries.processor],
45 auto_build=True, is_stale=True, branch=pack_branch)
26 self.assertEqual(0, prod_recipe.pending_builds.count())46 self.assertEqual(0, prod_recipe.pending_builds.count())
47 self.assertEqual(0, prod_snap.pending_builds.count())
27 self.assertEqual(0, pack_recipe.pending_builds.count())48 self.assertEqual(0, pack_recipe.pending_builds.count())
49 self.assertEqual(0, pack_snap.pending_builds.count())
28 transaction.commit()50 transaction.commit()
29 retcode, stdout, stderr = run_script(51 retcode, stdout, stderr = run_script(
30 'cronscripts/request_daily_builds.py', [])52 'cronscripts/request_daily_builds.py', [])
31 self.assertIn('Requested 2 daily builds.', stderr)53 self.assertIn('Requested 2 daily recipe builds.', stderr)
54 self.assertIn('Requested 2 automatic snap package builds.', stderr)
32 self.assertEqual(1, prod_recipe.pending_builds.count())55 self.assertEqual(1, prod_recipe.pending_builds.count())
56 self.assertEqual(1, prod_snap.pending_builds.count())
33 self.assertEqual(1, pack_recipe.pending_builds.count())57 self.assertEqual(1, pack_recipe.pending_builds.count())
58 self.assertEqual(1, pack_snap.pending_builds.count())
34 self.assertFalse(prod_recipe.is_stale)59 self.assertFalse(prod_recipe.is_stale)
60 self.assertFalse(prod_snap.is_stale)
35 self.assertFalse(pack_recipe.is_stale)61 self.assertFalse(pack_recipe.is_stale)
62 self.assertFalse(pack_snap.is_stale)
3663
37 def test_request_daily_builds_oops(self):64 def test_request_daily_builds_oops(self):
38 """Ensure errors are handled cleanly."""65 """Ensure errors are handled cleanly."""
@@ -43,7 +70,8 @@
43 retcode, stdout, stderr = run_script(70 retcode, stdout, stderr = run_script(
44 'cronscripts/request_daily_builds.py', [])71 'cronscripts/request_daily_builds.py', [])
45 self.assertEqual(0, recipe.pending_builds.count())72 self.assertEqual(0, recipe.pending_builds.count())
46 self.assertIn('Requested 0 daily builds.', stderr)73 self.assertIn('Requested 0 daily recipe builds.', stderr)
74 self.assertIn('Requested 0 automatic snap package builds.', stderr)
47 self.oops_capture.sync()75 self.oops_capture.sync()
48 self.assertEqual('NonPPABuildRequest', self.oopses[0]['type'])76 self.assertEqual('NonPPABuildRequest', self.oopses[0]['type'])
49 self.assertEqual(77 self.assertEqual(
5078
=== modified file 'lib/lp/code/subscribers/git.py'
--- lib/lp/code/subscribers/git.py 2016-01-12 04:34:09 +0000
+++ lib/lp/code/subscribers/git.py 2016-06-29 16:28:24 +0000
@@ -11,4 +11,5 @@
11 repository.updateMergeCommitIDs(event.paths)11 repository.updateMergeCommitIDs(event.paths)
12 repository.scheduleDiffUpdates(event.paths)12 repository.scheduleDiffUpdates(event.paths)
13 repository.markRecipesStale(event.paths)13 repository.markRecipesStale(event.paths)
14 repository.markSnapsStale(event.paths)
14 repository.detectMerges(event.paths, logger=event.logger)15 repository.detectMerges(event.paths, logger=event.logger)
1516
=== modified file 'lib/lp/codehosting/scanner/bzrsync.py'
--- lib/lp/codehosting/scanner/bzrsync.py 2016-05-24 05:29:48 +0000
+++ lib/lp/codehosting/scanner/bzrsync.py 2016-06-29 16:28:24 +0000
@@ -1,6 +1,6 @@
1#!/usr/bin/python1#!/usr/bin/python
2#2#
3# Copyright 2009-2015 Canonical Ltd. This software is licensed under the3# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
4# GNU Affero General Public License version 3 (see the file LICENSE).4# GNU Affero General Public License version 3 (see the file LICENSE).
55
6"""Import version control metadata from a Bazaar branch into the database."""6"""Import version control metadata from a Bazaar branch into the database."""
@@ -333,6 +333,10 @@
333 tip_changed.db_branch.markRecipesStale()333 tip_changed.db_branch.markRecipesStale()
334334
335335
336def update_snaps(tip_changed):
337 tip_changed.db_branch.markSnapsStale()
338
339
336def trigger_webhooks(tip_changed):340def trigger_webhooks(tip_changed):
337 old_revid = tip_changed.old_tip_revision_id341 old_revid = tip_changed.old_tip_revision_id
338 new_revid = tip_changed.new_tip_revision_id342 new_revid = tip_changed.new_tip_revision_id
339343
=== modified file 'lib/lp/codehosting/scanner/tests/test_bzrsync.py'
--- lib/lp/codehosting/scanner/tests/test_bzrsync.py 2016-03-02 21:21:26 +0000
+++ lib/lp/codehosting/scanner/tests/test_bzrsync.py 2016-06-29 16:28:24 +0000
@@ -51,6 +51,7 @@
51from lp.services.database.interfaces import IStore51from lp.services.database.interfaces import IStore
52from lp.services.features.testing import FeatureFixture52from lp.services.features.testing import FeatureFixture
53from lp.services.osutils import override_environ53from lp.services.osutils import override_environ
54from lp.snappy.interfaces.snap import SNAP_FEATURE_FLAG
54from lp.testing import TestCaseWithFactory55from lp.testing import TestCaseWithFactory
55from lp.testing.dbuser import (56from lp.testing.dbuser import (
56 dbuser,57 dbuser,
@@ -749,6 +750,32 @@
749 self.assertEqual(False, recipe.is_stale)750 self.assertEqual(False, recipe.is_stale)
750751
751752
753class TestMarkSnapsStale(BzrSyncTestCase):
754 """Test that snap packages associated with the branch are marked stale."""
755
756 def setUp(self):
757 super(TestMarkSnapsStale, self).setUp()
758 self.useFixture(FeatureFixture({SNAP_FEATURE_FLAG: u"on"}))
759
760 @run_as_db_user(config.launchpad.dbuser)
761 def test_same_branch(self):
762 # On tip change, snap packages using this branch become stale.
763 snap = self.factory.makeSnap(branch=self.db_branch)
764 removeSecurityProxy(snap).is_stale = False
765 switch_dbuser("branchscanner")
766 self.makeBzrSync(self.db_branch).syncBranchAndClose()
767 self.assertTrue(snap.is_stale)
768
769 @run_as_db_user(config.launchpad.dbuser)
770 def test_unrelated_branch(self):
771 # On tip change, unrelated snap packages are left alone.
772 snap = self.factory.makeSnap()
773 removeSecurityProxy(snap).is_stale = False
774 switch_dbuser("branchscanner")
775 self.makeBzrSync(self.db_branch).syncBranchAndClose()
776 self.assertFalse(snap.is_stale)
777
778
752class TestTriggerWebhooks(BzrSyncTestCase):779class TestTriggerWebhooks(BzrSyncTestCase):
753 """Test triggering of webhooks."""780 """Test triggering of webhooks."""
754781
755782
=== modified file 'lib/lp/snappy/interfaces/snap.py'
--- lib/lp/snappy/interfaces/snap.py 2016-06-29 16:28:24 +0000
+++ lib/lp/snappy/interfaces/snap.py 2016-06-29 16:28:24 +0000
@@ -527,8 +527,13 @@
527 def findByBranch(branch):527 def findByBranch(branch):
528 """Return all snap packages for the given Bazaar branch."""528 """Return all snap packages for the given Bazaar branch."""
529529
530 def findByGitRepository(repository):530 def findByGitRepository(repository, paths=None):
531 """Return all snap packages for the given Git repository."""531 """Return all snap packages for the given Git repository.
532
533 :param repository: An `IGitRepository`.
534 :param paths: If not None, only return snap packages for one of
535 these Git reference paths.
536 """
532537
533 def findByGitRef(ref):538 def findByGitRef(ref):
534 """Return all snap packages for the given Git reference."""539 """Return all snap packages for the given Git reference."""
535540
=== modified file 'lib/lp/snappy/model/snap.py'
--- lib/lp/snappy/model/snap.py 2016-06-29 16:28:24 +0000
+++ lib/lp/snappy/model/snap.py 2016-06-29 16:28:24 +0000
@@ -628,9 +628,12 @@
628 """See `ISnapSet`."""628 """See `ISnapSet`."""
629 return IStore(Snap).find(Snap, Snap.branch == branch)629 return IStore(Snap).find(Snap, Snap.branch == branch)
630630
631 def findByGitRepository(self, repository):631 def findByGitRepository(self, repository, paths=None):
632 """See `ISnapSet`."""632 """See `ISnapSet`."""
633 return IStore(Snap).find(Snap, Snap.git_repository == repository)633 clauses = [Snap.git_repository == repository]
634 if paths is not None:
635 clauses.append(Snap.git_path.is_in(paths))
636 return IStore(Snap).find(Snap, *clauses)
634637
635 def findByGitRef(self, ref):638 def findByGitRef(self, ref):
636 """See `ISnapSet`."""639 """See `ISnapSet`."""
637640
=== modified file 'lib/lp/snappy/tests/test_snap.py'
--- lib/lp/snappy/tests/test_snap.py 2016-06-29 16:28:24 +0000
+++ lib/lp/snappy/tests/test_snap.py 2016-06-29 16:28:24 +0000
@@ -721,6 +721,27 @@
721 self.assertContentEqual(721 self.assertContentEqual(
722 snaps[2:], snap_set.findByGitRepository(repositories[1]))722 snaps[2:], snap_set.findByGitRepository(repositories[1]))
723723
724 def test_findByGitRepository_paths(self):
725 # ISnapSet.findByGitRepository can restrict by reference paths.
726 repositories = [self.factory.makeGitRepository() for i in range(2)]
727 snaps = []
728 for repository in repositories:
729 for i in range(3):
730 [ref] = self.factory.makeGitRefs(repository=repository)
731 snaps.append(self.factory.makeSnap(git_ref=ref))
732 snap_set = getUtility(ISnapSet)
733 self.assertContentEqual(
734 [], snap_set.findByGitRepository(repositories[0], paths=[]))
735 self.assertContentEqual(
736 [snaps[0]],
737 snap_set.findByGitRepository(
738 repositories[0], paths=[snaps[0].git_ref.path]))
739 self.assertContentEqual(
740 snaps[:2],
741 snap_set.findByGitRepository(
742 repositories[0],
743 paths=[snaps[0].git_ref.path, snaps[1].git_ref.path]))
744
724 def test_findByGitRef(self):745 def test_findByGitRef(self):
725 # ISnapSet.findByGitRef returns all Snaps with the given Git746 # ISnapSet.findByGitRef returns all Snaps with the given Git
726 # reference.747 # reference.