Skip to content

Commit

Permalink
Merge pull request #2 from tomislater/fix-tests
Browse files Browse the repository at this point in the history
Fix tests and HTTPCoordinatedExecutor class
  • Loading branch information
pwilczynskiclearcode committed Dec 9, 2013
2 parents 1669b72 + b010c97 commit f06e202
Show file tree
Hide file tree
Showing 8 changed files with 151 additions and 19 deletions.
2 changes: 1 addition & 1 deletion summon_process/executors/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from .simple_executor import SimpleExecutor
from .simple_executor import SimpleExecutor, TimeoutExpired
from .output_coordinated_executor import OutputCoordinatedExecutor
from .tcp_coordinated_executor import TCPCoordinatedExecutor
from .http_coordinated_executor import HTTPCoordinatedExecutor
12 changes: 6 additions & 6 deletions summon_process/executors/http_coordinated_executor.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,24 @@
import socket
import httplib
import urlparse
from . import TCPCoordinatedExecutor


class HTTPCoordinatedExecutor(TCPCoordinatedExecutor):
def __init__(self, command, url, shell=False):
def __init__(self, command, url, shell=False, timeout=None):
self._url = urlparse.urlparse(url)
TCPCoordinatedExecutor.__init__(self, command, host=self._url.hostname,
port=self._url.port, shell=shell)
port=self._url.port, shell=shell, timeout=timeout)

def start(self):
TCPCoordinatedExecutor.start(self)
self._wait_for_successful_head()

def _wait_for_successful_head(self):
while True:
while self.check_timeout():
try:
conn = httplib.HTTPConnection(self._url.hostname,
self._url.port,
timeout=1)
self._url.port)

conn.request('HEAD', self._url.path)
response = conn.getresponse()
Expand All @@ -27,5 +27,5 @@ def _wait_for_successful_head(self):
conn.close()
break

except httplib.HTTPException:
except (httplib.HTTPException, socket.timeout):
continue
6 changes: 3 additions & 3 deletions summon_process/executors/output_coordinated_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@


class OutputCoordinatedExecutor(SimpleExecutor):
def __init__(self, command, banner, shell=False):
SimpleExecutor.__init__(self, command, shell)
def __init__(self, command, banner, shell=False, timeout=None):
SimpleExecutor.__init__(self, command, shell, timeout)
self._banner = re.compile(banner)

def start(self):
SimpleExecutor.start(self)
self._wait_for_output()

def _wait_for_output(self):
while True:
while self.check_timeout():
if self._banner.match(self.output().readline()):
break
45 changes: 44 additions & 1 deletion summon_process/executors/simple_executor.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,26 @@
import subprocess
import shlex
import time


class TimeoutExpired(Exception):
"""This exception is raised when the timeout expires while starting
an executor.
"""
def __init__(self, executor, timeout):
self.executor = executor
self.timeout = timeout

def __str__(self):
return ("Executor %s timed out after %s seconds" % (self.executor, self.timeout))


class SimpleExecutor:
def __init__(self, command, shell=False):
def __init__(self, command, shell=False, timeout=None):
self._args = shlex.split(command)
self._shell = shell
self._timeout = timeout
self._endtime = None
self._process = None

def running(self):
Expand All @@ -20,12 +35,40 @@ def start(self):
shell=self._shell,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE)
if self._timeout:
self._endtime = time.time() + self._timeout

def stop(self):
if self._process is not None:
self._process.terminate()
self._process = None
self._endtime = None

def kill(self, wait_for_exit=False):
"""Kill the process with SIGKILL
:param wait_for_exit: set to `True` to wait for the process to end.
"""
if self.running():
self._process.kill()
if wait_for_exit:
self._process.wait()
self._process = None
self._endtime = None

def output(self):
if self._process is not None:
return self._process.stdout

def check_timeout(self):
"""Check if timeout has expired.
Returns True if there is no timeout set or the timeout has not yet expired.
Kills the process and raises TimeoutExpired exception otherwise.
This method should be used in while loops waiting for some data.
"""
if self._endtime is not None and time.time() > self._endtime:
self.kill()
raise TimeoutExpired(self.__class__.__name__, timeout=self._timeout)
return True
6 changes: 3 additions & 3 deletions summon_process/executors/tcp_coordinated_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@


class TCPCoordinatedExecutor(SimpleExecutor):
def __init__(self, command, host, port, shell=False):
SimpleExecutor.__init__(self, command, shell=shell)
def __init__(self, command, host, port, shell=False, timeout=None):
SimpleExecutor.__init__(self, command, shell=shell, timeout=timeout)
self._host = host
self._port = port

Expand All @@ -14,7 +14,7 @@ def start(self):
self._wait_for_connection()

def _wait_for_connection(self):
while True:
while self.check_timeout():
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((self._host, self._port))
Expand Down
52 changes: 48 additions & 4 deletions summon_process/tests/executors/test_http_coordinated_executor.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,62 @@
import os

from unittest import TestCase
from httplib import HTTPConnection, OK
from summon_process.executors import HTTPCoordinatedExecutor

from summon_process.executors import HTTPCoordinatedExecutor, TimeoutExpired


class TestHTTPCoordinatedExecutor(TestCase):
host = "127.0.0.1"
port = "8000"

def test_it_waits_for_process_to_complete_head_request(self):
command = 'bash -c "sleep 3 && python -m SimpleHTTPServer"'
executor = HTTPCoordinatedExecutor(command, 'http://127.0.0.1:8000/')
command = 'bash -c "sleep 3 && exec python -m SimpleHTTPServer"'
executor = HTTPCoordinatedExecutor(
command, 'http://{0}:{1}/'.format(self.host, self.port)
)
executor.start()
assert executor.running()

conn = HTTPConnection(self.host, self.port)
conn.request('GET', '/')
assert conn.getresponse().status is OK
conn.close()

executor.stop()

def prepare_slow_server_executor(self, timeout=None):
slow_server = os.path.join(
os.path.abspath(os.path.dirname(__file__)),
"../slow_server.py"
)

command = 'python {0}'.format(slow_server)

return HTTPCoordinatedExecutor(
command,
'http://{0}:{1}/'.format(self.host, self.port),
timeout=timeout,
)

def test_slow_server_response(self):
"""
Simple example. You run gunicorn, gunicorn is working
but you have to wait for worker procesess.
"""
executor = self.prepare_slow_server_executor()
executor.start()
assert executor.running()

conn = HTTPConnection('127.0.0.1', '8000')
conn = HTTPConnection(self.host, self.port)
conn.request('GET', '/')

assert conn.getresponse().status is OK

conn.close()
executor.stop()

def test_slow_server_response_with_timeout(self):
executor = self.prepare_slow_server_executor(timeout=1)
self.assertRaises(TimeoutExpired, executor.start)
executor.stop()
16 changes: 15 additions & 1 deletion summon_process/tests/executors/test_tcp_coordinated_executor.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from unittest import TestCase
from summon_process.executors import TCPCoordinatedExecutor
from summon_process.executors import TCPCoordinatedExecutor, TimeoutExpired


class TestTCPCoordinatedExecutor(TestCase):
Expand All @@ -10,3 +10,17 @@ def test_it_waits_for_process_to_bind_at_given_port(self):

assert executor.running()
executor.stop()

def test_it_raises_error_on_timeout(self):
command = 'bash -c "sleep 10 && nc -l 3000"'
executor = TCPCoordinatedExecutor(command, host='localhost', port=3000, timeout=2)

self.assertRaises(TimeoutExpired, executor.start)
executor.stop()

def test_it_starts_up_without_raising_timeout_error(self):
command = 'bash -c "sleep 2 && nc -l 3000"'
executor = TCPCoordinatedExecutor(command, host='localhost', port=3000, timeout=5)

executor.start()
executor.stop()
31 changes: 31 additions & 0 deletions summon_process/tests/slow_server.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import time

from BaseHTTPServer import (
HTTPServer,
BaseHTTPRequestHandler,
)

class SlowServerHandler(BaseHTTPRequestHandler):

wait = 5

def do_GET(self):
time.sleep(self.wait)
self.send_response(200)
self.send_header("Content-type", "text/html")
self.end_headers()
self.wfile.write('Hi. I am very slow.')
return

def do_HEAD(self):
time.sleep(self.wait)
self.send_response(200)
self.end_headers()
return


server = HTTPServer(
('127.0.0.1', 8000),
SlowServerHandler
)
server.serve_forever()

0 comments on commit f06e202

Please sign in to comment.