Merge lp:~cjwatson/launchpad/branch-unscan-affordances into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18497
Proposed branch: lp:~cjwatson/launchpad/branch-unscan-affordances
Merge into: lp:launchpad
Diff against target: 509 lines (+161/-60)
15 files modified
lib/lp/code/browser/branch.py (+3/-3)
lib/lp/code/browser/branchmergeproposal.py (+3/-3)
lib/lp/code/browser/tests/test_branch.py (+1/-1)
lib/lp/code/browser/tests/test_branchmergeproposal.py (+20/-0)
lib/lp/code/interfaces/branch.py (+6/-0)
lib/lp/code/interfaces/gitref.py (+1/-1)
lib/lp/code/interfaces/gitrepository.py (+1/-1)
lib/lp/code/model/branch.py (+37/-9)
lib/lp/code/model/gitref.py (+3/-3)
lib/lp/code/model/gitrepository.py (+1/-1)
lib/lp/code/model/tests/test_branch.py (+73/-26)
lib/lp/code/model/tests/test_gitrepository.py (+7/-7)
lib/lp/code/templates/branch-index.pt (+2/-2)
lib/lp/code/templates/gitrepository-index.pt (+2/-2)
lib/lp/codehosting/tests/test_branchdistro.py (+1/-1)
To merge this branch: bzr merge lp:~cjwatson/launchpad/branch-unscan-affordances
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+333251@code.launchpad.net

Commit message

Improve handling of branches with various kinds of partial data.

Description of the change

This tidies up OOPSes on e.g. dogfood, and allows us to unscan branches without having the branches and any associated merge proposals displaying permanent "Updating" UI indications.

I could probably have made this shorter by renaming the other half of the pending_updates/pending_writes split, but I've always found the naming a bit confusing and thought it was clearer this way round (hence also why I renamed the Git analogue to match). I tried making pending_writes be a superset of pending_updates, but the test failures were a bit catastrophic, and users of this really do care about whether the actual scanned data is in sync rather than whether there happens to be a branch scan job in progress.

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
1=== modified file 'lib/lp/code/browser/branch.py'
2--- lib/lp/code/browser/branch.py 2016-11-11 12:51:58 +0000
3+++ lib/lp/code/browser/branch.py 2017-11-08 11:16:30 +0000
4@@ -451,9 +451,9 @@
5 return self.context.control_format is None
6
7 @property
8- def pending_writes(self):
9- """Whether or not there are pending writes for this branch."""
10- return self.context.pending_writes
11+ def pending_updates(self):
12+ """Whether or not there are pending updates for this branch."""
13+ return self.context.pending_updates
14
15 def bzr_download_url(self):
16 """Return the generic URL for downloading the branch."""
17
18=== modified file 'lib/lp/code/browser/branchmergeproposal.py'
19--- lib/lp/code/browser/branchmergeproposal.py 2017-05-24 12:04:18 +0000
20+++ lib/lp/code/browser/branchmergeproposal.py 2017-11-08 11:16:30 +0000
21@@ -370,7 +370,7 @@
22 return []
23
24 @property
25- def pending_writes(self):
26+ def pending_updates(self):
27 """Needed to make the branch-revisions metal macro work."""
28 return False
29
30@@ -532,7 +532,7 @@
31 diff = preview_diff.text.decode('utf-8')
32 except UnicodeDecodeError:
33 diff = preview_diff.text.decode('windows-1252', 'replace')
34- except LibrarianServerError:
35+ except (LookupError, LibrarianServerError):
36 self._diff_available = False
37 diff = ''
38 # Strip off the trailing carriage returns.
39@@ -736,7 +736,7 @@
40 def pending_diff(self):
41 return (
42 self.context.next_preview_diff_job is not None or
43- self.context.merge_source.pending_writes)
44+ self.context.merge_source.pending_updates)
45
46 @cachedproperty
47 def preview_diff(self):
48
49=== modified file 'lib/lp/code/browser/tests/test_branch.py'
50--- lib/lp/code/browser/tests/test_branch.py 2017-10-04 01:16:22 +0000
51+++ lib/lp/code/browser/tests/test_branch.py 2017-11-08 11:16:30 +0000
52@@ -607,7 +607,7 @@
53 logout()
54 with StormStatementRecorder() as recorder:
55 browser.open(branch_url)
56- self.assertThat(recorder, HasQueryCount(Equals(27)))
57+ self.assertThat(recorder, HasQueryCount(Equals(28)))
58
59
60 class TestBranchViewPrivateArtifacts(BrowserTestCase):
61
62=== modified file 'lib/lp/code/browser/tests/test_branchmergeproposal.py'
63--- lib/lp/code/browser/tests/test_branchmergeproposal.py 2017-10-04 01:16:22 +0000
64+++ lib/lp/code/browser/tests/test_branchmergeproposal.py 2017-11-08 11:16:30 +0000
65@@ -1395,6 +1395,26 @@
66 markup = view()
67 self.assertIn('The diff is not available at this time.', markup)
68
69+ def test_preview_diff_lookup_error(self):
70+ # The preview_diff will recover from a LookupError while getting the
71+ # librarian content. (This can happen e.g. on staging replicas of
72+ # the production database.)
73+ text = b''.join(chr(x) for x in range(255))
74+ diff_bytes = b''.join(unified_diff(b'', text))
75+ preview_diff = self.setPreviewDiff(diff_bytes)
76+ transaction.commit()
77+
78+ def fake_open(*args):
79+ raise LookupError
80+
81+ lfa = preview_diff.diff.diff_text
82+ with monkey_patch(lfa, open=fake_open):
83+ view = create_initialized_view(preview_diff, '+diff')
84+ self.assertEqual('', view.preview_diff_text)
85+ self.assertFalse(view.diff_available)
86+ markup = view()
87+ self.assertIn('The diff is not available at this time.', markup)
88+
89 def setPreviewDiff(self, preview_diff_bytes):
90 return PreviewDiff.create(
91 self.bmp, preview_diff_bytes, 'a', 'b', None, '')
92
93=== modified file 'lib/lp/code/interfaces/branch.py'
94--- lib/lp/code/interfaces/branch.py 2016-11-11 12:51:58 +0000
95+++ lib/lp/code/interfaces/branch.py 2017-11-08 11:16:30 +0000
96@@ -538,6 +538,12 @@
97 pending_writes = Attribute(
98 "Whether there is new Bazaar data for this branch.")
99
100+ pending_updates = Attribute(
101+ "Whether there is an update job of some kind (mirroring or scanning) "
102+ "pending for the Bazaar data in this branch. Note that "
103+ "pending_writes may be True and pending_updates False if a branch "
104+ "has been unscanned.")
105+
106 def latest_revisions(quantity=10):
107 """A specific number of the latest revisions in that branch."""
108
109
110=== modified file 'lib/lp/code/interfaces/gitref.py'
111--- lib/lp/code/interfaces/gitref.py 2016-12-05 14:46:40 +0000
112+++ lib/lp/code/interfaces/gitref.py 2017-11-08 11:16:30 +0000
113@@ -358,7 +358,7 @@
114 eager_load=False):
115 """Return BranchMergeProposals dependent on merging this reference."""
116
117- pending_writes = Attribute(
118+ pending_updates = Attribute(
119 "Whether there are recent changes in this repository that have not "
120 "yet been scanned.")
121
122
123=== modified file 'lib/lp/code/interfaces/gitrepository.py'
124--- lib/lp/code/interfaces/gitrepository.py 2017-02-10 12:52:07 +0000
125+++ lib/lp/code/interfaces/gitrepository.py 2017-11-08 11:16:30 +0000
126@@ -537,7 +537,7 @@
127 def isRepositoryMergeable(other):
128 """Is the other repository mergeable into this one (or vice versa)?"""
129
130- pending_writes = Attribute(
131+ pending_updates = Attribute(
132 "Whether there are recent changes in this repository that have not "
133 "yet been scanned.")
134
135
136=== modified file 'lib/lp/code/model/branch.py'
137--- lib/lp/code/model/branch.py 2016-11-11 14:24:38 +0000
138+++ lib/lp/code/model/branch.py 2017-11-08 11:16:30 +0000
139@@ -1163,25 +1163,53 @@
140 return recipients
141
142 @property
143- def pending_writes(self):
144- """See `IBranch`.
145+ def _pending_mirror_operations(self):
146+ """Does this branch have pending mirror operations?
147
148- A branch has pending writes if it has just been pushed to, if it has
149- been mirrored and not yet scanned or if it is in the middle of being
150+ A branch has pending mirror operations if it is an imported branch
151+ that has just been pushed to or if it is in the middle of being
152 mirrored.
153 """
154 new_data_pushed = (
155 self.branch_type == BranchType.IMPORTED
156 and self.next_mirror_time is not None)
157- # XXX 2010-04-22, MichaelHudson: This should really look for a branch
158- # scan job.
159- pulled_but_not_scanned = self.last_mirrored_id != self.last_scanned_id
160 pull_in_progress = (
161 self.last_mirror_attempt is not None
162 and (self.last_mirrored is None
163 or self.last_mirror_attempt > self.last_mirrored))
164- return (
165- new_data_pushed or pulled_but_not_scanned or pull_in_progress)
166+ return new_data_pushed or pull_in_progress
167+
168+ @property
169+ def pending_writes(self):
170+ """See `IBranch`.
171+
172+ A branch has pending writes if it has pending mirror operations or
173+ if it has been mirrored and not yet scanned. Use this when you need
174+ to know if the branch is in a condition where it is possible to run
175+ other jobs on it: for example, a branch that has been unscanned
176+ cannot support jobs being run for its related merge proposals.
177+ """
178+ pulled_but_not_scanned = self.last_mirrored_id != self.last_scanned_id
179+ return self._pending_mirror_operations or pulled_but_not_scanned
180+
181+ @property
182+ def pending_updates(self):
183+ """See `IBranch`.
184+
185+ A branch has pending updates if it has pending mirror operations or
186+ if it has a pending scan job. Use this when you need to know if
187+ there is work queued, for example when deciding whether to display
188+ in-progress UI indicators.
189+ """
190+ from lp.code.model.branchjob import BranchJob, BranchJobType
191+ jobs = Store.of(self).find(
192+ BranchJob,
193+ BranchJob.branch == self,
194+ Job.id == BranchJob.jobID,
195+ Job._status.is_in([JobStatus.WAITING, JobStatus.RUNNING]),
196+ BranchJob.job_type == BranchJobType.SCAN_BRANCH)
197+ pending_scan_job = not jobs.is_empty()
198+ return self._pending_mirror_operations or pending_scan_job
199
200 def getScannerData(self):
201 """See `IBranch`."""
202
203=== modified file 'lib/lp/code/model/gitref.py'
204--- lib/lp/code/model/gitref.py 2017-08-22 11:33:34 +0000
205+++ lib/lp/code/model/gitref.py 2017-11-08 11:16:30 +0000
206@@ -282,9 +282,9 @@
207 prerequisite_path=self.path, eager_load=eager_load)
208
209 @property
210- def pending_writes(self):
211+ def pending_updates(self):
212 """See `IGitRef`."""
213- return self.repository.pending_writes
214+ return self.repository.pending_updates
215
216 def _getLog(self, start, limit=None, stop=None, union_repository=None,
217 enable_hosting=None, enable_memcache=None, logger=None):
218@@ -715,7 +715,7 @@
219 createMergeProposal = _unimplemented
220 getMergeProposals = _unimplemented
221 getDependentMergeProposals = _unimplemented
222- pending_writes = False
223+ pending_updates = False
224
225 def getCommits(self, *args, **kwargs):
226 """See `IGitRef`."""
227
228=== modified file 'lib/lp/code/model/gitrepository.py'
229--- lib/lp/code/model/gitrepository.py 2017-05-04 16:02:40 +0000
230+++ lib/lp/code/model/gitrepository.py 2017-11-08 11:16:30 +0000
231@@ -949,7 +949,7 @@
232 return self.namespace.areRepositoriesMergeable(other.namespace)
233
234 @property
235- def pending_writes(self):
236+ def pending_updates(self):
237 """See `IGitRepository`."""
238 from lp.code.model.gitjob import (
239 GitJob,
240
241=== modified file 'lib/lp/code/model/tests/test_branch.py'
242--- lib/lp/code/model/tests/test_branch.py 2017-10-04 01:49:22 +0000
243+++ lib/lp/code/model/tests/test_branch.py 2017-11-08 11:16:30 +0000
244@@ -130,6 +130,8 @@
245 from lp.services.database.constants import UTC_NOW
246 from lp.services.database.interfaces import IStore
247 from lp.services.features.testing import FeatureFixture
248+from lp.services.job.interfaces.job import JobStatus
249+from lp.services.job.runner import JobRunner
250 from lp.services.job.tests import (
251 block_on_job,
252 monitor_celery,
253@@ -155,6 +157,7 @@
254 TestCaseWithFactory,
255 WebServiceTestCase,
256 )
257+from lp.testing.dbuser import dbuser
258 from lp.testing.factory import LaunchpadObjectFactory
259 from lp.testing.layers import (
260 CeleryBranchWriteJobLayer,
261@@ -2364,89 +2367,133 @@
262 self.assertNamespaceEqual(namespace, branch.namespace)
263
264
265-class TestPendingWrites(TestCaseWithFactory):
266+class TestPendingWritesAndUpdates(TestCaseWithFactory):
267 """Are there changes to this branch not reflected in the database?"""
268
269 layer = LaunchpadFunctionalLayer
270
271 def test_new_branch_no_writes(self):
272- # New branches have no pending writes.
273+ # New branches have no pending writes or pending updates.
274 branch = self.factory.makeAnyBranch()
275- self.assertEqual(False, branch.pending_writes)
276+ self.assertFalse(branch.pending_writes)
277+ self.assertFalse(branch.pending_updates)
278
279 def test_branchChanged_for_hosted(self):
280 # If branchChanged has been called with a new tip revision id, there
281- # are pending writes.
282+ # are pending writes and pending updates.
283 branch = self.factory.makeAnyBranch(branch_type=BranchType.HOSTED)
284 with person_logged_in(branch.owner):
285 branch.branchChanged('', 'new-tip', None, None, None)
286- self.assertEqual(True, branch.pending_writes)
287+ self.assertTrue(branch.pending_writes)
288+ self.assertTrue(branch.pending_updates)
289+
290+ def test_unscanned_without_rescan(self):
291+ # If a branch was unscanned without requesting a rescan, then there
292+ # are pending writes but no pending updates.
293+ self.useBzrBranches(direct_database=True)
294+ branch, bzr_tree = self.create_branch_and_tree()
295+ rev_id = self.factory.getUniqueString(b'rev-id')
296+ bzr_tree.commit('Commit', committer='me@example.com', rev_id=rev_id)
297+ removeSecurityProxy(branch).branchChanged('', rev_id, None, None, None)
298+ transaction.commit()
299+ [job] = getUtility(IBranchScanJobSource).iterReady()
300+ with dbuser('branchscanner'):
301+ JobRunner([job]).runAll()
302+ self.assertFalse(branch.pending_writes)
303+ self.assertFalse(branch.pending_updates)
304+ removeSecurityProxy(branch).unscan(rescan=False)
305+ self.assertTrue(branch.pending_writes)
306+ self.assertFalse(branch.pending_updates)
307+
308+ def test_unscanned_with_rescan(self):
309+ # If a branch was unscanned and a rescan was requested, then there
310+ # are pending writes and pending updates.
311+ self.useBzrBranches(direct_database=True)
312+ branch, bzr_tree = self.create_branch_and_tree()
313+ rev_id = self.factory.getUniqueString(b'rev-id')
314+ bzr_tree.commit('Commit', committer='me@example.com', rev_id=rev_id)
315+ removeSecurityProxy(branch).branchChanged('', rev_id, None, None, None)
316+ transaction.commit()
317+ [job] = getUtility(IBranchScanJobSource).iterReady()
318+ with dbuser('branchscanner'):
319+ JobRunner([job]).runAll()
320+ self.assertFalse(branch.pending_writes)
321+ self.assertFalse(branch.pending_updates)
322+ removeSecurityProxy(branch).unscan(rescan=True)
323+ self.assertTrue(branch.pending_writes)
324+ self.assertTrue(branch.pending_updates)
325
326 def test_requestMirror_for_imported(self):
327 # If an imported branch has a requested mirror, then we've just
328- # imported new changes. Therefore, pending writes.
329+ # imported new changes. Therefore, pending writes and pending
330+ # updates.
331 branch = self.factory.makeAnyBranch(branch_type=BranchType.IMPORTED)
332 branch.requestMirror()
333- self.assertEqual(True, branch.pending_writes)
334+ self.assertTrue(branch.pending_writes)
335+ self.assertTrue(branch.pending_updates)
336
337 def test_requestMirror_for_mirrored(self):
338- # Mirrored branches *always* have a requested mirror. The fact that a
339- # mirror is requested has no bearing on whether there are pending
340- # writes. Thus, pending_writes is False.
341+ # Mirrored branches *always* have a requested mirror. The fact that
342+ # a mirror is requested has no bearing on whether there are pending
343+ # writes or pending updates. Thus, pending_writes and
344+ # pending_updates are both False.
345 branch = self.factory.makeAnyBranch(branch_type=BranchType.MIRRORED)
346 branch.requestMirror()
347- self.assertEqual(False, branch.pending_writes)
348+ self.assertFalse(branch.pending_writes)
349+ self.assertFalse(branch.pending_updates)
350
351 def test_pulled_but_not_scanned(self):
352 # If a branch has been pulled (mirrored) but not scanned, then we have
353 # yet to load the revisions into the database. This means there are
354- # pending writes.
355+ # pending writes and pending updates.
356 branch = self.factory.makeAnyBranch(branch_type=BranchType.MIRRORED)
357 branch.startMirroring()
358 rev_id = self.factory.getUniqueString('rev-id')
359 removeSecurityProxy(branch).branchChanged(
360 '', rev_id, None, None, None)
361- self.assertEqual(True, branch.pending_writes)
362+ self.assertTrue(branch.pending_writes)
363+ self.assertTrue(branch.pending_updates)
364
365 def test_pulled_and_scanned(self):
366 # If a branch has been pulled and scanned, then there are no pending
367- # writes.
368+ # writes or pending updates.
369 branch = self.factory.makeAnyBranch(branch_type=BranchType.MIRRORED)
370 branch.startMirroring()
371 rev_id = self.factory.getUniqueString('rev-id')
372 removeSecurityProxy(branch).branchChanged(
373 '', rev_id, None, None, None)
374- # Cheat! The actual API for marking a branch as scanned is
375- # updateScannedDetails. That requires a revision in the database
376+ # Cheat! The actual API for marking a branch as scanned is to run
377+ # the BranchScanJob. That requires a revision in the database
378 # though.
379 removeSecurityProxy(branch).last_scanned_id = rev_id
380- self.assertEqual(False, branch.pending_writes)
381+ [job] = getUtility(IBranchScanJobSource).iterReady()
382+ removeSecurityProxy(job).job._status = JobStatus.COMPLETED
383+ self.assertFalse(branch.pending_writes)
384+ self.assertFalse(branch.pending_updates)
385
386 def test_first_mirror_started(self):
387 # If we have started mirroring the branch for the first time, then
388- # there are probably pending writes.
389+ # there are probably pending writes and pending updates.
390 branch = self.factory.makeAnyBranch(branch_type=BranchType.MIRRORED)
391 branch.startMirroring()
392- self.assertEqual(True, branch.pending_writes)
393+ self.assertTrue(branch.pending_writes)
394+ self.assertTrue(branch.pending_updates)
395
396 def test_following_mirror_started(self):
397 # If we have started mirroring the branch, then there are probably
398- # pending writes.
399+ # pending writes and pending updates.
400 branch = self.factory.makeAnyBranch(branch_type=BranchType.MIRRORED)
401 branch.startMirroring()
402 rev_id = self.factory.getUniqueString('rev-id')
403 removeSecurityProxy(branch).branchChanged(
404 '', rev_id, None, None, None)
405- # Cheat! The actual API for marking a branch as scanned is
406- # updateScannedDetails. That requires a revision in the database
407- # though.
408- removeSecurityProxy(branch).last_scanned_id = rev_id
409- # Cheat again! We can only tell if mirroring has started if the last
410+ # Cheat! We can only tell if mirroring has started if the last
411 # mirrored attempt is different from the last mirrored time. To ensure
412 # this, we start the second mirror in a new transaction.
413 transaction.commit()
414 branch.startMirroring()
415- self.assertEqual(True, branch.pending_writes)
416+ self.assertTrue(branch.pending_writes)
417+ self.assertTrue(branch.pending_updates)
418
419
420 class TestBranchPrivacy(TestCaseWithFactory):
421
422=== modified file 'lib/lp/code/model/tests/test_gitrepository.py'
423--- lib/lp/code/model/tests/test_gitrepository.py 2017-10-04 01:29:35 +0000
424+++ lib/lp/code/model/tests/test_gitrepository.py 2017-11-08 11:16:30 +0000
425@@ -960,28 +960,28 @@
426 self.assertEqual(namespace, repository.namespace)
427
428
429-class TestGitRepositoryPendingWrites(TestCaseWithFactory):
430+class TestGitRepositoryPendingUpdates(TestCaseWithFactory):
431 """Are there changes to this repository not reflected in the database?"""
432
433 layer = LaunchpadFunctionalLayer
434
435- def test_new_repository_no_writes(self):
436- # New repositories have no pending writes.
437+ def test_new_repository_no_updates(self):
438+ # New repositories have no pending updates.
439 repository = self.factory.makeGitRepository()
440- self.assertFalse(repository.pending_writes)
441+ self.assertFalse(repository.pending_updates)
442
443 def test_notify(self):
444 # If the hosting service has just sent us a change notification,
445- # then there are pending writes, but running the ref-scanning job
446+ # then there are pending updates, but running the ref-scanning job
447 # clears that flag.
448 git_api = GitAPI(None, None)
449 repository = self.factory.makeGitRepository()
450 self.assertIsNone(git_api.notify(repository.getInternalPath()))
451- self.assertTrue(repository.pending_writes)
452+ self.assertTrue(repository.pending_updates)
453 [job] = list(getUtility(IGitRefScanJobSource).iterReady())
454 with dbuser("branchscanner"):
455 JobRunner([job]).runAll()
456- self.assertFalse(repository.pending_writes)
457+ self.assertFalse(repository.pending_updates)
458
459
460 class TestGitRepositoryPrivacy(TestCaseWithFactory):
461
462=== modified file 'lib/lp/code/templates/branch-index.pt'
463--- lib/lp/code/templates/branch-index.pt 2016-10-13 12:43:14 +0000
464+++ lib/lp/code/templates/branch-index.pt 2017-11-08 11:16:30 +0000
465@@ -130,9 +130,9 @@
466
467 </div>
468
469- <div class="yui-g" tal:condition="view/pending_writes">
470+ <div class="yui-g" tal:condition="view/pending_updates">
471 <div class="portlet">
472- <div id="branch-pending-writes" class="pending-update">
473+ <div id="branch-pending-updates" class="pending-update">
474 <h3>Updating branch...</h3>
475 <p>
476 Launchpad is processing new changes to this branch which will be
477
478=== modified file 'lib/lp/code/templates/gitrepository-index.pt'
479--- lib/lp/code/templates/gitrepository-index.pt 2016-10-13 12:43:14 +0000
480+++ lib/lp/code/templates/gitrepository-index.pt 2017-11-08 11:16:30 +0000
481@@ -72,9 +72,9 @@
482 </div>
483 </div>
484
485- <div class="yui-g" tal:condition="context/pending_writes">
486+ <div class="yui-g" tal:condition="context/pending_updates">
487 <div class="portlet">
488- <div id="repository-pending-writes" class="pending-update">
489+ <div id="repository-pending-updates" class="pending-update">
490 <h3>Updating repository...</h3>
491 <p>
492 Launchpad is processing new changes to this repository which will
493
494=== modified file 'lib/lp/codehosting/tests/test_branchdistro.py'
495--- lib/lp/codehosting/tests/test_branchdistro.py 2012-02-15 17:29:54 +0000
496+++ lib/lp/codehosting/tests/test_branchdistro.py 2017-11-08 11:16:30 +0000
497@@ -273,12 +273,12 @@
498 new_branch).getScannerData()
499 self.assertEqual(old_ancestry, new_ancestry)
500 self.assertEqual(old_history, new_history)
501- self.assertFalse(new_branch.pending_writes)
502 self.assertIs(None, new_branch.stacked_on)
503 self.assertEqual(new_branch, db_branch.stacked_on)
504 # The script doesn't have permission to create branch jobs, but just
505 # to be insanely paranoid.
506 switch_dbuser('launchpad')
507+ self.assertFalse(new_branch.pending_writes)
508 scan_jobs = list(getUtility(IBranchScanJobSource).iterReady())
509 self.assertEqual(existing_scan_job_count, len(scan_jobs))
510