Check liveness of changes before removing

The checks to remove old versions or abandoned changes have been
updated to operate on items and consider whether those items are
live.

Change-Id: Ife3c4833a7d6a8f29014eabb33e50f82918051f4
diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py
index fb90734..4ebc0da 100755
--- a/tests/test_scheduler.py
+++ b/tests/test_scheduler.py
@@ -1497,35 +1497,178 @@
         self.assertEqual(D.reported, 2)
         self.assertEqual(len(self.history), 9)  # 3 each for A, B, D.
 
-    def test_abandoned_change_dequeues(self):
-        "Test that an abandoned change is dequeued"
+    def test_new_patchset_check(self):
+        "Test a new patchset in check"
 
         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')
+        check_pipeline = self.sched.layout.pipelines['check']
+
+        # Add two git-dependent changes
+        B.setDependsOn(A, 1)
+        self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
         self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
         self.waitUntilSettled()
+
+        # A live item, and a non-live/live pair
+        items = check_pipeline.getAllItems()
+        self.assertEqual(len(items), 3)
+
+        self.assertEqual(items[0].change.number, '1')
+        self.assertEqual(items[0].change.patchset, '1')
+        self.assertFalse(items[0].live)
+
+        self.assertEqual(items[1].change.number, '2')
+        self.assertEqual(items[1].change.patchset, '1')
+        self.assertTrue(items[1].live)
+
+        self.assertEqual(items[2].change.number, '1')
+        self.assertEqual(items[2].change.patchset, '1')
+        self.assertTrue(items[2].live)
+
+        # Add a new patchset to A
+        A.addPatchset()
+        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(2))
+        self.waitUntilSettled()
+
+        # The live copy of A,1 should be gone, but the non-live and B
+        # should continue, and we should have a new A,2
+        items = check_pipeline.getAllItems()
+        self.assertEqual(len(items), 3)
+
+        self.assertEqual(items[0].change.number, '1')
+        self.assertEqual(items[0].change.patchset, '1')
+        self.assertFalse(items[0].live)
+
+        self.assertEqual(items[1].change.number, '2')
+        self.assertEqual(items[1].change.patchset, '1')
+        self.assertTrue(items[1].live)
+
+        self.assertEqual(items[2].change.number, '1')
+        self.assertEqual(items[2].change.patchset, '2')
+        self.assertTrue(items[2].live)
+
+        # Add a new patchset to B
+        B.addPatchset()
+        self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(2))
+        self.waitUntilSettled()
+
+        # The live copy of B,1 should be gone, and it's non-live copy of A,1
+        # but we should have a new B,2 (still based on A,1)
+        items = check_pipeline.getAllItems()
+        self.assertEqual(len(items), 3)
+
+        self.assertEqual(items[0].change.number, '1')
+        self.assertEqual(items[0].change.patchset, '2')
+        self.assertTrue(items[0].live)
+
+        self.assertEqual(items[1].change.number, '1')
+        self.assertEqual(items[1].change.patchset, '1')
+        self.assertFalse(items[1].live)
+
+        self.assertEqual(items[2].change.number, '2')
+        self.assertEqual(items[2].change.patchset, '2')
+        self.assertTrue(items[2].live)
+
+        self.builds[0].release()
+        self.waitUntilSettled()
+        self.builds[0].release()
+        self.waitUntilSettled()
+        self.worker.hold_jobs_in_build = False
+        self.worker.release()
+        self.waitUntilSettled()
+
+        self.assertEqual(A.reported, 1)
+        self.assertEqual(B.reported, 1)
+        self.assertEqual(self.history[0].result, 'ABORTED')
+        self.assertEqual(self.history[0].changes, '1,1')
+        self.assertEqual(self.history[1].result, 'ABORTED')
+        self.assertEqual(self.history[1].changes, '1,1 2,1')
+        self.assertEqual(self.history[2].result, 'SUCCESS')
+        self.assertEqual(self.history[2].changes, '1,2')
+        self.assertEqual(self.history[3].result, 'SUCCESS')
+        self.assertEqual(self.history[3].changes, '1,1 2,2')
+
+    def test_abandoned_gate(self):
+        "Test that an abandoned change is dequeued from gate"
+
+        self.worker.hold_jobs_in_build = True
+
+        A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+        A.addApproval('CRVW', 2)
+        self.fake_gerrit.addEvent(A.addApproval('APRV', 1))
+        self.waitUntilSettled()
         self.assertEqual(len(self.builds), 1, "One job being built (on hold)")
         self.assertEqual(self.builds[0].name, 'project-merge')
 
         self.fake_gerrit.addEvent(A.getChangeAbandonedEvent())
         self.waitUntilSettled()
 
-        # For debugging purposes...
-        # for pipeline in self.sched.layout.pipelines.values():
-        #     for queue in pipeline.queues:
-        #         self.log.info("pipepline %s queue %s contents %s" % (
-        #             pipeline.name, queue.name, queue.queue))
-
         self.worker.release('.*-merge')
         self.waitUntilSettled()
 
         self.assertEqual(len(self.builds), 0, "No job running")
-        self.assertEmptyQueues()
         self.assertEqual(len(self.history), 1, "Only one build in history")
         self.assertEqual(self.history[0].result, 'ABORTED',
+                         "Build should have been aborted")
+        self.assertEqual(A.reported, 1,
+                         "Abandoned gate change should report only start")
+
+    def test_abandoned_check(self):
+        "Test that an abandoned change is dequeued from check"
+
+        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')
+        check_pipeline = self.sched.layout.pipelines['check']
+
+        # Add two git-dependent changes
+        B.setDependsOn(A, 1)
+        self.fake_gerrit.addEvent(B.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+        # A live item, and a non-live/live pair
+        items = check_pipeline.getAllItems()
+        self.assertEqual(len(items), 3)
+
+        self.assertEqual(items[0].change.number, '1')
+        self.assertFalse(items[0].live)
+
+        self.assertEqual(items[1].change.number, '2')
+        self.assertTrue(items[1].live)
+
+        self.assertEqual(items[2].change.number, '1')
+        self.assertTrue(items[2].live)
+
+        # Abandon A
+        self.fake_gerrit.addEvent(A.getChangeAbandonedEvent())
+        self.waitUntilSettled()
+
+        # The live copy of A should be gone, but the non-live and B
+        # should continue
+        items = check_pipeline.getAllItems()
+        self.assertEqual(len(items), 2)
+
+        self.assertEqual(items[0].change.number, '1')
+        self.assertFalse(items[0].live)
+
+        self.assertEqual(items[1].change.number, '2')
+        self.assertTrue(items[1].live)
+
+        self.worker.hold_jobs_in_build = False
+        self.worker.release()
+        self.waitUntilSettled()
+
+        self.assertEqual(len(self.history), 4)
+        self.assertEqual(self.history[0].result, 'ABORTED',
                          'Build should have been aborted')
         self.assertEqual(A.reported, 0, "Abandoned change should not report")
+        self.assertEqual(B.reported, 1, "Change should report")
 
     def test_zuul_url_return(self):
         "Test if ZUUL_URL is returning when zuul_url is set in zuul.conf"