Tests: reworked TestUnitHTTP.

This commit is contained in:
Andrey Zelenkov
2018-01-30 16:17:01 +03:00
parent 9f48f2b3e7
commit afa0fd9a71
3 changed files with 143 additions and 103 deletions

View File

@@ -52,14 +52,14 @@ def application(environ, start_response):
body = 'Test body string.' body = 'Test body string.'
r = unit.TestUnitHTTP.post(headers={ resp = self.post(headers={
'Host': 'localhost', 'Host': 'localhost',
'Content-Type': 'text/html', 'Content-Type': 'text/html',
'Custom-Header': 'blah' 'Custom-Header': 'blah'
}, data=body) }, body=body)
self.assertEqual(r.status_code, 200, 'status') self.assertEqual(resp['status'], 200, 'status')
headers = dict(r.headers) headers = resp['headers']
self.assertRegex(headers.pop('Server'), r'unit/[\d\.]+', self.assertRegex(headers.pop('Server'), r'unit/[\d\.]+',
'server header') 'server header')
self.assertDictEqual(headers, { self.assertDictEqual(headers, {
@@ -71,7 +71,7 @@ def application(environ, start_response):
'Server-Protocol': 'HTTP/1.1', 'Server-Protocol': 'HTTP/1.1',
'Custom-Header': 'blah' 'Custom-Header': 'blah'
}, 'headers') }, 'headers')
self.assertEqual(r.content, body.encode(), 'body') self.assertEqual(resp['body'], body, 'body')
def test_python_application_query_string(self): def test_python_application_query_string(self):
code, name = """ code, name = """
@@ -89,12 +89,10 @@ def application(environ, start_response):
self.python_application(name, code) self.python_application(name, code)
self.conf_with_name(name) self.conf_with_name(name)
r = unit.TestUnitHTTP.get(uri='/?var1=val1&var2=val2', headers={ resp = self.get(url='/?var1=val1&var2=val2')
'Host': 'localhost'
})
self.assertEqual(r.status_code, 200, 'status') self.assertEqual(resp['status'], 200, 'status')
headers = dict(r.headers) headers = resp['headers']
headers.pop('Server') headers.pop('Server')
self.assertDictEqual(headers, { self.assertDictEqual(headers, {
'Content-Length': '0', 'Content-Length': '0',
@@ -118,9 +116,7 @@ def application(environ, start_response):
self.python_application(name, code) self.python_application(name, code)
self.conf_with_name(name) self.conf_with_name(name)
r = unit.TestUnitHTTP.get(headers={'Host': 'localhost'}) self.assertEqual(self.get()['headers']['Server-Port'], '7080',
self.assertEqual(r.headers.pop('Server-Port'), '7080',
'Server-Port header') 'Server-Port header')
@unittest.expectedFailure @unittest.expectedFailure
@@ -137,8 +133,7 @@ def application(environ, start_response):
self.python_application(name, code) self.python_application(name, code)
self.conf_with_name(name) self.conf_with_name(name)
r = unit.TestUnitHTTP.get(headers={'Host': 'localhost'}) self.assertNotIn('Transfer-Encoding', self.get()['headers'],
self.assertNotIn('Transfer-Encoding', r.headers,
'204 header transfer encoding') '204 header transfer encoding')
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -44,7 +44,7 @@ def application(env, start_response):
} }
}) })
unit.TestUnitHTTP.get() self.get()
self.conf({ self.conf({
"listeners": {}, "listeners": {},

View File

@@ -5,9 +5,9 @@ import json
import time import time
import shutil import shutil
import socket import socket
import select
import tempfile import tempfile
import unittest import unittest
from requests import Request, Session
from subprocess import call from subprocess import call
from multiprocessing import Process from multiprocessing import Process
@@ -144,25 +144,59 @@ class TestUnit(unittest.TestCase):
return ret return ret
class TestUnitControl(TestUnit): class TestUnitHTTP(TestUnit):
# TODO socket reuse def http(self, start_str, **kwargs):
# TODO http client sock_type = 'ipv4' if 'sock_type' not in kwargs else kwargs['sock_type']
port = 7080 if 'port' not in kwargs else kwargs['port']
url = '/' if 'url' not in kwargs else kwargs['url']
http = 'HTTP/1.0' if 'http_10' in kwargs else 'HTTP/1.1'
headers = {'Host': 'localhost'} if 'headers' not in kwargs else kwargs['headers']
body = b'' if 'body' not in kwargs else kwargs['body']
crlf = '\r\n'
def conf(self, conf, path='/'): if 'addr' not in kwargs:
if isinstance(conf, dict): addr = '::1' if sock_type == 'ipv6' else '127.0.0.1'
conf = json.dumps(conf) else:
addr = kwargs['addr']
return self._body_json(self.put(path, conf)) sock_types = {
'ipv4': socket.AF_INET,
'ipv6': socket.AF_INET6,
'unix': socket.AF_UNIX
}
def conf_get(self, path='/'): if 'sock' not in kwargs:
return self._body_json(self.get(path)) sock = socket.socket(sock_types[sock_type], socket.SOCK_STREAM)
def conf_delete(self, path='/'): if sock_type == 'unix':
return self._body_json(self.delete(path)) sock.connect(addr)
else:
sock.connect((addr, port))
else:
sock = kwargs['sock']
sock.setblocking(False)
if 'raw' not in kwargs:
req = ' '.join([start_str, url, http]) + crlf
if body is not b'':
if isinstance(body, str):
body = body.encode()
if 'Content-Length' not in headers:
headers['Content-Length'] = len(body)
for header, value in headers.items():
req += header + ': ' + str(value) + crlf
req = (req + crlf).encode() + body
else:
req = start_str
def http(self, req):
with self._control_sock() as sock:
sock.sendall(req) sock.sendall(req)
if '--verbose' in sys.argv: if '--verbose' in sys.argv:
@@ -173,72 +207,83 @@ class TestUnitControl(TestUnit):
if '--verbose' in sys.argv: if '--verbose' in sys.argv:
print('<<<', resp, sep='\n') print('<<<', resp, sep='\n')
if 'raw_resp' not in kwargs:
resp = self._resp_to_dict(resp)
if 'start' not in kwargs:
sock.close()
return resp return resp
def get(self, path='/'): return (resp, sock)
resp = self.http(('GET ' + path
+ ' HTTP/1.1\r\nHost: localhost\r\n\r\n').encode())
return resp def delete(self, **kwargs):
return self.http('DELETE', **kwargs)
def delete(self, path='/'): def get(self, **kwargs):
resp = self.http(('DELETE ' + path return self.http('GET', **kwargs)
+ ' HTTP/1.1\r\nHost: localhost\r\n\r\n').encode())
return resp def post(self, **kwargs):
return self.http('POST', **kwargs)
def put(self, path='/', data=''): def put(self, **kwargs):
if isinstance(data, str): return self.http('PUT', **kwargs)
data = data.encode()
resp = self.http(('PUT ' + path + ' HTTP/1.1\nHost: localhost\n'
+ 'Content-Length: ' + str(len(data))
+ '\r\n\r\n').encode() + data)
return resp
def _control_sock(self):
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
sock.connect(self.testdir + '/control.unit.sock')
return sock
def _recvall(self, sock, buff_size=4096): def _recvall(self, sock, buff_size=4096):
data = '' data = ''
while True: while select.select([sock], [], [], 1)[0]:
part = sock.recv(buff_size).decode() part = sock.recv(buff_size).decode()
data += part data += part
if len(part) < buff_size: if part is '':
break break
return data return data
def _body_json(self, resp): def _resp_to_dict(self, resp):
m = re.search('.*?\x0d\x0a?\x0d\x0a?(.*)', resp, re.M | re.S) m = re.search('(.*?\x0d\x0a?)\x0d\x0a?(.*)', resp, re.M | re.S)
return json.loads(m.group(1)) headers_text, body = m.group(1), m.group(2)
class TestUnitHTTP(): p = re.compile('(.*?)\x0d\x0a?', re.M | re.S)
headers_lines = p.findall(headers_text)
@classmethod status = re.search('^HTTP\/\d\.\d\s(\d+)|$', headers_lines.pop(0)).group(1)
def http(self, method, **kwargs):
host = '127.0.0.1:7080' if 'host' not in kwargs else kwargs['host']
uri = '/' if 'uri' not in kwargs else kwargs['uri']
sess = Session() if 'sess' not in kwargs else kwargs['sess']
data = None if 'data' not in kwargs else kwargs['data']
headers = None if 'headers' not in kwargs else kwargs['headers']
req = Request(method, 'http://' + host + uri, data=data, headers = {}
headers=headers) for line in headers_lines:
m = re.search('(.*)\:\s(.*)', line)
headers[m.group(1)] = m.group(2)
r = sess.send(req.prepare()) return {
'status': int(status),
'headers': headers,
'body': body
}
if 'keep' not in kwargs: class TestUnitControl(TestUnitHTTP):
sess.close()
return r
return (r, sess) # TODO socket reuse
# TODO http client
def get(**kwargs): def conf(self, conf, path='/'):
return TestUnitHTTP.http('GET', **kwargs) if isinstance(conf, dict):
conf = json.dumps(conf)
def post(**kwargs): return json.loads(self.put(
return TestUnitHTTP.http('POST', **kwargs) url=path,
body=conf,
sock_type='unix',
addr=self.testdir + '/control.unit.sock'
)['body'])
def conf_get(self, path='/'):
return json.loads(self.get(
url=path,
sock_type='unix',
addr=self.testdir + '/control.unit.sock'
)['body'])
def conf_delete(self, path='/'):
return json.loads(self.delete(
url=path,
sock_type='unix',
addr=self.testdir + '/control.unit.sock'
)['body'])