Add base class for reporters

and test the all reporters adhere to the set contract.

Also standardise the reporter (triggers+sources to come) class names
to NameReporter.

This will make it easier to do more reporters in the future and also
add the possibility of loading reporters dynamically.

Co-Authored-By: Gregory Haynes <greg@greghaynes.net>

Change-Id: Ie67537c44bbb0dc5aa2a4708ffb5d381ce7e80fc
diff --git a/tests/base.py b/tests/base.py
index 9316b43..7cd9de3 100755
--- a/tests/base.py
+++ b/tests/base.py
@@ -1041,8 +1041,8 @@
     def register_reporters(self):
         # Register the available reporters
         self.sched.registerReporter(
-            zuul.reporter.gerrit.Reporter(self.fake_gerrit))
-        self.smtp_reporter = zuul.reporter.smtp.Reporter(
+            zuul.reporter.gerrit.GerritReporter(self.fake_gerrit))
+        self.smtp_reporter = zuul.reporter.smtp.SMTPReporter(
             self.config.get('smtp', 'default_from'),
             self.config.get('smtp', 'default_to'),
             self.config.get('smtp', 'server'))
diff --git a/tests/test_reporter.py b/tests/test_reporter.py
new file mode 100644
index 0000000..e7290b1
--- /dev/null
+++ b/tests/test_reporter.py
@@ -0,0 +1,46 @@
+# Copyright 2014 Rackspace Australia
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+import testtools
+
+import zuul.reporter
+
+
+class TestSMTPReporter(testtools.TestCase):
+    log = logging.getLogger("zuul.test_reporter")
+
+    def setUp(self):
+        super(TestSMTPReporter, self).setUp()
+
+    def test_reporter_abc(self):
+        # We only need to instantiate a class for this
+        reporter = zuul.reporter.smtp.SMTPReporter('', '')  # noqa
+
+    def test_reporter_name(self):
+        self.assertEqual('smtp', zuul.reporter.smtp.SMTPReporter.name)
+
+
+class TestGerritReporter(testtools.TestCase):
+    log = logging.getLogger("zuul.test_reporter")
+
+    def setUp(self):
+        super(TestGerritReporter, self).setUp()
+
+    def test_reporter_abc(self):
+        # We only need to instantiate a class for this
+        reporter = zuul.reporter.gerrit.GerritReporter(None)  # noqa
+
+    def test_reporter_name(self):
+        self.assertEqual('gerrit', zuul.reporter.gerrit.GerritReporter.name)
diff --git a/tests/test_scheduler.py b/tests/test_scheduler.py
index d99a65c..1713cac 100755
--- a/tests/test_scheduler.py
+++ b/tests/test_scheduler.py
@@ -3361,23 +3361,23 @@
 
         self.assertTrue(isinstance(
             self.sched.layout.pipelines['check'].merge_failure_actions[0].
-            reporter, zuul.reporter.gerrit.Reporter))
+            reporter, zuul.reporter.gerrit.GerritReporter))
 
         self.assertTrue(
             (
                 isinstance(self.sched.layout.pipelines['gate'].
                            merge_failure_actions[0].reporter,
-                           zuul.reporter.smtp.Reporter) and
+                           zuul.reporter.smtp.SMTPReporter) and
                 isinstance(self.sched.layout.pipelines['gate'].
                            merge_failure_actions[1].reporter,
-                           zuul.reporter.gerrit.Reporter)
+                           zuul.reporter.gerrit.GerritReporter)
             ) or (
                 isinstance(self.sched.layout.pipelines['gate'].
                            merge_failure_actions[0].reporter,
-                           zuul.reporter.gerrit.Reporter) and
+                           zuul.reporter.gerrit.GerritReporter) and
                 isinstance(self.sched.layout.pipelines['gate'].
                            merge_failure_actions[1].reporter,
-                           zuul.reporter.smtp.Reporter)
+                           zuul.reporter.smtp.SMTPReporter)
             )
         )
 
diff --git a/zuul/cmd/server.py b/zuul/cmd/server.py
index 75226dc..5393289 100755
--- a/zuul/cmd/server.py
+++ b/zuul/cmd/server.py
@@ -175,8 +175,8 @@
         # See comment at top of file about zuul imports
         import zuul.reporter.gerrit
         import zuul.reporter.smtp
-        gerrit_reporter = zuul.reporter.gerrit.Reporter(self.gerrit)
-        smtp_reporter = zuul.reporter.smtp.Reporter(
+        gerrit_reporter = zuul.reporter.gerrit.GerritReporter(self.gerrit)
+        smtp_reporter = zuul.reporter.smtp.SMTPReporter(
             self.config.get('smtp', 'default_from')
             if self.config.has_option('smtp', 'default_from') else 'zuul',
             self.config.get('smtp', 'default_to')
diff --git a/zuul/reporter/__init__.py b/zuul/reporter/__init__.py
index e69de29..a9df257 100644
--- a/zuul/reporter/__init__.py
+++ b/zuul/reporter/__init__.py
@@ -0,0 +1,42 @@
+# Copyright 2014 Rackspace Australia
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import abc
+
+import six
+
+
+@six.add_metaclass(abc.ABCMeta)
+class BaseReporter(object):
+    """Base class for reporters.
+
+    Defines the exact public methods that must be supplied.
+    """
+
+    @abc.abstractmethod
+    def __init__(self, *args, **kwargs):
+        # TODO(jhesketh): Fix *args to just a connection
+        pass
+
+    @abc.abstractmethod
+    def report(self, source, change, message, params):
+        """Send the compiled report message."""
+
+    def getSubmitAllowNeeds(self, params):
+        """Get a list of code review labels that are allowed to be
+        "needed" in the submit records for a change, with respect
+        to this queue.  In other words, the list of review labels
+        this reporter itself is likely to set before submitting.
+        """
+        return []
diff --git a/zuul/reporter/gerrit.py b/zuul/reporter/gerrit.py
index a2debfd..f24c3b3 100644
--- a/zuul/reporter/gerrit.py
+++ b/zuul/reporter/gerrit.py
@@ -15,7 +15,10 @@
 import logging
 
 
-class Reporter(object):
+from zuul.reporter import BaseReporter
+
+
+class GerritReporter(BaseReporter):
     """Sends off reports to Gerrit."""
 
     name = 'gerrit'
diff --git a/zuul/reporter/smtp.py b/zuul/reporter/smtp.py
index 0992d66..2a3ed6a 100644
--- a/zuul/reporter/smtp.py
+++ b/zuul/reporter/smtp.py
@@ -17,8 +17,10 @@
 
 from email.mime.text import MIMEText
 
+from zuul.reporter import BaseReporter
 
-class Reporter(object):
+
+class SMTPReporter(BaseReporter):
     """Sends off reports to emails via SMTP."""
 
     name = 'smtp'
@@ -61,11 +63,3 @@
         except:
             return "Could not send email via SMTP"
         return
-
-    def getSubmitAllowNeeds(self, params):
-        """Get a list of code review labels that are allowed to be
-        "needed" in the submit records for a change, with respect
-        to this queue.  In other words, the list of review labels
-        this reporter itself is likely to set before submitting.
-        """
-        return []