Add support for negative requirements

This change adds support for false matching of requirements. To make
this useful you can now require all approvals match a requirement or
only one (ie any).

Therefore depreciate require-approvals, replacing with
require-approvals-any and a new require-approvals-all.

Change-Id: I458e677315ccb90d64cd0c0e734951141324a9c3
diff --git a/tests/test_requirements.py b/tests/test_requirements.py
index 4316925..52e3973 100644
--- a/tests/test_requirements.py
+++ b/tests/test_requirements.py
@@ -323,3 +323,131 @@
         self.fake_gerrit.addEvent(B.addApproval('CRVW', 2))
         self.waitUntilSettled()
         self.assertEqual(len(self.history), 1)
+
+    def test_pipeline_require_negative_username(self):
+        "Test negative pipeline requirement: no comment from jenkins"
+        return self._test_require_negative_username('org/project1',
+                                                    'project1-pipeline')
+
+    def test_trigger_require_negative_username(self):
+        "Test negative trigger requirement: no comment from jenkins"
+        return self._test_require_negative_username('org/project2',
+                                                    'project2-trigger')
+
+    def _test_require_negative_username(self, project, job):
+        "Test negative username's match"
+        # Should only trigger if Jenkins hasn't voted.
+        self.config.set(
+            'zuul', 'layout_config',
+            'tests/fixtures/layout-requirement-negative-username.yaml')
+        self.sched.reconfigure(self.config)
+        self.registerJobs()
+
+        # add in a change with no comments
+        A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
+        self.waitUntilSettled()
+        self.assertEqual(len(self.history), 0)
+
+        # add in a comment that will trigger
+        self.fake_gerrit.addEvent(A.addApproval('CRVW', 1,
+                                                username='reviewer'))
+        self.waitUntilSettled()
+        self.assertEqual(len(self.history), 1)
+        self.assertEqual(self.history[0].name, job)
+
+        # add in a comment from jenkins user which shouldn't trigger
+        self.fake_gerrit.addEvent(A.addApproval('VRFY', 1, username='jenkins'))
+        self.waitUntilSettled()
+        self.assertEqual(len(self.history), 1)
+
+        # Check future reviews also won't trigger as a 'jenkins' user has
+        # commented previously
+        self.fake_gerrit.addEvent(A.addApproval('CRVW', 1,
+                                                username='reviewer'))
+        self.waitUntilSettled()
+        self.assertEqual(len(self.history), 1)
+
+    def test_pipeline_require_any(self):
+        "Test pipeline requirement: any requirement passes"
+        return self._test_require_any('org/project1', 'project1-pipeline')
+
+    def test_trigger_require_any(self):
+        "Test trigger requirement: any requirement passes"
+        return self._test_require_any('org/project2', 'project2-trigger')
+
+    def _test_require_any(self, project, job):
+        "Test any of the given requirements are matched"
+        self.config.set(
+            'zuul', 'layout_config',
+            'tests/fixtures/layout-requirement-any.yaml')
+        self.sched.reconfigure(self.config)
+        self.registerJobs()
+
+        A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
+        # A comment event that we will keep submitting to trigger
+        comment = A.addApproval('CRVW', 1, username='nobody')
+        self.fake_gerrit.addEvent(comment)
+        self.waitUntilSettled()
+        # No approval from Jenkins so should not be enqueued
+        self.assertEqual(len(self.history), 0)
+
+        # A +1 from jenkins should allow it to be enqueued
+        A.addApproval('VRFY', 1, username='jenkins')
+        self.fake_gerrit.addEvent(comment)
+        self.waitUntilSettled()
+        self.assertEqual(len(self.history), 1)
+        self.assertEqual(self.history[0].name, job)
+
+        # A non-negative from a non-core should not queue
+        B = self.fake_gerrit.addFakeChange(project, 'master', 'B')
+        # A comment event that we will keep submitting to trigger
+        comment = B.addApproval('CRVW', 1, username='nobody')
+        self.fake_gerrit.addEvent(comment)
+        self.waitUntilSettled()
+        self.assertEqual(len(self.history), 1)
+
+        # A non-negative from a core member should queue
+        B.addApproval('CRVW', 2, username='core-reviewer')
+        self.fake_gerrit.addEvent(comment)
+        self.waitUntilSettled()
+        self.assertEqual(len(self.history), 2)
+        self.assertEqual(self.history[1].name, job)
+
+    def test_pipeline_require_all(self):
+        "Test pipeline requirement: all requirements pass"
+        return self._test_require_all('org/project1', 'project1-pipeline')
+
+    def test_trigger_require_all(self):
+        "Test trigger requirement: all requirements pass"
+        return self._test_require_all('org/project2', 'project2-trigger')
+
+    def _test_require_all(self, project, job):
+        "Test all of the given requirements are matched"
+        self.config.set(
+            'zuul', 'layout_config',
+            'tests/fixtures/layout-requirement-all.yaml')
+        self.sched.reconfigure(self.config)
+        self.registerJobs()
+
+        A = self.fake_gerrit.addFakeChange(project, 'master', 'A')
+        self.waitUntilSettled()
+        self.assertEqual(len(self.history), 0)
+
+        # A +2 from 'nobody' only satisfies the non-negative requirement,
+        # not the requirement to be from 'jenkins'
+        comment = A.addApproval('VRFY', 1, username='nobody')
+        self.fake_gerrit.addEvent(comment)
+        self.waitUntilSettled()
+        self.assertEqual(len(self.history), 0)
+
+        B = self.fake_gerrit.addFakeChange(project, 'master', 'A')
+        self.waitUntilSettled()
+        self.assertEqual(len(self.history), 0)
+
+        # A +2 from Jenkins satisfies both the user condition and the
+        # non-negative condition
+        comment = B.addApproval('VRFY', 2, username='jenkins')
+        self.fake_gerrit.addEvent(comment)
+        self.waitUntilSettled()
+        self.assertEqual(len(self.history), 1)
+        self.assertEqual(self.history[0].name, job)