Merge lp:~robru/cupstream2distro/detect-new-commits-when-building into lp:cupstream2distro

Proposed by Robert Bruce Park
Status: Merged
Merged at revision: 1160
Proposed branch: lp:~robru/cupstream2distro/detect-new-commits-when-building
Merge into: lp:cupstream2distro
Diff against target: 330 lines (+85/-106)
6 files modified
citrain/build.py (+12/-32)
citrain/recipes/manager.py (+1/-1)
citrain/recipes/merge.py (+19/-15)
tests/unit/__init__.py (+1/-0)
tests/unit/test_recipe_merge.py (+2/-0)
tests/unit/test_script_build.py (+50/-58)
To merge this branch: bzr merge lp:~robru/cupstream2distro/detect-new-commits-when-building
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration Needs Fixing
Robert Bruce Park (community) Approve
Review via email: mp+274960@code.launchpad.net

Commit message

Detect which packages have new commits to build.

To post a comment you must log in.
Revision history for this message
Robert Bruce Park (robru) wrote :

TODO:

* write tests
* test in staging
* should probably also scan trunk revids
* should also include packages marked dirty.

review: Needs Fixing
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1168
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/828/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/828/rebuild

review: Approve (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1170
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/829/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/829/rebuild

review: Approve (continuous-integration)
Revision history for this message
Robert Bruce Park (robru) wrote :

Ok I think this is making sense, will just test in staging tomorrow before merging.

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:1172
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/830/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/830/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Continuous integration, rev:1173
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/831/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/831/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:1174
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-ci/832/
Executed test runs:

Click here to trigger a rebuild:
http://s-jenkins.ubuntu-ci:8080/job/cu2d-choo-choo-ci/832/rebuild

review: Approve (continuous-integration)
Revision history for this message
Robert Bruce Park (robru) wrote :

Staging instance has apparently imploded so I'm not able to test this, but I'm confident with the tests and will monitor production closely for regressions.

review: Approve
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

FAILED: Autolanding.
More details in the following jenkins job:
http://jenkins.qa.ubuntu.com/job/cu2d-choo-choo-autolanding/310/
Executed test runs:

review: Needs Fixing (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'citrain/build.py'
--- citrain/build.py 2015-10-17 09:26:56 +0000
+++ citrain/build.py 2015-10-20 09:18:51 +0000
@@ -47,49 +47,30 @@
4747
48sys.path.append(os.path.dirname(os.path.dirname(__file__)))48sys.path.append(os.path.dirname(os.path.dirname(__file__)))
4949
50from glob import glob
51from os.path import basename
52
53from citrain.recipes.base import missing50from citrain.recipes.base import missing
51from citrain.recipes.merge import check_for_unbuilt_revids
54from citrain.recipes.manager import Manager52from citrain.recipes.manager import Manager
55from cupstream2distro.silomanager import SiloState53from cupstream2distro.silomanager import SiloState, get_dirty, splitter
56from cupstream2distro.errors import BuildError, CITrainError54from cupstream2distro.errors import BuildError, CITrainError
57from cupstream2distro.utils import SILO_DIR, env, run_script55from cupstream2distro.utils import env, run_script
58
59
60def find_all_uploaded():
61 """Return all source package names and versions uploaded to the ppa.
62
63 :yields: Source package names that have been uploaded.
64 """
65 for uploaded in glob(SILO_DIR('*_source.ppa.upload')):
66 yield basename(uploaded).split('_')[0]
6756
6857
69class BuildManager(Manager):58class BuildManager(Manager):
70 """Honor PACKAGES_TO_REBUILD during building."""59 """Honor PACKAGES_TO_REBUILD during building."""
7160
72 def include_all(self):
73 """Ignore PACKAGES_TO_REBUILD and include all."""
74 self.choose_packages = super().choose_packages
75 self.types.clear()
76
77 def choose_packages(self):61 def choose_packages(self):
78 """Decide which packages need to be built."""62 """Decide which packages need to be built."""
79 self.names = set(env.PACKAGES_TO_REBUILD.split()63 all_projects = set(self.silo_state.all_projects)
80 or self.silo_state.all_projects)64 self.names = set(splitter(env.PACKAGES_TO_REBUILD) or all_projects)
81 self.names.update(missing(self.names))65 self.names.update(missing(self.names))
82 include_all = 'true' in (env.FORCE_REBUILD, env.WATCH_ONLY)66 include_all = 'true' in (env.FORCE_REBUILD, env.WATCH_ONLY)
83 if not (include_all or env.PACKAGES_TO_REBUILD):67 if not (include_all or env.PACKAGES_TO_REBUILD):
84 # Don't rebuild existing builds without forcing.68 for source, merges in self.silo_state.mps.items():
85 previous = self.names & set(find_all_uploaded())69 if check_for_unbuilt_revids(source, merges) == []:
86 if previous:70 logging.info(source + ' has no new commits, skipping.')
87 logging.info('{} already built, skipping.'.format(71 self.names.discard(source)
88 ', '.join(previous)))72 self.names.update(set(get_dirty(env.SILONAME)) & all_projects)
89 self.names -= previous73 if not self.names:
90 if self.names:
91 logging.info('Including {}.'.format(', '.join(self.names)))
92 else:
93 raise BuildError(74 raise BuildError(
94 'No packages are being considered. You probably want one of '75 'No packages are being considered. You probably want one of '
95 'PACKAGES_TO_REBUILD, FORCE_REBUILD, or WATCH_ONLY.')76 'PACKAGES_TO_REBUILD, FORCE_REBUILD, or WATCH_ONLY.')
@@ -106,8 +87,7 @@
106 manager.do('validate')87 manager.do('validate')
107 if env.WATCH_ONLY != 'true':88 if env.WATCH_ONLY != 'true':
108 manager.do('clean', 'collect', 'build', 'upload')89 manager.do('clean', 'collect', 'build', 'upload')
109 manager.include_all()90 Manager(silo_state).do('watch', 'diff')
110 manager.do('watch', 'diff')
111 silo_state.status = 'Packages built.'91 silo_state.status = 'Packages built.'
112 except CITrainError as err:92 except CITrainError as err:
113 logging.error(str(err))93 logging.error(str(err))
11494
=== modified file 'citrain/recipes/manager.py'
--- citrain/recipes/manager.py 2015-10-07 19:47:09 +0000
+++ citrain/recipes/manager.py 2015-10-20 09:18:51 +0000
@@ -54,6 +54,7 @@
54 self.choose_packages()54 self.choose_packages()
55 self.choose_classes()55 self.choose_classes()
56 self.instantiate_build_objects()56 self.instantiate_build_objects()
57 logging.info('Including {}.'.format(', '.join(sorted(self.names))))
57 for phase in phases:58 for phase in phases:
58 self.execute_phase(phase)59 self.execute_phase(phase)
5960
@@ -64,7 +65,6 @@
64 def choose_packages(self):65 def choose_packages(self):
65 """Act on all silo packages."""66 """Act on all silo packages."""
66 self.names = set(self.silo_state.all_projects)67 self.names = set(self.silo_state.all_projects)
67 logging.info('Including {}.'.format(', '.join(self.names)))
6868
69 def choose_classes(self):69 def choose_classes(self):
70 """Decide which class to use for each individual package build."""70 """Decide which class to use for each individual package build."""
7171
=== modified file 'citrain/recipes/merge.py'
--- citrain/recipes/merge.py 2015-10-19 22:13:22 +0000
+++ citrain/recipes/merge.py 2015-10-20 09:18:51 +0000
@@ -77,6 +77,21 @@
77 raise PrepError('Not all MPs targeting same branch.')77 raise PrepError('Not all MPs targeting same branch.')
7878
7979
80def check_for_unbuilt_revids(source, merges):
81 """Return a list of MPs that have new commits since last build."""
82 unbuilt = []
83 logging.info('Checking {} for new commits...'.format(source))
84 # TODO: check also the merge target revids, which only just started being
85 # recorded and need time to appear in production.
86 for merge in merges:
87 url = merge.web_link
88 revid = Rev(source, url).read()
89 if merge.source_branch.last_scanned_id != revid:
90 unbuilt.append(url)
91 logging.info('New commits: ' + url)
92 return unbuilt
93
94
80class Merge(BuildBase):95class Merge(BuildBase):
81 """Build a source package from a series of merge proposals.96 """Build a source package from a series of merge proposals.
8297
@@ -86,18 +101,6 @@
86 all_mps = {}101 all_mps = {}
87 merges = None102 merges = None
88103
89 def check_for_unbuilt_revids(self):
90 """Return a list of MPs that have new commits since last build."""
91 unbuilt = []
92 logging.info('Checking {} for new commits...'.format(self.name))
93 for merge in self.merges:
94 url = merge.web_link
95 revid = Rev(self.name, url).read()
96 if merge.source_branch.last_scanned_id != revid:
97 unbuilt.append(url)
98 logging.error('New commits: ' + url)
99 return unbuilt
100
101 def enforce_merge_states(self, acceptable):104 def enforce_merge_states(self, acceptable):
102 """Block when merges have bad states."""105 """Block when merges have bad states."""
103 # (\/) (°,,,°) (\/)106 # (\/) (°,,,°) (\/)
@@ -128,8 +131,9 @@
128 """131 """
129 logging.info('Preparing merges for {}.'.format(self.name))132 logging.info('Preparing merges for {}.'.format(self.name))
130 merges = self.merges = reorder_branches(self.merges)133 merges = self.merges = reorder_branches(self.merges)
131 target = merges[0].target_branch.web_link134 target = merges[0].target_branch
132 self.get_branch(target)135 Rev(self.name, target.web_link).write(target.last_scanned_id)
136 self.get_branch(target.web_link)
133 trunk_version = self.get_package_version()137 trunk_version = self.get_package_version()
134 DotProject.soft_save(self.name, dest=trunk_version)138 DotProject.soft_save(self.name, dest=trunk_version)
135 for merge in merges:139 for merge in merges:
@@ -238,7 +242,7 @@
238242
239 def unbuilt_phase(self):243 def unbuilt_phase(self):
240 """Block publication of merges that have new commits."""244 """Block publication of merges that have new commits."""
241 if self.check_for_unbuilt_revids():245 if check_for_unbuilt_revids(self.name, self.merges):
242 self.failures.add(self.name + ' has new, unbuilt commits.')246 self.failures.add(self.name + ' has new, unbuilt commits.')
243247
244 def merge_phase(self):248 def merge_phase(self):
245249
=== modified file 'tests/unit/__init__.py'
--- tests/unit/__init__.py 2015-09-22 06:53:49 +0000
+++ tests/unit/__init__.py 2015-10-20 09:18:51 +0000
@@ -144,6 +144,7 @@
144 self.script.Archive = Mock()144 self.script.Archive = Mock()
145 self.script.Branch = Mock()145 self.script.Branch = Mock()
146 self.script.DotProject = Mock()146 self.script.DotProject = Mock()
147 self.script.Manager = Mock()
147 self.script.Package = Mock()148 self.script.Package = Mock()
148 self.script.Rev = Mock()149 self.script.Rev = Mock()
149 self.script.SiloState = Mock()150 self.script.SiloState = Mock()
150151
=== modified file 'tests/unit/test_recipe_merge.py'
--- tests/unit/test_recipe_merge.py 2015-10-19 22:13:22 +0000
+++ tests/unit/test_recipe_merge.py 2015-10-20 09:18:51 +0000
@@ -242,6 +242,8 @@
242 ])242 ])
243 authors = merge.get_remote_branch_authors()243 authors = merge.get_remote_branch_authors()
244 self.assertEqual(rev_mock.mock_calls, [244 self.assertEqual(rev_mock.mock_calls, [
245 call('a', merge1.target_branch.web_link),
246 call().write(merge1.target_branch.last_scanned_id),
245 call('a', merge1.web_link),247 call('a', merge1.web_link),
246 call().write(merge1.source_branch.last_scanned_id),248 call().write(merge1.source_branch.last_scanned_id),
247 call('a', merge2.web_link),249 call('a', merge2.web_link),
248250
=== modified file 'tests/unit/test_script_build.py'
--- tests/unit/test_script_build.py 2015-10-17 09:26:56 +0000
+++ tests/unit/test_script_build.py 2015-10-20 09:18:51 +0000
@@ -56,52 +56,40 @@
56 self.series = Mock()56 self.series = Mock()
57 self.ppa = Mock()57 self.ppa = Mock()
5858
59 def test_find_all_uploaded(self):
60 """Find everything that has been uploaded from this silo."""
61 self.script.glob.return_value = [
62 '/path/to/foo_0.1_source.ppa.upload',
63 '/path/to/bar_0.2_source.ppa.upload',
64 ]
65 self.assertEqual(
66 sorted(self.script.find_all_uploaded()), ['bar', 'foo'])
67
68 def test_find_all_uploaded_multiple_series(self):
69 """We can handle uploading multiple series from a single silo."""
70 self.script.glob.return_value = [
71 '/path/to/foo_0.1_source.ppa.upload',
72 '/path/to/foo_0.1~utopic_source.ppa.upload',
73 '/path/to/bar_0.2_source.ppa.upload',
74 '/path/to/bar_0.2~utopic_source.ppa.upload',
75 ]
76 self.assertEqual(
77 sorted(self.script.find_all_uploaded()),
78 ['bar', 'bar', 'foo', 'foo'])
79 self.assertEqual(
80 set(self.script.find_all_uploaded()), set(['foo', 'bar']))
81
82 def test_buildmanager_include_all(self):
83 """Correctly switch to watching all packages."""
84 bman = self.script.BuildManager(Mock())
85 bman.types['foo'] = 'bar'
86 self.assertEqual(
87 bman.choose_packages.__doc__,
88 'Decide which packages need to be built.')
89 bman.include_all()
90 self.assertEqual(
91 bman.choose_packages.__doc__,
92 'Act on all silo packages.')
93 self.assertFalse(bman.types)
94
95 def test_buildmanager_choose_packages(self):59 def test_buildmanager_choose_packages(self):
96 """Determine what packages to act upon."""60 """Determine what packages to act upon."""
97 silo_state = Mock(61 silo_state = Mock(
98 all_projects=['a', 'b', 'c'], mps=['a'], sources=['b', 'c'])62 all_projects=list('abcd'),
99 self.script.env.PACKAGES_TO_REBUILD = ''63 mps=dict(
100 self.script.env.FORCE_REBUILD = 'false'64 c=[Mock(
101 self.script.find_all_uploaded = Mock(return_value=['c'])65 source_branch=Mock(last_scanned_id=None),
102 bman = self.script.BuildManager(silo_state)66 web_link='foo/123')],
103 bman.choose_packages()67 d=[Mock(
104 self.assertEqual(bman.names, set(['a', 'b']))68 source_branch=Mock(last_scanned_id='stale'),
69 web_link='foo/789')]),
70 sources=list('abcd'))
71 self.script.env.PACKAGES_TO_REBUILD = ''
72 self.script.env.FORCE_REBUILD = 'false'
73 bman = self.script.BuildManager(silo_state)
74 bman.choose_packages()
75 self.assertEqual(bman.names, {'a', 'b', 'd'})
76
77 def test_buildmanager_choose_packages_dirty(self):
78 """Include dirty packages by default."""
79 self.script.get_dirty = Mock(return_value=list('abcde'))
80 silo_state = Mock(
81 all_projects=list('abcd'),
82 mps=dict(
83 c=[Mock(
84 source_branch=Mock(last_scanned_id=None),
85 web_link='foo/123')]),
86 sources=list('abcd'))
87 self.script.env.PACKAGES_TO_REBUILD = ''
88 self.script.env.FORCE_REBUILD = 'false'
89 bman = self.script.BuildManager(silo_state)
90 bman.choose_packages()
91 self.script.get_dirty.assert_called_once_with(self.tempdir)
92 self.assertEqual(bman.names, set('abcd'))
10593
106 def test_buildmanager_choose_packages_twins(self):94 def test_buildmanager_choose_packages_twins(self):
107 """Always add twins for all builds."""95 """Always add twins for all builds."""
@@ -116,10 +104,14 @@
116 def test_buildmanager_choose_packages_none_found(self):104 def test_buildmanager_choose_packages_none_found(self):
117 """Fail when no packages to act upon."""105 """Fail when no packages to act upon."""
118 silo_state = Mock(106 silo_state = Mock(
119 all_projects=['a', 'b', 'c'], mps=['a'], sources=['b', 'c'])107 all_projects=['a'],
108 mps=dict(a=[Mock(
109 source_branch=Mock(last_scanned_id=None),
110 web_link='foo/123')]),
111 sources=[])
120 self.script.env.PACKAGES_TO_REBUILD = ''112 self.script.env.PACKAGES_TO_REBUILD = ''
121 self.script.env.FORCE_REBUILD = 'false'113 self.script.env.FORCE_REBUILD = 'false'
122 self.script.find_all_uploaded = Mock(return_value=list('abc'))114 self.script.env.WATCH_ONLY = 'false'
123 bman = self.script.BuildManager(silo_state)115 bman = self.script.BuildManager(silo_state)
124 with self.assertRaisesRegexp(BuildError, 'No packages are being'):116 with self.assertRaisesRegexp(BuildError, 'No packages are being'):
125 bman.choose_packages()117 bman.choose_packages()
@@ -132,13 +124,13 @@
132 bman = self.script.BuildManager = Mock()124 bman = self.script.BuildManager = Mock()
133 self.assertEqual(self.script.main(bman), 0)125 self.assertEqual(self.script.main(bman), 0)
134 bman.assert_called_once_with(silo_state)126 bman.assert_called_once_with(silo_state)
135 self.assertEqual(127 self.assertEqual(bman.return_value.mock_calls, [
136 bman.return_value.mock_calls, [128 call.do('validate'),
137 call.do('validate'),129 call.do('clean', 'collect', 'build', 'upload'),
138 call.do('clean', 'collect', 'build', 'upload'),130 ])
139 call.include_all(),131 self.assertEqual(self.script.Manager.return_value.mock_calls, [
140 call.do('watch', 'diff'),132 call.do('watch', 'diff'),
141 ])133 ])
142 self.assertEqual(silo_state.status, 'Packages built.')134 self.assertEqual(silo_state.status, 'Packages built.')
143 silo_state.mark_dirty.assert_called_once_with()135 silo_state.mark_dirty.assert_called_once_with()
144 silo_state.save_config.assert_called_once_with()136 silo_state.save_config.assert_called_once_with()
@@ -150,12 +142,12 @@
150 bman = self.script.BuildManager = Mock()142 bman = self.script.BuildManager = Mock()
151 self.assertEqual(self.script.main(bman), 0)143 self.assertEqual(self.script.main(bman), 0)
152 bman.assert_called_once_with(silo_state)144 bman.assert_called_once_with(silo_state)
153 self.assertEqual(145 self.assertEqual(bman.return_value.mock_calls, [
154 bman.return_value.mock_calls, [146 call.do('validate'),
155 call.do('validate'),147 ])
156 call.include_all(),148 self.assertEqual(self.script.Manager.return_value.mock_calls, [
157 call.do('watch', 'diff'),149 call.do('watch', 'diff'),
158 ])150 ])
159 silo_state.save_config.assert_called_once_with()151 silo_state.save_config.assert_called_once_with()
160152
161 def test_main_error(self):153 def test_main_error(self):

Subscribers

People subscribed via source and target branches