Fix timer jobs getting aborted from abandoning a change

NullChange returned equal for any change in the project, now
only returns equal for a NullChange (_id None) in the project.

Also added test case for the issue.

Change-Id: I4e43d1aa6cb040f775308f18333e090ff20e0dc0
Closes-Bug: 2000223
diff --git a/tests/fixtures/layout-no-timer.yaml b/tests/fixtures/layout-no-timer.yaml
index 9436821..ca40d13 100644
--- a/tests/fixtures/layout-no-timer.yaml
+++ b/tests/fixtures/layout-no-timer.yaml
@@ -1,14 +1,28 @@
 pipelines:
+  - name: check
+    manager: IndependentPipelineManager
+    trigger:
+      gerrit:
+        - event: patchset-created
+    success:
+      gerrit:
+        verified: 1
+    failure:
+      gerrit:
+        verified: -1
+
   - name: periodic
     manager: IndependentPipelineManager
     # Trigger is required, set it to one that is a noop
     # during tests that check the timer trigger.
     trigger:
       gerrit:
-        - event: patchset-created
+        - event: ref-updated
 
 projects:
   - name: org/project
+    check:
+      - project-test1
     periodic:
       - project-bitrot-stable-old
       - project-bitrot-stable-older
diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py
index 0779bfa..26ad196 100755
--- a/tests/test_scheduler.py
+++ b/tests/test_scheduler.py
@@ -1710,6 +1710,41 @@
         self.assertEqual(A.reported, 0, "Abandoned change should not report")
         self.assertEqual(B.reported, 1, "Change should report")
 
+    def test_abandoned_not_timer(self):
+        "Test that an abandoned change does not cancel timer jobs"
+
+        self.worker.hold_jobs_in_build = True
+
+        # Start timer trigger - also org/project
+        self.config.set('zuul', 'layout_config',
+                        'tests/fixtures/layout-idle.yaml')
+        self.sched.reconfigure(self.config)
+        self.registerJobs()
+        # The pipeline triggers every second, so we should have seen
+        # several by now.
+        time.sleep(5)
+        self.waitUntilSettled()
+        # Stop queuing timer triggered jobs so that the assertions
+        # below don't race against more jobs being queued.
+        self.config.set('zuul', 'layout_config',
+                        'tests/fixtures/layout-no-timer.yaml')
+        self.sched.reconfigure(self.config)
+        self.registerJobs()
+        self.assertEqual(len(self.builds), 2, "Two timer jobs")
+
+        A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
+        self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
+        self.waitUntilSettled()
+        self.assertEqual(len(self.builds), 3, "One change plus two timer jobs")
+
+        self.fake_gerrit.addEvent(A.getChangeAbandonedEvent())
+        self.waitUntilSettled()
+
+        self.assertEqual(len(self.builds), 2, "Two timer jobs remain")
+
+        self.worker.release()
+        self.waitUntilSettled()
+
     def test_zuul_url_return(self):
         "Test if ZUUL_URL is returning when zuul_url is set in zuul.conf"
         self.assertTrue(self.sched.config.has_option('merger', 'zuul_url'))
diff --git a/zuul/model.py b/zuul/model.py
index 8dc28df..4d402ff 100644
--- a/zuul/model.py
+++ b/zuul/model.py
@@ -965,7 +965,8 @@
         return None
 
     def equals(self, other):
-        if (self.project == other.project):
+        if (self.project == other.project
+            and other._id() is None):
             return True
         return False