From 0a1b414f3acfe888eb7bb3d47de2259703cfaa81 Mon Sep 17 00:00:00 2001 From: Darragh Bailey Date: Wed, 8 Jul 2015 13:10:49 +0100 Subject: [PATCH] Add socket timeout tests and helpers Add tests that use a simple socket server instance that opens the port and ignores all data in order to support testing when the socket timeout is set and catching the exception triggered. Also test that when not set that the request will continue to remain connected for a period of time longer than the other test using a wrapper time limit process. Includes some helper classes to black hole all requests by simply listening using a socketserver but never accepting connections, and a time limit function to allow wrapping actions with a time limit when checking for blocking behaviour where the call should not return and will need to be focibly terminated. Change-Id: Idab98b0121adb26aed6320ddcf2afbdf2c4428e0 --- jenkins/__init__.py | 10 +++++++++ requirements.txt | 2 +- tests/helper.py | 33 +++++++++++++++++++++++++++++ tests/test_jenkins_sockets.py | 39 +++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 tests/helper.py create mode 100644 tests/test_jenkins_sockets.py diff --git a/jenkins/__init__.py b/jenkins/__init__.py index e02bfbb..e6e93ff 100644 --- a/jenkins/__init__.py +++ b/jenkins/__init__.py @@ -181,6 +181,10 @@ class BadHTTPException(JenkinsException): pass +class TimeoutException(JenkinsException): + '''A special exception to call out in the case of a socket timeout.''' + + def auth_headers(username, password): '''Simple implementation of HTTP Basic Authentication. @@ -333,7 +337,13 @@ class Jenkins(object): raise NotFoundException('Requested item could not be found') else: raise + except socket.timeout as e: + raise TimeoutException('Error in request: %s' % (e)) except URLError as e: + # python 2.6 compatibility to ensure same exception raised + # since URLError wraps a socket timeout on python 2.6. + if str(e.reason) == "timed out": + raise TimeoutException('Error in request: %s' % (e.reason)) raise JenkinsException('Error in request: %s' % (e.reason)) def get_build_info(self, name, number, depth=0): diff --git a/requirements.txt b/requirements.txt index d2deb98..1b08df6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -six +six>=1.3.0 pbr>=0.8.2,<2.0 diff --git a/tests/helper.py b/tests/helper.py new file mode 100644 index 0000000..1068cf0 --- /dev/null +++ b/tests/helper.py @@ -0,0 +1,33 @@ +from multiprocessing import Process + +from six.moves import socketserver + + +class TestsTimeoutException(Exception): + pass + + +def time_limit(seconds, func, *args, **kwargs): + # although creating a separate process is expensive it's the only way to + # ensure cross platform that we can cleanly terminate after timeout + p = Process(target=func, args=args, kwargs=kwargs) + p.start() + p.join(seconds) + p.terminate() + if p.exitcode is None: + raise TestsTimeoutException + + +class NullServer(socketserver.TCPServer): + + request_queue_size = 1 + + def __init__(self, server_address, *args, **kwargs): + # TCPServer is old style in python 2.x so cannot use + # super() correctly, explicitly call __init__. + + # simply init'ing is sufficient to open the port, which + # with the server not started creates a black hole server + socketserver.TCPServer.__init__( + self, server_address, socketserver.BaseRequestHandler, + *args, **kwargs) diff --git a/tests/test_jenkins_sockets.py b/tests/test_jenkins_sockets.py new file mode 100644 index 0000000..d8fc945 --- /dev/null +++ b/tests/test_jenkins_sockets.py @@ -0,0 +1,39 @@ +import sys + +import jenkins +from tests.helper import NullServer +from tests.helper import TestsTimeoutException +from tests.helper import time_limit + +if sys.version_info < (2, 7): + import unittest2 as unittest +else: + import unittest + + +class JenkinsRequestTimeoutTests(unittest.TestCase): + + def setUp(self): + super(JenkinsRequestTimeoutTests, self).setUp() + self.server = NullServer(("127.0.0.1", 0)) + + def test_jenkins_open_timeout(self): + j = jenkins.Jenkins("http://%s:%s" % self.server.server_address, + None, None, timeout=0.1) + request = jenkins.Request('http://%s:%s/job/TestJob' % + self.server.server_address) + + # assert our request times out when no response + with self.assertRaises(jenkins.TimeoutException): + j.jenkins_open(request, add_crumb=False) + + def test_jenkins_open_no_timeout(self): + j = jenkins.Jenkins("http://%s:%s" % self.server.server_address, + None, None) + request = jenkins.Request('http://%s:%s/job/TestJob' % + self.server.server_address) + + # assert we don't timeout quickly like previous test when + # no timeout defined. + with self.assertRaises(TestsTimeoutException): + time_limit(0.5, j.jenkins_open, request, add_crumb=False)