Changed job and build modules to use tree api
authorAleksey Maksimov <ctpeko3a@gmail.com>
Fri, 12 Sep 2014 15:10:21 +0000 (23:10 +0800)
committerAleksey Maksimov <ctpeko3a@gmail.com>
Fri, 19 Sep 2014 13:20:31 +0000 (21:20 +0800)
jenkinsapi/build.py
jenkinsapi/jenkinsbase.py
jenkinsapi/job.py
jenkinsapi_tests/systests/test_invocation.py
jenkinsapi_tests/unittests/test_build.py
jenkinsapi_tests/unittests/test_jenkins.py
jenkinsapi_tests/unittests/test_job.py
jenkinsapi_tests/unittests/test_job_get_all_builds.py

index 6fc94100e025810adce5d6c56cfc7101b6a92ae4..0ab0abe50a5b3606f35ef8099f4dcf755a789bc4 100644 (file)
@@ -52,8 +52,8 @@ class Build(JenkinsBase):
     def _poll(self, tree=None):
         # For build's we need more information for downstream and upstream builds
         # so we override the poll to get at the extra data for build objects
-        url = '%s?depth=%s' % (self.python_api_url(self.baseurl), self.depth)
-        return self.get_data(url, tree=tree)
+        url = self.python_api_url(self.baseurl)
+        return self.get_data(url, params={'depth': self.depth}, tree=tree)
 
     def __str__(self):
         return self._data['fullDisplayName']
@@ -116,7 +116,8 @@ class Build(JenkinsBase):
         return datetime.timedelta(milliseconds=self._data["duration"])
 
     def get_artifacts(self):
-        for afinfo in self._data["artifacts"]:
+        data = self.poll(tree='artifacts[relativePath,fileName]')
+        for afinfo in data["artifacts"]:
             url = "%s/artifact/%s" % (self.baseurl, afinfo["relativePath"])
             af = Artifact(afinfo["fileName"], url, self)
             yield af
@@ -277,8 +278,8 @@ class Build(JenkinsBase):
         """
         Return a bool if running.
         """
-        self.poll()
-        return self._data["building"]
+        data = self.poll(tree='building')
+        return data.get('building', False)
 
     def block(self):
         while self.is_running():
index aa7897d739bed42befa055f8326ed6bd4c727de8..d7d23a6eb7b06331841ed7ef4078b912e9ce44aa 100644 (file)
@@ -54,10 +54,13 @@ class JenkinsBase(object):
         return url
 
     def poll(self, tree=None):
-        self._data = self._poll(tree=tree)
-        if 'jobs' in self._data:
-            self._data['jobs'] = self.resolve_job_folders(self._data['jobs'])
-        return self
+        data = self._poll(tree=tree)
+        if 'jobs' in data:
+            data['jobs'] = self.resolve_job_folders(data['jobs'])
+        if not tree:
+            self._data = data
+        else:
+            return data
 
     def _poll(self, tree=None):
         url = self.python_api_url(self.baseurl)
@@ -69,7 +72,7 @@ class JenkinsBase(object):
             if not params:
                 params = {'tree': tree}
             else:
-                params.update({'tree', tree})
+                params.update({'tree': tree})
 
         response = requester.get_url(url, params)
         if response.status_code != 200:
index 2c2da5a4ef4c2b0eaeaef52cc9faf7136b3d159a..31f3222ef44848d42ef8dd4339909eb0ab3a92a1 100644 (file)
@@ -73,7 +73,7 @@ class Job(JenkinsBase, MutableJenkinsThing):
         JenkinsBase.__init__(self, url)
 
     def __str__(self):
-        return self._data["name"]
+        return self.name
 
     def get_description(self):
         return self._data["description"]
@@ -92,11 +92,12 @@ class Job(JenkinsBase, MutableJenkinsThing):
             branches.append(hg_default_branch)
         return branches
 
-    def _poll(self, tree=None):
-        data = JenkinsBase._poll(self, tree=tree)
-        # jenkins loads only the first 100 builds, load more if needed
-        data = self._add_missing_builds(data)
-        return data
+    def poll(self, tree=None):
+        data = super(Job, self).poll(tree=tree)
+        if not tree:
+            self._data = self._add_missing_builds(self._data)
+        else:
+            return data
 
     # pylint: disable=E1123
     # Unexpected keyword arg 'params'
@@ -111,16 +112,14 @@ class Job(JenkinsBase, MutableJenkinsThing):
         # do not call _buildid_for_type here: it would poll and do an infinite
         # loop
         oldest_loaded_build_number = data["builds"][-1]["number"]
-        if not data['firstBuild']:
+        if not self._data['firstBuild']:
             first_build_number = oldest_loaded_build_number
         else:
-            first_build_number = data["firstBuild"]["number"]
+            first_build_number = self._data["firstBuild"]["number"]
         all_builds_loaded = (oldest_loaded_build_number == first_build_number)
         if all_builds_loaded:
             return data
-        api_url = self.python_api_url(self.baseurl)
-        response = self.get_data(
-            api_url, params={'tree': 'allBuilds[number,url]'})
+        response = self.poll(tree='allBuilds[number,url]')
         data['builds'] = response['allBuilds']
         return data
 
@@ -219,7 +218,6 @@ class Job(JenkinsBase, MutableJenkinsThing):
 
     def _buildid_for_type(self, buildtype):
         """Gets a buildid for a given type of build"""
-        self.poll()
         KNOWNBUILDTYPES = [
             "lastStableBuild",
             "lastSuccessfulBuild",
@@ -229,9 +227,11 @@ class Job(JenkinsBase, MutableJenkinsThing):
             "lastFailedBuild"]
         assert buildtype in KNOWNBUILDTYPES, 'Unknown build info type: %s' % buildtype
 
-        if not self._data.get(buildtype):
+        data = self.poll(tree='%s[number]' % buildtype)
+
+        if not data.get(buildtype):
             raise NoBuildData(buildtype)
-        return self._data[buildtype]["number"]
+        return data[buildtype]["number"]
 
     def get_first_buildnumber(self):
         """
@@ -270,10 +270,12 @@ class Job(JenkinsBase, MutableJenkinsThing):
         return self._buildid_for_type("lastCompletedBuild")
 
     def get_build_dict(self):
-        if "builds" not in self._data:
+        builds = self.poll(tree='builds[number,url]')
+        if not builds:
             raise NoBuildData(repr(self))
-        builds = self._data["builds"]
-        last_build = self._data['lastBuild']
+        builds = self._add_missing_builds(builds)
+        builds = builds['builds']
+        last_build = self.poll(tree='lastBuild[number,url]')['lastBuild']
         if builds and last_build and builds[0]['number'] != last_build['number']:
             builds = [last_build] + builds
         # FIXME SO how is this supposed to work if build is false-y?
@@ -387,8 +389,8 @@ class Job(JenkinsBase, MutableJenkinsThing):
         return self.is_queued() or self.is_running()
 
     def is_queued(self):
-        self.poll()
-        return self._data["inQueue"]
+        data = self.poll(tree='inQueue')
+        return data.get('inQueue', False)
 
     def get_queue_item(self):
         """
@@ -399,7 +401,7 @@ class Job(JenkinsBase, MutableJenkinsThing):
         return QueueItem(self.jenkins, **self._data['queueItem'])
 
     def is_running(self):
-        self.poll()
+        self.poll()
         try:
             build = self.get_last_build_or_none()
             if build is not None:
@@ -568,8 +570,8 @@ class Job(JenkinsBase, MutableJenkinsThing):
         return upstream_jobs
 
     def is_enabled(self):
-        self.poll()
-        return self._data["color"] != 'disabled'
+        data = self.poll(tree='color')
+        return data.get('color', None) != 'disabled'
 
     def disable(self):
         '''Disable job'''
index 78c3692b86b4cc786fa7a90026d50912e32c3ae8..a545a905d4aa174ba754cda354f6a869f33a2ba2 100644 (file)
@@ -42,8 +42,8 @@ class TestInvocation(BaseSystemTest):
         b = qq.get_build()
         self.assertIsInstance(b, Build)
         self.assertTrue(b.is_running())
-        
-        b.stop()
+        # if we call next line right away - Jenkins have no time to stop job
+        # so we wait a bit
         time.sleep(1)
         self.assertFalse(b.poll().is_running())
         console = b.get_console()
index 055e08c59676aee88e97e6e40b15fd285fcd5680..d7cf7282a7b8c8848f7d092ec4296c6bd55f1215 100644 (file)
@@ -83,8 +83,10 @@ class test_build(unittest.TestCase):
 
     @mock.patch.object(Build, 'get_data')
     def test_build_depth(self, get_data_mock):
-        build = Build('http://halob:8080/job/foo/98', 98, self.j, depth=0)
-        get_data_mock.assert_called_with('http://halob:8080/job/foo/98/api/python?depth=0', tree=None)
+        Build('http://halob:8080/job/foo/98', 98, self.j, depth=0)
+        get_data_mock.assert_called_with('http://halob:8080/job/foo/98/api/'
+                                         'python',
+                                         tree=None, params={'depth': 0})
 
     def test_get_revision_no_scm(self):
         """ with no scm, get_revision should return None """
@@ -95,6 +97,7 @@ class test_build(unittest.TestCase):
     #     expected = ['SingleJob','MultipleJobs']
     #     self.assertEquals(self.b.get_downstream_job_names(), expected)
 
+
 def main():
     unittest.main(verbosity=2)
 
index 6c0b279c7774dbe2955ec27eec95bd81835a0f29..1f2a74bc1c06e2fc13883a60e0a233502063aaf2 100644 (file)
@@ -238,9 +238,12 @@ class TestJenkins(unittest.TestCase):
     def second_call_poll(tree=None):
         return TestJenkins.create_job_returns.pop(0)
 
+    def job_second_call_poll(tree=None):
+        return {}
+
     # Patch Jenkins with mock function
     @mock.patch.object(Jenkins, '_poll', side_effect=second_call_poll)
-    @mock.patch.object(Job, '_poll')
+    @mock.patch.object(Job, '_poll', side_effect=job_second_call_poll)
     def test_create_new_job(self, _poll, _job_poll):
         _job_poll.return_value = {}
 
index aaa29d4b4bb89b147f60afce91a21b8471455e4c..8096316ddfb2bed9e5318c1fe4b2a35b8e4776b3 100644 (file)
@@ -91,6 +91,18 @@ class TestJob(unittest.TestCase):
         except KeyError:
             raise Exception("Missing data for %s" % url)
 
+    def fakeGetDataTree(self, url, **args):
+        try:
+            if 'builds' in args['tree']:
+                return {'builds': TestJob.URL_DATA[url]['builds']}
+            else:
+                return {'lastBuild': TestJob.URL_DATA[url]['lastBuild']}
+        except KeyError:
+            raise Exception("Missing data for %s" % url)
+
+    def fake_get_data_tree_empty(self, url, **args):
+        return {}
+
     @mock.patch.object(JenkinsBase, 'get_data', fakeGetData)
     def setUp(self):
 
@@ -162,20 +174,19 @@ class TestJob(unittest.TestCase):
         ret = self.j.get_last_completed_buildnumber()
         self.assertEquals(ret, 3)
 
+    @mock.patch.object(JenkinsBase, 'get_data', fakeGetDataTree)
     def test_get_build_dict(self):
         ret = self.j.get_build_dict()
         self.assertTrue(isinstance(ret, dict))
         self.assertEquals(len(ret), 4)
 
-    @mock.patch.object(Job, '_poll')
-    def test_nobuilds_get_build_dict(self, _poll):
-        # Bare minimum build dict, we only testing dissapearance of 'builds'
-        _poll.return_value = {"name": "foo"}
-
+    @mock.patch.object(JenkinsBase, 'get_data', fake_get_data_tree_empty)
+    def test_nobuilds_get_build_dict(self):
         j = Job('http://halob:8080/job/foo/', 'foo', self.J)
         with self.assertRaises(NoBuildData):
             j.get_build_dict()
 
+    @mock.patch.object(JenkinsBase, 'get_data', fakeGetDataTree)
     def test_get_build_ids(self):
         # We don't want to deal with listreverseiterator here
         # So we convert result to a list
@@ -264,6 +275,8 @@ class TestJob(unittest.TestCase):
         self.assertEquals(len(params), 2)
         self.assertEquals(params, ['param1', 'param2'])
 
+    @mock.patch.object(JenkinsBase, 'get_data', fakeGetDataTree)
+    # @mock.patch.object(JenkinsBase, 'get_data', fakeGetLastBuild)
     def test_get_build(self):
         buildnumber = 1
         with mock.patch('jenkinsapi.job.Build') as build_mock:
@@ -273,6 +286,7 @@ class TestJob(unittest.TestCase):
             build_mock.assert_called_with('http://halob:8080/job/foo/1/',
                                           buildnumber, job=self.j)
 
+    @mock.patch.object(JenkinsBase, 'get_data', fakeGetDataTree)
     def test_get_build_metadata(self):
         buildnumber = 1
         with mock.patch('jenkinsapi.job.Build') as build_mock:
index 39cfa51b92571f6dd074bd9b4550143ba8980702..3fee88b1ac5d980dfde06f315ff0106a95f0e92d 100644 (file)
@@ -130,11 +130,11 @@ class TestJobGetAllBuilds(unittest.TestCase):
 
     URL_DATA = {
         JOB1_API_URL: JOB1_DATA,
-        (JOB1_API_URL, str({'tree': 'allBuilds[number,url]'})): JOB1_ALL_BUILDS_DATA,
+        (JOB1_API_URL, 'allBuilds[number,url]'): JOB1_ALL_BUILDS_DATA,
         JOB2_API_URL: JOB2_DATA,
         JOB3_API_URL: JOB3_DATA,
         # this one below should never be used
-        (JOB3_API_URL, str({'tree': 'allBuilds[number,url]'})): JOB3_ALL_BUILDS_DATA,
+        (JOB3_API_URL, 'allBuilds[number,url]'): JOB3_ALL_BUILDS_DATA,
     }
 
     def fakeGetData(self, url, params=None, tree=None):
@@ -150,12 +150,28 @@ class TestJobGetAllBuilds(unittest.TestCase):
             except KeyError:
                 raise Exception("Missing data for url: %s with parameters %s" % (url, repr(params)))
 
-    @mock.patch.object(JenkinsBase, 'get_data', fakeGetData)
+    def fakeGetDataTree(self, url, **args):
+        TestJobGetAllBuilds.__get_data_call_count += 1
+        try:
+            if args['tree']:
+                if 'builds' in args['tree']:
+                    return {'builds': TestJobGetAllBuilds.URL_DATA[url]['builds']}
+                elif 'allBuilds' in args['tree']:
+                    return TestJobGetAllBuilds.URL_DATA[(url, args['tree'])]
+                elif 'lastBuild' in args['tree']:
+                    return {'lastBuild': TestJobGetAllBuilds.URL_DATA[url]['lastBuild']}
+            else:
+                return dict(TestJobGetAllBuilds.URL_DATA[url])
+        except KeyError:
+            raise Exception("Missing data for %s" % url)
+
+    @mock.patch.object(JenkinsBase, 'get_data', fakeGetDataTree)
     def setUp(self):
         TestJobGetAllBuilds.__get_data_call_count = 0
         self.J = mock.MagicMock()  # Jenkins object
         self.j = Job('http://halob:8080/job/foo/', 'foo', self.J)
 
+    @mock.patch.object(JenkinsBase, 'get_data', fakeGetDataTree)
     def test_get_build_dict(self):
         # The job data contains only one build, so we expect that the
         # remaining jobs will be fetched automatically
@@ -163,7 +179,7 @@ class TestJobGetAllBuilds(unittest.TestCase):
         self.assertTrue(isinstance(ret, dict))
         self.assertEquals(len(ret), 4)
 
-    @mock.patch.object(JenkinsBase, 'get_data', fakeGetData)
+    @mock.patch.object(JenkinsBase, 'get_data', fakeGetDataTree)
     def test_incomplete_builds_list_will_call_jenkins_twice(self):
         # The job data contains only one build, so we expect that the
         # remaining jobs will be fetched automatically, and to have two calls to
@@ -172,14 +188,14 @@ class TestJobGetAllBuilds(unittest.TestCase):
         self.j = Job('http://halob:8080/job/foo/', 'foo', self.J)
         self.assertEquals(TestJobGetAllBuilds.__get_data_call_count, 2)
 
-    @mock.patch.object(JenkinsBase, 'get_data', fakeGetData)
+    @mock.patch.object(JenkinsBase, 'get_data', fakeGetDataTree)
     def test_complete_builds_list_will_call_jenkins_once(self):
         # The job data contains all builds, so we will not gather remaining builds
         TestJobGetAllBuilds.__get_data_call_count = 0
         self.j = Job('http://halob:8080/job/fullfoo/', 'fullfoo', self.J)
         self.assertEquals(TestJobGetAllBuilds.__get_data_call_count, 1)
 
-    @mock.patch.object(JenkinsBase, 'get_data', fakeGetData)
+    @mock.patch.object(JenkinsBase, 'get_data', fakeGetDataTree)
     def test_nobuilds_get_build_dict(self):
         j = Job('http://halob:8080/job/look_ma_no_builds/', 'look_ma_no_builds', self.J)
 
@@ -187,6 +203,7 @@ class TestJobGetAllBuilds(unittest.TestCase):
         self.assertTrue(isinstance(ret, dict))
         self.assertEquals(len(ret), 0)
 
+    @mock.patch.object(JenkinsBase, 'get_data', fakeGetDataTree)
     def test_get_build_ids(self):
         # The job data contains only one build, so we expect that the
         # remaining jobs will be fetched automatically