Repository: TheFriendlyCoder/pyjen

Issue:
Write tests for build blocker plugin
The build blocker plugin has very little code coverage atm. This needs to be improved.

Discussion:

Patch:
diff --git a/src/pyjen/build.py b/src/pyjen/build.py
index d0c902e..aba3a36 100644
--- a/src/pyjen/build.py
+++ b/src/pyjen/build.py
@@ -82,7 +82,6 @@ class Build(object):
         :rtype: :class:`bool`
         """
         data = self._api.get_api_data()
-
         return data['building']
 
     @property
@@ -105,10 +104,11 @@ class Build(object):
             * "SUCCESS"
             * "UNSTABLE"
             * "FAILURE"
+            * "ABORTED"
+
         :rtype: :class:`str`
         """
         data = self._api.get_api_data()
-
         return data['result']
 
     @property
@@ -162,6 +162,10 @@ class Build(object):
 
         return retval
 
+    def abort(self):
+        """Aborts this build before it completes"""
+        self._api.post(self._api.url + "stop")
+
 
 if __name__ == "__main__":  # pragma: no cover
     pass
diff --git a/src/pyjen/plugins/buildblocker.py b/src/pyjen/plugins/buildblocker.py
index 87d09e0..f89643f 100644
--- a/src/pyjen/plugins/buildblocker.py
+++ b/src/pyjen/plugins/buildblocker.py
@@ -8,6 +8,58 @@ class BuildBlockerProperty(XMLPlugin):
 
     https://wiki.jenkins-ci.org/display/JENKINS/Build+Blocker+Plugin
     """
+    QUEUE_SCAN_TYPES = ("DISABLED", "ALL", "BUILDABLE")
+    LEVEL_TYPES = ("GLOBAL", "NODE")
+
+    @property
+    def queue_scan(self):
+        """Checks to see whether build blocking scans the build queue or not
+
+        :returns: One of BuildBlockerProperty.QUEUE_SCAN_TYPES
+        :rtype: :class:`str`
+        """
+        retval = self._root.find("scanQueueFor").text
+        assert retval in BuildBlockerProperty.QUEUE_SCAN_TYPES
+        return retval
+
+    @queue_scan.setter
+    def queue_scan(self, value):
+        """Sets the type of build queue scanning for the blocking job
+
+        :param str value:
+            type of queue scanning to perform
+            Must be one of BuildBlockerProperty.QUEUE_SCAN_TYPES
+        """
+        if value not in BuildBlockerProperty.QUEUE_SCAN_TYPES:
+            raise ValueError(
+                "Build blocker queue scan may only be one of the following "
+                "types: " + ",".join(BuildBlockerProperty.QUEUE_SCAN_TYPES))
+        self._root.find("scanQueueFor").text = value
+
+    @property
+    def level(self):
+        """Gets the scope of the blocked job settings
+
+        :returns: One of BuildBlockerProperty.LEVEL_TYPES
+        :rtype: :class:`str`
+        """
+        retval = self._root.find("blockLevel").text
+        assert retval in BuildBlockerProperty.LEVEL_TYPES
+        return retval
+
+    @level.setter
+    def level(self, value):
+        """Sets the scope of the blocked builds
+
+        :param str value:
+            scope for this build blocker
+            Must be one of BuildBlockerProperty.LEVEL_TYPES
+        """
+        if value not in BuildBlockerProperty.LEVEL_TYPES:
+            raise ValueError(
+                "Build blocker scope level may only be one of the following "
+                "types: " + ",".join(BuildBlockerProperty.LEVEL_TYPES))
+        self._root.find("blockLevel").text = value
 
     @property
     def blockers(self):
@@ -16,27 +68,24 @@ class BuildBlockerProperty(XMLPlugin):
         :return: list of search criteria for blocking jobs
         :rtype: :class:`list`
         """
-        retval = []
-        if not self.is_enabled:
-            return retval
-
         temp = self._root.find("blockingJobs").text
-        if temp is None:
-            return retval
-
-        retval = temp.split()
-        return retval
+        return temp.split()
 
     @blockers.setter
-    def blockers(self, new_blockers):
-        """Sets the list of search criteria for blocking jobs
-
-        :param list new_blockers: list of search criteria for blocking jobs
+    def blockers(self, patterns):
+        """Defines the names or regular expressions for jobs that block
+        execution of this job
+
+        :param patterns:
+            One or more names or regular expressions for jobs that block the
+            execution of this one.
+        :type patterns: either :class:`list` or :class:`str`
         """
         node = self._root.find("blockingJobs")
-        if node is None:
-            node = ElementTree.SubElement(self._root, 'blockingJobs')
-        node.text = "\n".join(new_blockers)
+        if isinstance(patterns, str):
+            node.text = patterns
+        else:
+            node.text = "\n".join(patterns)
 
     @property
     def is_enabled(self):
@@ -51,15 +100,11 @@ class BuildBlockerProperty(XMLPlugin):
     def enable(self):
         """Enables this set of build blockers"""
         node = self._root.find("useBuildBlocker")
-        if node is None:
-            node = ElementTree.SubElement(self._root, 'useBuildBlocker')
         node.text = "true"
 
     def disable(self):
         """Disables this set of build blockers"""
         node = self._root.find("useBuildBlocker")
-        if node is None:
-            node = ElementTree.SubElement(self._root, 'useBuildBlocker')
         node.text = "false"
 
     @staticmethod
@@ -71,7 +116,30 @@ class BuildBlockerProperty(XMLPlugin):
 
         :rtype: :class:`str`
         """
-        return "buildblocker"
+        return "hudson.plugins.buildblocker.BuildBlockerProperty"
+
+    @classmethod
+    def create(cls, patterns):
+        """Factory method used to instantiate an instance of this plugin
+
+        :param patterns:
+            One or more names or regular expressions for jobs that block the
+            execution of this one.
+        :type patterns: either :class:`list` or :class:`str`
+        """
+        default_xml = """<hudson.plugins.buildblocker.BuildBlockerProperty plugin="build-blocker-plugin@1.7.3">
+<useBuildBlocker>true</useBuildBlocker>
+<blockLevel>GLOBAL</blockLevel>
+<scanQueueFor>DISABLED</scanQueueFor>
+</hudson.plugins.buildblocker.BuildBlockerProperty>"""
+
+        root_node = ElementTree.fromstring(default_xml)
+        jobs_node = ElementTree.SubElement(root_node, "blockingJobs")
+        if isinstance(patterns, str):
+            jobs_node.text = patterns
+        else:
+            jobs_node.text = " ".join(patterns)
+        return cls(root_node)
 
 
 PluginClass = BuildBlockerProperty
diff --git a/src/pyjen/queue_item.py b/src/pyjen/queue_item.py
index 7783f26..7c2165b 100644
--- a/src/pyjen/queue_item.py
+++ b/src/pyjen/queue_item.py
@@ -88,7 +88,7 @@ class QueueItem(object):
 
         :rtype: :class:`bool`
         """
-        return self._data["cancelled"]
+        return self._data.get("cancelled", False)
 
     @property
     def job(self):


Test patch:
diff --git a/tests/conftest.py b/tests/conftest.py
index 75dce52..178ae95 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -37,6 +37,7 @@ JENKINS_PLUGINS = [
     "sectioned-view",
     "conditional-buildstep",
     "parameterized-trigger",
+    "build-blocker-plugin",
 ]
 
 
diff --git a/tests/test_build.py b/tests/test_build.py
index 1d02763..68e7129 100644
--- a/tests/test_build.py
+++ b/tests/test_build.py
@@ -88,5 +88,32 @@ def test_console_text(jenkins_env):
         assert expected_output in jb.last_build.console_output
 
 
+@pytest.mark.timeout(10)
+def test_abort(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    expected_job_name = "test_abort"
+    jb = jk.create_job(expected_job_name, "hudson.model.FreeStyleProject")
+
+    with clean_job(jb):
+        jb.quiet_period = 0
+        shell_builder = ShellBuilder.create("echo 'waiting for sleep' && sleep 40")
+        jb.add_builder(shell_builder)
+
+        # Get a fresh copy of our job to ensure we have an up to date
+        # copy of the config.xml for the job
+        async_assert(lambda: jk.find_job(expected_job_name).builders)
+
+        # Trigger a build and wait for it to complete
+        jb.start_build()
+        async_assert(lambda: jb.last_build)
+
+        async_assert(lambda: "waiting for sleep" in jb.last_build.console_output)
+
+        jb.last_build.abort()
+
+        assert jb.last_build.is_building is False
+        assert jb.last_build.result == "ABORTED"
+
+
 if __name__ == "__main__":
     pytest.main([__file__, "-v", "-s"])
diff --git a/tests/test_build_queue.py b/tests/test_build_queue.py
index eb0d335..79bea8f 100644
--- a/tests/test_build_queue.py
+++ b/tests/test_build_queue.py
@@ -83,5 +83,19 @@ def test_start_build_returned_queue_item(jenkins_env):
         assert queue.items[0] == item
 
 
+def test_queue_get_build(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    jb = jk.create_job("test_queue_get_build", "hudson.model.FreeStyleProject")
+    with clean_job(jb):
+        jb.quiet_period = 0
+        item = jb.start_build()
+
+        async_assert(lambda: not item.waiting)
+
+        bld = item.build
+        assert bld is not None
+        assert bld == jb.last_build
+
+
 if __name__ == "__main__":
     pytest.main([__file__, "-v", "-s"])
diff --git a/tests/test_job_properties/test_build_blocker.py b/tests/test_job_properties/test_build_blocker.py
new file mode 100644
index 0000000..e884044
--- /dev/null
+++ b/tests/test_job_properties/test_build_blocker.py
@@ -0,0 +1,209 @@
+import pytest
+from pyjen.jenkins import Jenkins
+from pyjen.plugins.buildblocker import BuildBlockerProperty
+from pyjen.plugins.shellbuilder import ShellBuilder
+from ..utils import async_assert, clean_job
+
+
+def test_add_build_blocker(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    job_name = "test_add_build_blocker"
+    jb = jk.create_job(job_name, "hudson.model.FreeStyleProject")
+    with clean_job(jb):
+        expected_job_name = "MyCoolJob"
+        build_blocker = BuildBlockerProperty.create(expected_job_name)
+        jb.add_property(build_blocker)
+
+        # Get a fresh copy of our job to ensure we have an up to date
+        # copy of the config.xml for the job
+        async_assert(lambda: jk.find_job(job_name).properties)
+        properties = jk.find_job(job_name).properties
+
+        assert isinstance(properties, list)
+        assert len(properties) == 1
+        assert isinstance(properties[0], BuildBlockerProperty)
+
+
+def test_multiple_blocking_jobs(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    job_name = "test_multiple_blocking_jobs"
+    jb = jk.create_job(job_name, "hudson.model.FreeStyleProject")
+    with clean_job(jb):
+        expected_jobs = ["MyCoolJob1", "MyCoolJob2"]
+        build_blocker = BuildBlockerProperty.create(["ShouldNotSeeMe"])
+        build_blocker.blockers = expected_jobs
+        jb.add_property(build_blocker)
+
+        # Get a fresh copy of our job to ensure we have an up to date
+        # copy of the config.xml for the job
+        async_assert(lambda: jk.find_job(job_name).properties)
+        properties = jk.find_job(job_name).properties
+
+        prop = properties[0]
+        blockers = prop.blockers
+        assert isinstance(blockers, list)
+        assert len(blockers) == 2
+        assert expected_jobs[0] in blockers
+        assert expected_jobs[1] in blockers
+
+
+def test_default_queue_scan(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    job_name = "test_default_queue_scan"
+    jb = jk.create_job(job_name, "hudson.model.FreeStyleProject")
+    with clean_job(jb):
+        expected_jobs = ["MyCoolJob1", "MyCoolJob2"]
+        build_blocker = BuildBlockerProperty.create(expected_jobs)
+        jb.add_property(build_blocker)
+
+        # Get a fresh copy of our job to ensure we have an up to date
+        # copy of the config.xml for the job
+        async_assert(lambda: jk.find_job(job_name).properties)
+        properties = jk.find_job(job_name).properties
+
+        prop = properties[0]
+        assert prop.queue_scan == "DISABLED"
+
+
+def test_custom_queue_scan(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    job_name = "test_custom_queue_scan"
+    jb = jk.create_job(job_name, "hudson.model.FreeStyleProject")
+    with clean_job(jb):
+        expected_jobs = ["MyCoolJob1", "MyCoolJob2"]
+        expected_type = "ALL"
+        build_blocker = BuildBlockerProperty.create(expected_jobs)
+        build_blocker.queue_scan = expected_type
+        jb.add_property(build_blocker)
+
+        # Get a fresh copy of our job to ensure we have an up to date
+        # copy of the config.xml for the job
+        async_assert(lambda: jk.find_job(job_name).properties)
+        properties = jk.find_job(job_name).properties
+
+        prop = properties[0]
+        assert prop.queue_scan == expected_type
+
+
+def test_invalid_queue_scan_type():
+    expected_jobs = ["MyCoolJob1", "MyCoolJob2"]
+    build_blocker = BuildBlockerProperty.create(expected_jobs)
+    with pytest.raises(ValueError):
+        build_blocker.queue_scan = "FuBar"
+
+
+def test_default_block_level(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    job_name = "test_default_block_level"
+    jb = jk.create_job(job_name, "hudson.model.FreeStyleProject")
+    with clean_job(jb):
+        expected_jobs = ["MyCoolJob1", "MyCoolJob2"]
+        build_blocker = BuildBlockerProperty.create(expected_jobs)
+        jb.add_property(build_blocker)
+
+        # Get a fresh copy of our job to ensure we have an up to date
+        # copy of the config.xml for the job
+        async_assert(lambda: jk.find_job(job_name).properties)
+        properties = jk.find_job(job_name).properties
+
+        prop = properties[0]
+        assert prop.level == "GLOBAL"
+
+
+def test_custom_block_level(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    job_name = "test_custom_block_level"
+    jb = jk.create_job(job_name, "hudson.model.FreeStyleProject")
+    with clean_job(jb):
+        expected_jobs = ["MyCoolJob1", "MyCoolJob2"]
+        expected_type = "NODE"
+        build_blocker = BuildBlockerProperty.create(expected_jobs)
+        build_blocker.level = expected_type
+        jb.add_property(build_blocker)
+
+        # Get a fresh copy of our job to ensure we have an up to date
+        # copy of the config.xml for the job
+        async_assert(lambda: jk.find_job(job_name).properties)
+        properties = jk.find_job(job_name).properties
+
+        prop = properties[0]
+        assert prop.level == expected_type
+
+
+def test_invalid_block_level():
+    expected_jobs = ["MyCoolJob1", "MyCoolJob2"]
+    build_blocker = BuildBlockerProperty.create(expected_jobs)
+    with pytest.raises(ValueError):
+        build_blocker.level = "FuBar"
+
+
+def test_default_queue_scan(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    job_name = "test_default_queue_scan"
+    jb = jk.create_job(job_name, "hudson.model.FreeStyleProject")
+    with clean_job(jb):
+        expected_jobs = ["MyCoolJob1", "MyCoolJob2"]
+        build_blocker = BuildBlockerProperty.create(expected_jobs)
+        jb.add_property(build_blocker)
+
+        # Get a fresh copy of our job to ensure we have an up to date
+        # copy of the config.xml for the job
+        async_assert(lambda: jk.find_job(job_name).properties)
+        properties = jk.find_job(job_name).properties
+
+        prop = properties[0]
+        assert prop.queue_scan == "DISABLED"
+
+
+def test_disable_build_blocker(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    job_name = "test_disable_build_blocker"
+    jb = jk.create_job(job_name, "hudson.model.FreeStyleProject")
+    with clean_job(jb):
+        build_blocker = BuildBlockerProperty.create("MyJob")
+        build_blocker.disable()
+        jb.quiet_period = 0
+        jb.add_property(build_blocker)
+
+        # Get a fresh copy of our job to ensure we have an up to date
+        # copy of the config.xml for the job
+        async_assert(lambda: jk.find_job(job_name).properties)
+        properties = jk.find_job(job_name).properties
+
+        assert properties[0].is_enabled is False
+
+
+def test_build_blocker_functionality(jenkins_env):
+    jk = Jenkins(jenkins_env["url"], (jenkins_env["admin_user"], jenkins_env["admin_token"]))
+    job_name1 = "test_build_blocker_functionality1"
+    jb1 = jk.create_job(job_name1, "hudson.model.FreeStyleProject")
+    with clean_job(jb1):
+        job_name2 = "test_build_blocker_functionality2"
+        jb2 = jk.create_job(job_name2, "hudson.model.FreeStyleProject")
+        with clean_job(jb2):
+            expected_jobs = job_name2
+            build_blocker = BuildBlockerProperty.create(expected_jobs)
+            jb1.quiet_period = 0
+            jb1.add_property(build_blocker)
+
+            # Get a fresh copy of our job to ensure we have an up to date
+            # copy of the config.xml for the job
+            async_assert(lambda: jk.find_job(job_name1).properties)
+
+            build_step = ShellBuilder.create("sleep 10")
+            jb2.quiet_period = 0
+            jb2.add_builder(build_step)
+            async_assert(lambda: jb2.builders)
+            queue2 = jb2.start_build()
+
+            async_assert(lambda: not queue2.waiting)
+
+            queue1 = jb1.start_build()
+            assert job_name2 in queue1.reason
+
+            queue2.build.abort()
+            assert queue1.waiting is False
+
+
+if __name__ == "__main__":
+    pytest.main([__file__, "-v", "-s"])