Merge "Fix NNFI bug with two failing changes at head"
diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py
index 395ff25..a1f48fa 100644
--- a/tests/test_scheduler.py
+++ b/tests/test_scheduler.py
@@ -1501,6 +1501,111 @@
self.assertEqual(B.reported, 2)
self.assertEqual(C.reported, 2)
+ def test_two_failed_changes_at_head(self):
+ "Test that changes are reparented correctly if 2 fail at head"
+
+ self.worker.hold_jobs_in_build = True
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+ B = self.fake_gerrit.addFakeChange('org/project', 'master', 'B')
+ C = self.fake_gerrit.addFakeChange('org/project', 'master', 'C')
+ A.addApproval('CRVW', 2)
+ B.addApproval('CRVW', 2)
+ C.addApproval('CRVW', 2)
+
+ self.worker.addFailTest('project-test1', A)
+ self.worker.addFailTest('project-test1', B)
+
+ self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
+ self.fake_gerrit.addEvent(B.addApproval('APRV', 1))
+ self.fake_gerrit.addEvent(C.addApproval('APRV', 1))
+ self.waitUntilSettled()
+
+ self.worker.release('.*-merge')
+ self.waitUntilSettled()
+ self.worker.release('.*-merge')
+ self.waitUntilSettled()
+ self.worker.release('.*-merge')
+ self.waitUntilSettled()
+
+ self.assertEqual(len(self.builds), 6)
+ self.assertEqual(self.builds[0].name, 'project-test1')
+ self.assertEqual(self.builds[1].name, 'project-test2')
+ self.assertEqual(self.builds[2].name, 'project-test1')
+ self.assertEqual(self.builds[3].name, 'project-test2')
+ self.assertEqual(self.builds[4].name, 'project-test1')
+ self.assertEqual(self.builds[5].name, 'project-test2')
+
+ self.assertTrue(self.job_has_changes(self.builds[0], A))
+ self.assertTrue(self.job_has_changes(self.builds[2], A))
+ self.assertTrue(self.job_has_changes(self.builds[2], B))
+ self.assertTrue(self.job_has_changes(self.builds[4], A))
+ self.assertTrue(self.job_has_changes(self.builds[4], B))
+ self.assertTrue(self.job_has_changes(self.builds[4], C))
+
+ # Fail change B first
+ self.release(self.builds[2])
+ self.waitUntilSettled()
+
+ # restart of C after B failure
+ self.worker.release('.*-merge')
+ self.waitUntilSettled()
+
+ self.assertEqual(len(self.builds), 5)
+ self.assertEqual(self.builds[0].name, 'project-test1')
+ self.assertEqual(self.builds[1].name, 'project-test2')
+ self.assertEqual(self.builds[2].name, 'project-test2')
+ self.assertEqual(self.builds[3].name, 'project-test1')
+ self.assertEqual(self.builds[4].name, 'project-test2')
+
+ self.assertTrue(self.job_has_changes(self.builds[1], A))
+ self.assertTrue(self.job_has_changes(self.builds[2], A))
+ self.assertTrue(self.job_has_changes(self.builds[2], B))
+ self.assertTrue(self.job_has_changes(self.builds[4], A))
+ self.assertFalse(self.job_has_changes(self.builds[4], B))
+ self.assertTrue(self.job_has_changes(self.builds[4], C))
+
+ # Finish running all passing jobs for change A
+ self.release(self.builds[1])
+ self.waitUntilSettled()
+ # Fail and report change A
+ self.release(self.builds[0])
+ self.waitUntilSettled()
+
+ # restart of B,C after A failure
+ self.worker.release('.*-merge')
+ self.waitUntilSettled()
+ self.worker.release('.*-merge')
+ self.waitUntilSettled()
+
+ self.assertEqual(len(self.builds), 4)
+ self.assertEqual(self.builds[0].name, 'project-test1') # B
+ self.assertEqual(self.builds[1].name, 'project-test2') # B
+ self.assertEqual(self.builds[2].name, 'project-test1') # C
+ self.assertEqual(self.builds[3].name, 'project-test2') # C
+
+ self.assertFalse(self.job_has_changes(self.builds[1], A))
+ self.assertTrue(self.job_has_changes(self.builds[1], B))
+ self.assertFalse(self.job_has_changes(self.builds[1], C))
+
+ self.assertFalse(self.job_has_changes(self.builds[2], A))
+ # After A failed and B and C restarted, B should be back in
+ # C's tests because it has not failed yet.
+ self.assertTrue(self.job_has_changes(self.builds[2], B))
+ self.assertTrue(self.job_has_changes(self.builds[2], C))
+
+ self.worker.hold_jobs_in_build = False
+ self.worker.release()
+ self.waitUntilSettled()
+
+ self.assertEqual(len(self.builds), 0)
+ self.assertEqual(len(self.history), 21)
+ self.assertEqual(A.data['status'], 'NEW')
+ self.assertEqual(B.data['status'], 'NEW')
+ self.assertEqual(C.data['status'], 'MERGED')
+ self.assertEqual(A.reported, 2)
+ self.assertEqual(B.reported, 2)
+ self.assertEqual(C.reported, 2)
+
def test_patch_order(self):
"Test that dependent patches are tested in the right order"
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
diff --git a/zuul/scheduler.py b/zuul/scheduler.py
index 514be2f..46897bd 100644
--- a/zuul/scheduler.py
+++ b/zuul/scheduler.py
@@ -982,8 +982,11 @@
failing_reasons.append('a needed change is failing')
self.cancelJobs(item, prime=False)
else:
- if (item_ahead and item_ahead != nnfi and
- not item_ahead.change.is_merged):
+ item_ahead_merged = False
+ if ((item_ahead and item_ahead.change.is_merged) or
+ not change_queue.dependent):
+ item_ahead_merged = True
+ if (item_ahead != nnfi and not item_ahead_merged):
# Our current base is different than what we expected,
# and it's not because our current base merged. Something
# ahead must have failed.