diff options
author | Bert JW Regeer <bertjw@regeer.org> | 2020-08-15 22:29:01 -0700 |
---|---|---|
committer | Bert JW Regeer <bertjw@regeer.org> | 2020-08-16 16:49:06 -0700 |
commit | d347823b8eb5bdc9ccecc440168032658bd752fa (patch) | |
tree | 4a7bd6794c82a2e5b59c024e16020432fefca2d4 | |
parent | 1b4bcce97cceaae588b5508d42308f13be926ce2 (diff) | |
download | waitress-d347823b8eb5bdc9ccecc440168032658bd752fa.tar.gz |
Reduce compat.py to minimum size
-rw-r--r-- | src/waitress/adjustments.py | 4 | ||||
-rw-r--r-- | src/waitress/compat.py | 36 | ||||
-rw-r--r-- | src/waitress/parser.py | 52 | ||||
-rw-r--r-- | src/waitress/rfc7230.py | 6 | ||||
-rw-r--r-- | src/waitress/task.py | 7 | ||||
-rw-r--r-- | tests/test_compat.py | 14 | ||||
-rw-r--r-- | tests/test_functional.py | 448 | ||||
-rw-r--r-- | tests/test_parser.py | 104 | ||||
-rw-r--r-- | tests/test_proxy_headers.py | 4 | ||||
-rw-r--r-- | tests/test_runner.py | 4 | ||||
-rw-r--r-- | tests/test_wasyncore.py | 48 |
11 files changed, 337 insertions, 390 deletions
diff --git a/src/waitress/adjustments.py b/src/waitress/adjustments.py index f121b6e..145ac86 100644 --- a/src/waitress/adjustments.py +++ b/src/waitress/adjustments.py @@ -17,7 +17,7 @@ import getopt import socket import warnings -from .compat import HAS_IPV6, WIN, string_types +from .compat import HAS_IPV6, WIN from .proxy_headers import PROXY_HEADERS truthy = frozenset(("t", "true", "y", "yes", "on", "1")) @@ -47,7 +47,7 @@ def asoctal(s): def aslist_cronly(value): - if isinstance(value, string_types): + if isinstance(value, str): value = filter(None, [x.strip() for x in value.splitlines()]) return list(value) diff --git a/src/waitress/compat.py b/src/waitress/compat.py index 7c2630c..67543b9 100644 --- a/src/waitress/compat.py +++ b/src/waitress/compat.py @@ -1,50 +1,14 @@ -import _thread as thread -from http import client as httplib -from io import StringIO as NativeIO -import os import platform # Fix for issue reported in https://github.com/Pylons/waitress/issues/138, # Python on Windows may not define IPPROTO_IPV6 in socket. import socket import sys -from urllib import parse as urlparse -from urllib.parse import unquote_to_bytes import warnings # True if we are running on Windows WIN = platform.system() == "Windows" -string_types = (str,) -integer_types = (int,) -class_types = (type,) -text_type = str -binary_type = bytes -long = int - - -def unquote_bytes_to_wsgi(bytestring): - return unquote_to_bytes(bytestring).decode("latin-1") - - -def text_(s, encoding="latin-1", errors="strict"): - """ If ``s`` is an instance of ``binary_type``, return - ``s.decode(encoding, errors)``, otherwise return ``s``""" - - if isinstance(s, binary_type): - return s.decode(encoding, errors) - - return s # pragma: no cover - - -def tostr(s): - return str(s, "latin-1", "strict") - - -def tobytes(s): - return bytes(s, "latin-1") - - MAXINT = sys.maxsize HAS_IPV6 = socket.has_ipv6 diff --git a/src/waitress/parser.py b/src/waitress/parser.py index 4530b23..3b99921 100644 --- a/src/waitress/parser.py +++ b/src/waitress/parser.py @@ -18,9 +18,10 @@ processing but threads to do work. """ from io import BytesIO import re +from urllib import parse +from urllib.parse import unquote_to_bytes from waitress.buffers import OverflowableBuffer -from waitress.compat import tostr, unquote_bytes_to_wsgi, urlparse from waitress.receiver import ChunkedReceiver, FixedStreamReceiver from waitress.utilities import ( BadRequest, @@ -33,6 +34,10 @@ from waitress.utilities import ( from .rfc7230 import HEADER_FIELD +def unquote_bytes_to_wsgi(bytestring): + return unquote_to_bytes(bytestring).decode("latin-1") + + class ParsingError(Exception): pass @@ -80,11 +85,13 @@ class HTTPRequestParser: bytes consumed. Sets the completed flag once both the header and the body have been received. """ + if self.completed: return 0 # Can't consume any more. datalen = len(data) br = self.body_rcv + if br is None: # In header. max_header = self.adj.max_request_header_size @@ -106,12 +113,14 @@ class HTTPRequestParser: # If the first line + headers is over the max length, we return a # RequestHeaderFieldsTooLarge error rather than continuing to # attempt to parse the headers. + if self.header_bytes_received >= max_header: self.parse_header(b"GET / HTTP/1.0\r\n") self.error = RequestHeaderFieldsTooLarge( "exceeds max_header of %s" % max_header ) self.completed = True + return consumed if index >= 0: @@ -195,6 +204,7 @@ class HTTPRequestParser: first line of the request). """ index = header_plus.find(b"\r\n") + if index >= 0: first_line = header_plus[:index].rstrip() header = header_plus[index + 2 :] @@ -209,6 +219,7 @@ class HTTPRequestParser: lines = get_header_lines(header) headers = self.headers + for line in lines: header = HEADER_FIELD.match(line) @@ -219,25 +230,26 @@ class HTTPRequestParser: if b"_" in key: # TODO(xistence): Should we drop this request instead? + continue # Only strip off whitespace that is considered valid whitespace by # RFC7230, don't strip the rest value = value.strip(b" \t") - key1 = tostr(key.upper().replace(b"-", b"_")) + key1 = key.upper().replace(b"-", b"_").decode("latin-1") # If a header already exists, we append subsequent values # separated by a comma. Applications already need to handle # the comma separated values, as HTTP front ends might do # the concatenation for you (behavior specified in RFC2616). try: - headers[key1] += tostr(b", " + value) + headers[key1] += (b", " + value).decode("latin-1") except KeyError: - headers[key1] = tostr(value) + headers[key1] = value.decode("latin-1") # command, uri, version will be bytes command, uri, version = crack_first_line(first_line) - version = tostr(version) - command = tostr(command) + version = version.decode("latin-1") + command = command.decode("latin-1") self.command = command self.version = version ( @@ -280,6 +292,7 @@ class HTTPRequestParser: # Note: the identity transfer-coding was removed in RFC7230: # https://tools.ietf.org/html/rfc7230#appendix-A.2 and is thus # not supported + if encoding not in {"chunked"}: raise TransferEncodingNotImplemented( "Transfer-Encoding requested is not supported." @@ -296,6 +309,7 @@ class HTTPRequestParser: expect = headers.get("EXPECT", "").lower() self.expect_continue = expect == "100-continue" + if connection.lower() == "close": self.connection_close = True @@ -306,12 +320,14 @@ class HTTPRequestParser: raise ParsingError("Content-Length is invalid") self.content_length = cl + if cl > 0: buf = OverflowableBuffer(self.adj.inbuf_overflow) self.body_rcv = FixedStreamReceiver(cl, buf) def get_body_stream(self): body_rcv = self.body_rcv + if body_rcv is not None: return body_rcv.getfile() else: @@ -319,6 +335,7 @@ class HTTPRequestParser: def close(self): body_rcv = self.body_rcv + if body_rcv is not None: body_rcv.getbuf().close() @@ -346,16 +363,16 @@ def split_uri(uri): path, query = path.split(b"?", 1) else: try: - scheme, netloc, path, query, fragment = urlparse.urlsplit(uri) + scheme, netloc, path, query, fragment = parse.urlsplit(uri) except UnicodeError: raise ParsingError("Bad URI") return ( - tostr(scheme), - tostr(netloc), + scheme.decode("latin-1"), + netloc.decode("latin-1"), unquote_bytes_to_wsgi(path), - tostr(query), - tostr(fragment), + query.decode("latin-1"), + fragment.decode("latin-1"), ) @@ -365,20 +382,24 @@ def get_header_lines(header): """ r = [] lines = header.split(b"\r\n") + for line in lines: if not line: continue if b"\r" in line or b"\n" in line: - raise ParsingError('Bare CR or LF found in header line "%s"' % tostr(line)) + raise ParsingError( + 'Bare CR or LF found in header line "%s"' % str(line, "latin-1") + ) if line.startswith((b" ", b"\t")): if not r: # https://corte.si/posts/code/pathod/pythonservers/index.html - raise ParsingError('Malformed header line "%s"' % tostr(line)) + raise ParsingError('Malformed header line "%s"' % str(line, "latin-1")) r[-1] += line else: r.append(line) + return r @@ -391,6 +412,7 @@ first_line_re = re.compile( def crack_first_line(line): m = first_line_re.match(line) + if m is not None and m.end() == len(line): if m.group(3): version = m.group(5) @@ -407,9 +429,11 @@ def crack_first_line(line): # unsuspecting souls from sending lowercase HTTP methods to waitress # and having the request complete, while servers like nginx drop the # request onto the floor. + if method != method.upper(): - raise ParsingError('Malformed HTTP method "%s"' % tostr(method)) + raise ParsingError('Malformed HTTP method "%s"' % str(method, "latin-1")) uri = m.group(2) + return method, uri, version else: return b"", b"", b"" diff --git a/src/waitress/rfc7230.py b/src/waitress/rfc7230.py index cd33c90..9b25fbd 100644 --- a/src/waitress/rfc7230.py +++ b/src/waitress/rfc7230.py @@ -5,8 +5,6 @@ needed to properly parse HTTP messages. import re -from .compat import tobytes - WS = "[ \t]" OWS = WS + "{0,}?" RWS = WS + "{1,}?" @@ -46,7 +44,7 @@ FIELD_CONTENT = FIELD_VCHAR + "+(?:[ \t]+" + FIELD_VCHAR + "+)*" FIELD_VALUE = "(?:" + FIELD_CONTENT + ")?" HEADER_FIELD = re.compile( - tobytes( + ( "^(?P<name>" + TOKEN + "):" + OWS + "(?P<value>" + FIELD_VALUE + ")" + OWS + "$" - ) + ).encode("latin-1") ) diff --git a/src/waitress/task.py b/src/waitress/task.py index b82109f..779f01e 100644 --- a/src/waitress/task.py +++ b/src/waitress/task.py @@ -19,7 +19,6 @@ import threading import time from .buffers import ReadOnlyFileBasedBuffer -from .compat import tobytes from .utilities import build_http_date, logger, queue_logger rename_headers = { # or keep them without the HTTP_ prefix added @@ -281,7 +280,7 @@ class Task: lines = [first_line] + next_lines res = "%s\r\n\r\n" % "\r\n".join(lines) - return tobytes(res) + return res.encode("latin-1") def remove_content_length_header(self): response_headers = [] @@ -317,7 +316,7 @@ class Task: cl = self.content_length if self.chunked_response: # use chunked encoding response - towrite = tobytes(hex(len(data))[2:].upper()) + b"\r\n" + towrite = hex(len(data))[2:].upper().encode("latin-1") + b"\r\n" towrite += data + b"\r\n" elif cl is not None: towrite = data[: cl - self.content_bytes_written] @@ -361,7 +360,7 @@ class ErrorTask(Task): self.response_headers.append(("Connection", "close")) self.close_on_finish = True self.content_length = len(body) - self.write(tobytes(body)) + self.write(body.encode("latin-1")) class WSGITask(Task): diff --git a/tests/test_compat.py b/tests/test_compat.py deleted file mode 100644 index e371348..0000000 --- a/tests/test_compat.py +++ /dev/null @@ -1,14 +0,0 @@ -import unittest - - -class Test_unquote_bytes_to_wsgi(unittest.TestCase): - def _callFUT(self, v): - from waitress.compat import unquote_bytes_to_wsgi - - return unquote_bytes_to_wsgi(v) - - def test_highorder(self): - val = b"/a%C5%9B" - result = self._callFUT(val) - # PEP 3333 urlunquoted-latin1-decoded-bytes - self.assertEqual(result, "/aÅ\x9b") diff --git a/tests/test_functional.py b/tests/test_functional.py index 9d94b8e..968fbe6 100644 --- a/tests/test_functional.py +++ b/tests/test_functional.py @@ -1,4 +1,5 @@ import errno +from http import client as httplib import logging import multiprocessing import os @@ -11,7 +12,6 @@ import time import unittest from waitress import server -from waitress.compat import httplib, tobytes from waitress.utilities import cleanup_unix_socket dn = os.path.dirname @@ -57,6 +57,7 @@ class FixtureTcpWSGIServer(server.TcpWSGIServer): kw["port"] = 0 # Bind to any available port. super().__init__(application, **kw) host, port = self.socket.getsockname() + if os.name == "nt": host = "127.0.0.1" queue.put((host, port)) @@ -99,9 +100,9 @@ class SubprocessTests: def assertline(self, line, status, reason, version): v, s, r = (x.strip() for x in line.split(None, 2)) - self.assertEqual(s, tobytes(status)) - self.assertEqual(r, tobytes(reason)) - self.assertEqual(v, tobytes(version)) + self.assertEqual(s, status.encode("latin-1")) + self.assertEqual(r, reason.encode("latin-1")) + self.assertEqual(v, version.encode("latin-1")) def create_socket(self): return socket.socket(self.server.family, socket.SOCK_STREAM) @@ -143,9 +144,11 @@ class SleepyThreadTests(TcpTests, unittest.TestCase): ) r, w = os.pipe() procs = [] + for cmd in cmds: procs.append(subprocess.Popen(cmd, stdout=w)) time.sleep(3) + for proc in procs: if proc.returncode is not None: # pragma: no cover proc.terminate() @@ -178,11 +181,11 @@ class EchoTests: from tests.fixtureapps import echo line, headers, body = read_http(fp) + return line, headers, echo.parse_response(body) def test_date_and_server(self): - to_send = "GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -193,8 +196,7 @@ class EchoTests: def test_bad_host_header(self): # https://corte.si/posts/code/pathod/pythonservers/index.html - to_send = "GET / HTTP/1.0\r\n Host: 0\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET / HTTP/1.0\r\n Host: 0\r\n\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -204,9 +206,8 @@ class EchoTests: self.assertTrue(headers.get("date")) def test_send_with_body(self): - to_send = "GET / HTTP/1.0\r\nContent-Length: 5\r\n\r\n" - to_send += "hello" - to_send = tobytes(to_send) + to_send = b"GET / HTTP/1.0\r\nContent-Length: 5\r\n\r\n" + to_send += b"hello" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -216,8 +217,7 @@ class EchoTests: self.assertEqual(echo.body, b"hello") def test_send_empty_body(self): - to_send = "GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET / HTTP/1.0\r\nContent-Length: 0\r\n\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -228,6 +228,7 @@ class EchoTests: def test_multiple_requests_with_body(self): orig_sock = self.sock + for x in range(3): self.sock = self.create_socket() self.test_send_with_body() @@ -236,6 +237,7 @@ class EchoTests: def test_multiple_requests_without_body(self): orig_sock = self.sock + for x in range(3): self.sock = self.create_socket() self.test_send_empty_body() @@ -243,13 +245,13 @@ class EchoTests: self.sock = orig_sock def test_without_crlf(self): - data = "Echo\r\nthis\r\nplease" - s = tobytes( - "GET / HTTP/1.0\r\n" - "Connection: close\r\n" - "Content-Length: %d\r\n" - "\r\n" - "%s" % (len(data), data) + data = b"Echo\r\nthis\r\nplease" + s = ( + b"GET / HTTP/1.0\r\n" + b"Connection: close\r\n" + b"Content-Length: %d\r\n" + b"\r\n" + b"%s" % (len(data), data) ) self.connect() self.sock.send(s) @@ -258,40 +260,42 @@ class EchoTests: self.assertline(line, "200", "OK", "HTTP/1.0") self.assertEqual(int(echo.content_length), len(data)) self.assertEqual(len(echo.body), len(data)) - self.assertEqual(echo.body, tobytes(data)) + self.assertEqual(echo.body, (data)) def test_large_body(self): # 1024 characters. - body = "This string has 32 characters.\r\n" * 32 - s = tobytes( - "GET / HTTP/1.0\r\nContent-Length: %d\r\n\r\n%s" % (len(body), body) - ) + body = b"This string has 32 characters.\r\n" * 32 + s = b"GET / HTTP/1.0\r\nContent-Length: %d\r\n\r\n%s" % (len(body), body) self.connect() self.sock.send(s) fp = self.sock.makefile("rb", 0) line, headers, echo = self._read_echo(fp) self.assertline(line, "200", "OK", "HTTP/1.0") self.assertEqual(echo.content_length, "1024") - self.assertEqual(echo.body, tobytes(body)) + self.assertEqual(echo.body, body) def test_many_clients(self): conns = [] + for n in range(50): h = self.make_http_connection() h.request("GET", "/", headers={"Accept": "text/plain"}) conns.append(h) responses = [] + for h in conns: response = h.getresponse() self.assertEqual(response.status, 200) responses.append(response) + for response in responses: response.read() + for h in conns: h.close() def test_chunking_request_without_content(self): - header = tobytes("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n") + header = b"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" self.connect() self.sock.send(header) self.sock.send(b"0\r\n\r\n") @@ -306,10 +310,11 @@ class EchoTests: control_line = b"20;\r\n" # 20 hex = 32 dec s = b"This string has 32 characters.\r\n" expected = s * 12 - header = tobytes("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n") + header = b"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" self.connect() self.sock.send(header) fp = self.sock.makefile("rb", 0) + for n in range(12): self.sock.send(control_line) self.sock.send(s) @@ -322,13 +327,12 @@ class EchoTests: self.assertFalse("transfer-encoding" in headers) def test_broken_chunked_encoding(self): - control_line = "20;\r\n" # 20 hex = 32 dec - s = "This string has 32 characters.\r\n" - to_send = "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" - to_send += control_line + s + "\r\n" + control_line = b"20;\r\n" # 20 hex = 32 dec + s = b"This string has 32 characters.\r\n" + to_send = b"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + to_send += control_line + s + b"\r\n" # garbage in input - to_send += "garbage\r\n" - to_send = tobytes(to_send) + to_send += b"garbage\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -347,13 +351,12 @@ class EchoTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_broken_chunked_encoding_missing_chunk_end(self): - control_line = "20;\r\n" # 20 hex = 32 dec - s = "This string has 32 characters.\r\n" - to_send = "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + control_line = b"20;\r\n" # 20 hex = 32 dec + s = b"This string has 32 characters.\r\n" + to_send = b"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" to_send += control_line + s # garbage in input - to_send += "garbage" - to_send = tobytes(to_send) + to_send += b"garbage" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -374,10 +377,8 @@ class EchoTests: def test_keepalive_http_10(self): # Handling of Keep-Alive within HTTP 1.0 - data = "Default: Don't keep me alive" - s = tobytes( - "GET / HTTP/1.0\r\nContent-Length: %d\r\n\r\n%s" % (len(data), data) - ) + data = b"Default: Don't keep me alive" + s = b"GET / HTTP/1.0\r\nContent-Length: %d\r\n\r\n%s" % (len(data), data) self.connect() self.sock.send(s) response = httplib.HTTPResponse(self.sock) @@ -392,13 +393,13 @@ class EchoTests: # If header Connection: Keep-Alive is explicitly sent, # we want to keept the connection open, we also need to return # the corresponding header - data = "Keep me alive" - s = tobytes( - "GET / HTTP/1.0\r\n" - "Connection: Keep-Alive\r\n" - "Content-Length: %d\r\n" - "\r\n" - "%s" % (len(data), data) + data = b"Keep me alive" + s = ( + b"GET / HTTP/1.0\r\n" + b"Connection: Keep-Alive\r\n" + b"Content-Length: %d\r\n" + b"\r\n" + b"%s" % (len(data), data) ) self.connect() self.sock.send(s) @@ -412,10 +413,8 @@ class EchoTests: # Handling of Keep-Alive within HTTP 1.1 # All connections are kept alive, unless stated otherwise - data = "Default: Keep me alive" - s = tobytes( - "GET / HTTP/1.1\r\nContent-Length: %d\r\n\r\n%s" % (len(data), data) - ) + data = b"Default: Keep me alive" + s = b"GET / HTTP/1.1\r\nContent-Length: %d\r\n\r\n%s" % (len(data), data) self.connect() self.sock.send(s) response = httplib.HTTPResponse(self.sock) @@ -425,13 +424,13 @@ class EchoTests: def test_keepalive_http11_explicit(self): # Explicitly set keep-alive - data = "Default: Keep me alive" - s = tobytes( - "GET / HTTP/1.1\r\n" - "Connection: keep-alive\r\n" - "Content-Length: %d\r\n" - "\r\n" - "%s" % (len(data), data) + data = b"Default: Keep me alive" + s = ( + b"GET / HTTP/1.1\r\n" + b"Connection: keep-alive\r\n" + b"Content-Length: %d\r\n" + b"\r\n" + b"%s" % (len(data), data) ) self.connect() self.sock.send(s) @@ -442,13 +441,13 @@ class EchoTests: def test_keepalive_http11_connclose(self): # specifying Connection: close explicitly - data = "Don't keep me alive" - s = tobytes( - "GET / HTTP/1.1\r\n" - "Connection: close\r\n" - "Content-Length: %d\r\n" - "\r\n" - "%s" % (len(data), data) + data = b"Don't keep me alive" + s = ( + b"GET / HTTP/1.1\r\n" + b"Connection: close\r\n" + b"Content-Length: %d\r\n" + b"\r\n" + b"%s" % (len(data), data) ) self.connect() self.sock.send(s) @@ -459,14 +458,13 @@ class EchoTests: def test_proxy_headers(self): to_send = ( - "GET / HTTP/1.0\r\n" - "Content-Length: 0\r\n" - "Host: www.google.com:8080\r\n" - "X-Forwarded-For: 192.168.1.1\r\n" - "X-Forwarded-Proto: https\r\n" - "X-Forwarded-Port: 5000\r\n\r\n" + b"GET / HTTP/1.0\r\n" + b"Content-Length: 0\r\n" + b"Host: www.google.com:8080\r\n" + b"X-Forwarded-For: 192.168.1.1\r\n" + b"X-Forwarded-Proto: https\r\n" + b"X-Forwarded-Port: 5000\r\n\r\n" ) - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -492,27 +490,30 @@ class PipeliningTests: def test_pipelining(self): s = ( - "GET / HTTP/1.0\r\n" - "Connection: %s\r\n" - "Content-Length: %d\r\n" - "\r\n" - "%s" + b"GET / HTTP/1.0\r\n" + b"Connection: %s\r\n" + b"Content-Length: %d\r\n" + b"\r\n" + b"%s" ) to_send = b"" count = 25 + for n in range(count): - body = "Response #%d\r\n" % (n + 1) + body = b"Response #%d\r\n" % (n + 1) + if n + 1 < count: - conn = "keep-alive" + conn = b"keep-alive" else: - conn = "close" - to_send += tobytes(s % (conn, len(body), body)) + conn = b"close" + to_send += s % (conn, len(body), body) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) + for n in range(count): - expect_body = tobytes("Response #%d\r\n" % (n + 1)) + expect_body = b"Response #%d\r\n" % (n + 1) line = fp.readline() # status line version, status, reason = (x.strip() for x in line.split(None, 2)) headers = parse_headers(fp) @@ -534,14 +535,14 @@ class ExpectContinueTests: def test_expect_continue(self): # specifying Connection: close explicitly - data = "I have expectations" - to_send = tobytes( - "GET / HTTP/1.1\r\n" - "Connection: close\r\n" - "Content-Length: %d\r\n" - "Expect: 100-continue\r\n" - "\r\n" - "%s" % (len(data), data) + data = b"I have expectations" + to_send = ( + b"GET / HTTP/1.1\r\n" + b"Connection: close\r\n" + b"Content-Length: %d\r\n" + b"Expect: 100-continue\r\n" + b"\r\n" + b"%s" % (len(data), data) ) self.connect() self.sock.send(to_send) @@ -559,7 +560,7 @@ class ExpectContinueTests: response_body = fp.read(length) self.assertEqual(int(status), 200) self.assertEqual(length, len(response_body)) - self.assertEqual(response_body, tobytes(data)) + self.assertEqual(response_body, data) class BadContentLengthTests: @@ -574,11 +575,11 @@ class BadContentLengthTests: def test_short_body(self): # check to see if server closes connection when body is too short # for cl header - to_send = tobytes( - "GET /short_body HTTP/1.0\r\n" - "Connection: Keep-Alive\r\n" - "Content-Length: 0\r\n" - "\r\n" + to_send = ( + b"GET /short_body HTTP/1.0\r\n" + b"Connection: Keep-Alive\r\n" + b"Content-Length: 0\r\n" + b"\r\n" ) self.connect() self.sock.send(to_send) @@ -591,7 +592,7 @@ class BadContentLengthTests: self.assertEqual(int(status), 200) self.assertNotEqual(content_length, len(response_body)) self.assertEqual(len(response_body), content_length - 1) - self.assertEqual(response_body, tobytes("abcdefghi")) + self.assertEqual(response_body, b"abcdefghi") # remote closed connection (despite keepalive header); not sure why # first send succeeds self.send_check_error(to_send) @@ -600,11 +601,11 @@ class BadContentLengthTests: def test_long_body(self): # check server doesnt close connection when body is too short # for cl header - to_send = tobytes( - "GET /long_body HTTP/1.0\r\n" - "Connection: Keep-Alive\r\n" - "Content-Length: 0\r\n" - "\r\n" + to_send = ( + b"GET /long_body HTTP/1.0\r\n" + b"Connection: Keep-Alive\r\n" + b"Content-Length: 0\r\n" + b"\r\n" ) self.connect() self.sock.send(to_send) @@ -616,7 +617,7 @@ class BadContentLengthTests: response_body = fp.read(content_length) self.assertEqual(int(status), 200) self.assertEqual(content_length, len(response_body)) - self.assertEqual(response_body, tobytes("abcdefgh")) + self.assertEqual(response_body, b"abcdefgh") # remote does not close connection (keepalive header) self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -638,14 +639,13 @@ class NoContentLengthTests: self.stop_subprocess() def test_http10_generator(self): - body = string.ascii_letters + body = string.ascii_letters.encode("latin-1") to_send = ( - "GET / HTTP/1.0\r\n" - "Connection: Keep-Alive\r\n" - "Content-Length: %d\r\n\r\n" % len(body) + b"GET / HTTP/1.0\r\n" + b"Connection: Keep-Alive\r\n" + b"Content-Length: %d\r\n\r\n" % len(body) ) to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -653,21 +653,20 @@ class NoContentLengthTests: self.assertline(line, "200", "OK", "HTTP/1.0") self.assertEqual(headers.get("content-length"), None) self.assertEqual(headers.get("connection"), "close") - self.assertEqual(response_body, tobytes(body)) + self.assertEqual(response_body, body) # remote closed connection (despite keepalive header), because # generators cannot have a content-length divined self.send_check_error(to_send) self.assertRaises(ConnectionClosed, read_http, fp) def test_http10_list(self): - body = string.ascii_letters + body = string.ascii_letters.encode("latin-1") to_send = ( - "GET /list HTTP/1.0\r\n" - "Connection: Keep-Alive\r\n" - "Content-Length: %d\r\n\r\n" % len(body) + b"GET /list HTTP/1.0\r\n" + b"Connection: Keep-Alive\r\n" + b"Content-Length: %d\r\n\r\n" % len(body) ) to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -675,7 +674,7 @@ class NoContentLengthTests: self.assertline(line, "200", "OK", "HTTP/1.0") self.assertEqual(headers["content-length"], str(len(body))) self.assertEqual(headers.get("connection"), "Keep-Alive") - self.assertEqual(response_body, tobytes(body)) + self.assertEqual(response_body, body) # remote keeps connection open because it divined the content length # from a length-1 list self.sock.send(to_send) @@ -683,14 +682,13 @@ class NoContentLengthTests: self.assertline(line, "200", "OK", "HTTP/1.0") def test_http10_listlentwo(self): - body = string.ascii_letters + body = string.ascii_letters.encode("latin-1") to_send = ( - "GET /list_lentwo HTTP/1.0\r\n" - "Connection: Keep-Alive\r\n" - "Content-Length: %d\r\n\r\n" % len(body) + b"GET /list_lentwo HTTP/1.0\r\n" + b"Connection: Keep-Alive\r\n" + b"Content-Length: %d\r\n\r\n" % len(body) ) to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -698,7 +696,7 @@ class NoContentLengthTests: self.assertline(line, "200", "OK", "HTTP/1.0") self.assertEqual(headers.get("content-length"), None) self.assertEqual(headers.get("connection"), "close") - self.assertEqual(response_body, tobytes(body)) + self.assertEqual(response_body, body) # remote closed connection (despite keepalive header), because # lists of length > 1 cannot have their content length divined self.send_check_error(to_send) @@ -706,18 +704,20 @@ class NoContentLengthTests: def test_http11_generator(self): body = string.ascii_letters - to_send = "GET / HTTP/1.1\r\nContent-Length: %s\r\n\r\n" % len(body) + body = body.encode("latin-1") + to_send = b"GET / HTTP/1.1\r\nContent-Length: %d\r\n\r\n" % len(body) to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb") line, headers, response_body = read_http(fp) self.assertline(line, "200", "OK", "HTTP/1.1") expected = b"" + for chunk in chunks(body, 10): - expected += tobytes( - "%s\r\n%s\r\n" % (str(hex(len(chunk))[2:].upper()), chunk) + expected += b"%s\r\n%s\r\n" % ( + hex(len(chunk))[2:].upper().encode("latin-1"), + chunk, ) expected += b"0\r\n\r\n" self.assertEqual(response_body, expected) @@ -726,17 +726,16 @@ class NoContentLengthTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_http11_list(self): - body = string.ascii_letters - to_send = "GET /list HTTP/1.1\r\nContent-Length: %d\r\n\r\n" % len(body) + body = string.ascii_letters.encode("latin-1") + to_send = b"GET /list HTTP/1.1\r\nContent-Length: %d\r\n\r\n" % len(body) to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) line, headers, response_body = read_http(fp) self.assertline(line, "200", "OK", "HTTP/1.1") self.assertEqual(headers["content-length"], str(len(body))) - self.assertEqual(response_body, tobytes(body)) + self.assertEqual(response_body, body) # remote keeps connection open because it divined the content length # from a length-1 list self.sock.send(to_send) @@ -744,19 +743,20 @@ class NoContentLengthTests: self.assertline(line, "200", "OK", "HTTP/1.1") def test_http11_listlentwo(self): - body = string.ascii_letters - to_send = "GET /list_lentwo HTTP/1.1\r\nContent-Length: %s\r\n\r\n" % len(body) + body = string.ascii_letters.encode("latin-1") + to_send = b"GET /list_lentwo HTTP/1.1\r\nContent-Length: %d\r\n\r\n" % len(body) to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb") line, headers, response_body = read_http(fp) self.assertline(line, "200", "OK", "HTTP/1.1") expected = b"" - for chunk in (body[0], body[1:]): - expected += tobytes( - "%s\r\n%s\r\n" % (str(hex(len(chunk))[2:].upper()), chunk) + + for chunk in (body[:1], body[1:]): + expected += b"%s\r\n%s\r\n" % ( + (hex(len(chunk))[2:].upper().encode("latin-1")), + chunk, ) expected += b"0\r\n\r\n" self.assertEqual(response_body, expected) @@ -777,11 +777,11 @@ class WriteCallbackTests: def test_short_body(self): # check to see if server closes connection when body is too short # for cl header - to_send = tobytes( - "GET /short_body HTTP/1.0\r\n" - "Connection: Keep-Alive\r\n" - "Content-Length: 0\r\n" - "\r\n" + to_send = ( + b"GET /short_body HTTP/1.0\r\n" + b"Connection: Keep-Alive\r\n" + b"Content-Length: 0\r\n" + b"\r\n" ) self.connect() self.sock.send(to_send) @@ -793,7 +793,7 @@ class WriteCallbackTests: self.assertEqual(cl, 9) self.assertNotEqual(cl, len(response_body)) self.assertEqual(len(response_body), cl - 1) - self.assertEqual(response_body, tobytes("abcdefgh")) + self.assertEqual(response_body, b"abcdefgh") # remote closed connection (despite keepalive header) self.send_check_error(to_send) self.assertRaises(ConnectionClosed, read_http, fp) @@ -801,11 +801,11 @@ class WriteCallbackTests: def test_long_body(self): # check server doesnt close connection when body is too long # for cl header - to_send = tobytes( - "GET /long_body HTTP/1.0\r\n" - "Connection: Keep-Alive\r\n" - "Content-Length: 0\r\n" - "\r\n" + to_send = ( + b"GET /long_body HTTP/1.0\r\n" + b"Connection: Keep-Alive\r\n" + b"Content-Length: 0\r\n" + b"\r\n" ) self.connect() self.sock.send(to_send) @@ -814,7 +814,7 @@ class WriteCallbackTests: content_length = int(headers.get("content-length")) or None self.assertEqual(content_length, 9) self.assertEqual(content_length, len(response_body)) - self.assertEqual(response_body, tobytes("abcdefghi")) + self.assertEqual(response_body, b"abcdefghi") # remote does not close connection (keepalive header) self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -824,11 +824,11 @@ class WriteCallbackTests: def test_equal_body(self): # check server doesnt close connection when body is equal to # cl header - to_send = tobytes( - "GET /equal_body HTTP/1.0\r\n" - "Connection: Keep-Alive\r\n" - "Content-Length: 0\r\n" - "\r\n" + to_send = ( + b"GET /equal_body HTTP/1.0\r\n" + b"Connection: Keep-Alive\r\n" + b"Content-Length: 0\r\n" + b"\r\n" ) self.connect() self.sock.send(to_send) @@ -838,7 +838,7 @@ class WriteCallbackTests: self.assertEqual(content_length, 9) self.assertline(line, "200", "OK", "HTTP/1.0") self.assertEqual(content_length, len(response_body)) - self.assertEqual(response_body, tobytes("abcdefghi")) + self.assertEqual(response_body, b"abcdefghi") # remote does not close connection (keepalive header) self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -847,11 +847,11 @@ class WriteCallbackTests: def test_no_content_length(self): # wtf happens when there's no content-length - to_send = tobytes( - "GET /no_content_length HTTP/1.0\r\n" - "Connection: Keep-Alive\r\n" - "Content-Length: 0\r\n" - "\r\n" + to_send = ( + b"GET /no_content_length HTTP/1.0\r\n" + b"Connection: Keep-Alive\r\n" + b"Content-Length: 0\r\n" + b"\r\n" ) self.connect() self.sock.send(to_send) @@ -860,7 +860,7 @@ class WriteCallbackTests: line, headers, response_body = read_http(fp) content_length = headers.get("content-length") self.assertEqual(content_length, None) - self.assertEqual(response_body, tobytes("abcdefghi")) + self.assertEqual(response_body, b"abcdefghi") # remote closed connection (despite keepalive header) self.send_check_error(to_send) self.assertRaises(ConnectionClosed, read_http, fp) @@ -881,10 +881,9 @@ class TooLargeTests: self.stop_subprocess() def test_request_body_too_large_with_wrong_cl_http10(self): - body = "a" * self.toobig - to_send = "GET / HTTP/1.0\r\nContent-Length: 5\r\n\r\n" + body = b"a" * self.toobig + to_send = b"GET / HTTP/1.0\r\nContent-Length: 5\r\n\r\n" to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb") @@ -900,12 +899,11 @@ class TooLargeTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_request_body_too_large_with_wrong_cl_http10_keepalive(self): - body = "a" * self.toobig + body = b"a" * self.toobig to_send = ( - "GET / HTTP/1.0\r\nContent-Length: 5\r\nConnection: Keep-Alive\r\n\r\n" + b"GET / HTTP/1.0\r\nContent-Length: 5\r\nConnection: Keep-Alive\r\n\r\n" ) to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb") @@ -923,10 +921,9 @@ class TooLargeTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_request_body_too_large_with_no_cl_http10(self): - body = "a" * self.toobig - to_send = "GET / HTTP/1.0\r\n\r\n" + body = b"a" * self.toobig + to_send = b"GET / HTTP/1.0\r\n\r\n" to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -939,10 +936,9 @@ class TooLargeTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_request_body_too_large_with_no_cl_http10_keepalive(self): - body = "a" * self.toobig - to_send = "GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n" + body = b"a" * self.toobig + to_send = b"GET / HTTP/1.0\r\nConnection: Keep-Alive\r\n\r\n" to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -962,10 +958,9 @@ class TooLargeTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_request_body_too_large_with_wrong_cl_http11(self): - body = "a" * self.toobig - to_send = "GET / HTTP/1.1\r\nContent-Length: 5\r\n\r\n" + body = b"a" * self.toobig + to_send = b"GET / HTTP/1.1\r\nContent-Length: 5\r\n\r\n" to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb") @@ -984,10 +979,9 @@ class TooLargeTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_request_body_too_large_with_wrong_cl_http11_connclose(self): - body = "a" * self.toobig - to_send = "GET / HTTP/1.1\r\nContent-Length: 5\r\nConnection: close\r\n\r\n" + body = b"a" * self.toobig + to_send = b"GET / HTTP/1.1\r\nContent-Length: 5\r\nConnection: close\r\n\r\n" to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -1001,10 +995,9 @@ class TooLargeTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_request_body_too_large_with_no_cl_http11(self): - body = "a" * self.toobig - to_send = "GET / HTTP/1.1\r\n\r\n" + body = b"a" * self.toobig + to_send = b"GET / HTTP/1.1\r\n\r\n" to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb") @@ -1026,10 +1019,9 @@ class TooLargeTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_request_body_too_large_with_no_cl_http11_connclose(self): - body = "a" * self.toobig - to_send = "GET / HTTP/1.1\r\nConnection: close\r\n\r\n" + body = b"a" * self.toobig + to_send = b"GET / HTTP/1.1\r\nConnection: close\r\n\r\n" to_send += body - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -1043,12 +1035,11 @@ class TooLargeTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_request_body_too_large_chunked_encoding(self): - control_line = "20;\r\n" # 20 hex = 32 dec - s = "This string has 32 characters.\r\n" - to_send = "GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" + control_line = b"20;\r\n" # 20 hex = 32 dec + s = b"This string has 32 characters.\r\n" + to_send = b"GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n" repeat = control_line + s to_send += repeat * ((self.toobig // len(repeat)) + 1) - to_send = tobytes(to_send) self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -1073,8 +1064,7 @@ class InternalServerErrorTests: self.stop_subprocess() def test_before_start_response_http_10(self): - to_send = "GET /before_start_response HTTP/1.0\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /before_start_response HTTP/1.0\r\n\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -1089,8 +1079,7 @@ class InternalServerErrorTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_before_start_response_http_11(self): - to_send = "GET /before_start_response HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /before_start_response HTTP/1.1\r\n\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -1108,9 +1097,7 @@ class InternalServerErrorTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_before_start_response_http_11_close(self): - to_send = tobytes( - "GET /before_start_response HTTP/1.1\r\nConnection: close\r\n\r\n" - ) + to_send = b"GET /before_start_response HTTP/1.1\r\nConnection: close\r\n\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -1129,8 +1116,7 @@ class InternalServerErrorTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_after_start_response_http10(self): - to_send = "GET /after_start_response HTTP/1.0\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /after_start_response HTTP/1.0\r\n\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -1149,8 +1135,7 @@ class InternalServerErrorTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_after_start_response_http11(self): - to_send = "GET /after_start_response HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /after_start_response HTTP/1.1\r\n\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -1168,9 +1153,7 @@ class InternalServerErrorTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_after_start_response_http11_close(self): - to_send = tobytes( - "GET /after_start_response HTTP/1.1\r\nConnection: close\r\n\r\n" - ) + to_send = b"GET /after_start_response HTTP/1.1\r\nConnection: close\r\n\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -1189,8 +1172,7 @@ class InternalServerErrorTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_after_write_cb(self): - to_send = "GET /after_write_cb HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /after_write_cb HTTP/1.1\r\n\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -1202,8 +1184,7 @@ class InternalServerErrorTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_in_generator(self): - to_send = "GET /in_generator HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /in_generator HTTP/1.1\r\n\r\n" self.connect() self.sock.send(to_send) fp = self.sock.makefile("rb", 0) @@ -1225,8 +1206,7 @@ class FileWrapperTests: self.stop_subprocess() def test_filelike_http11(self): - to_send = "GET /filelike HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /filelike HTTP/1.1\r\n\r\n" self.connect() @@ -1243,8 +1223,7 @@ class FileWrapperTests: # connection has not been closed def test_filelike_nocl_http11(self): - to_send = "GET /filelike_nocl HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /filelike_nocl HTTP/1.1\r\n\r\n" self.connect() @@ -1261,8 +1240,7 @@ class FileWrapperTests: # connection has not been closed def test_filelike_shortcl_http11(self): - to_send = "GET /filelike_shortcl HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /filelike_shortcl HTTP/1.1\r\n\r\n" self.connect() @@ -1280,8 +1258,7 @@ class FileWrapperTests: # connection has not been closed def test_filelike_longcl_http11(self): - to_send = "GET /filelike_longcl HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /filelike_longcl HTTP/1.1\r\n\r\n" self.connect() @@ -1298,8 +1275,7 @@ class FileWrapperTests: # connection has not been closed def test_notfilelike_http11(self): - to_send = "GET /notfilelike HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /notfilelike HTTP/1.1\r\n\r\n" self.connect() @@ -1316,8 +1292,7 @@ class FileWrapperTests: # connection has not been closed def test_notfilelike_iobase_http11(self): - to_send = "GET /notfilelike_iobase HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /notfilelike_iobase HTTP/1.1\r\n\r\n" self.connect() @@ -1334,8 +1309,7 @@ class FileWrapperTests: # connection has not been closed def test_notfilelike_nocl_http11(self): - to_send = "GET /notfilelike_nocl HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /notfilelike_nocl HTTP/1.1\r\n\r\n" self.connect() @@ -1351,8 +1325,7 @@ class FileWrapperTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_notfilelike_shortcl_http11(self): - to_send = "GET /notfilelike_shortcl HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /notfilelike_shortcl HTTP/1.1\r\n\r\n" self.connect() @@ -1370,8 +1343,7 @@ class FileWrapperTests: # connection has not been closed def test_notfilelike_longcl_http11(self): - to_send = "GET /notfilelike_longcl HTTP/1.1\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /notfilelike_longcl HTTP/1.1\r\n\r\n" self.connect() @@ -1389,8 +1361,7 @@ class FileWrapperTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_filelike_http10(self): - to_send = "GET /filelike HTTP/1.0\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /filelike HTTP/1.0\r\n\r\n" self.connect() @@ -1408,8 +1379,7 @@ class FileWrapperTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_filelike_nocl_http10(self): - to_send = "GET /filelike_nocl HTTP/1.0\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /filelike_nocl HTTP/1.0\r\n\r\n" self.connect() @@ -1427,8 +1397,7 @@ class FileWrapperTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_notfilelike_http10(self): - to_send = "GET /notfilelike HTTP/1.0\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /notfilelike HTTP/1.0\r\n\r\n" self.connect() @@ -1446,8 +1415,7 @@ class FileWrapperTests: self.assertRaises(ConnectionClosed, read_http, fp) def test_notfilelike_nocl_http10(self): - to_send = "GET /notfilelike_nocl HTTP/1.0\r\n\r\n" - to_send = tobytes(to_send) + to_send = b"GET /notfilelike_nocl HTTP/1.0\r\n\r\n" self.connect() @@ -1571,13 +1539,16 @@ def parse_headers(fp): """Parses only RFC2822 headers from a file pointer. """ headers = {} + while True: line = fp.readline() + if line in (b"\r\n", b"\n", b""): break line = line.decode("iso-8859-1") name, value = line.strip().split(":", 1) headers[name.lower().strip()] = value.lower().strip() + return headers @@ -1606,22 +1577,28 @@ def read_http(fp): # pragma: no cover except OSError as exc: fp.close() # errno 104 is ENOTRECOVERABLE, In WinSock 10054 is ECONNRESET + if get_errno(exc) in (errno.ECONNABORTED, errno.ECONNRESET, 104, 10054): raise ConnectionClosed raise + if not response_line: raise ConnectionClosed header_lines = [] + while True: line = fp.readline() + if line in (b"\r\n", b"\r\n", b""): break else: header_lines.append(line) headers = dict() + for x in header_lines: x = x.strip() + if not x: continue key, value = x.split(b": ", 1) @@ -1634,8 +1611,10 @@ def read_http(fp): # pragma: no cover num = int(headers["content-length"]) body = b"" left = num + while left > 0: data = fp.read(left) + if not data: break body += data @@ -1671,5 +1650,6 @@ def get_errno(exc): # pragma: no cover def chunks(l, n): """ Yield successive n-sized chunks from l. """ + for i in range(0, len(l), n): yield l[i : i + n] diff --git a/tests/test_parser.py b/tests/test_parser.py index e0e4d25..eace4af 100644 --- a/tests/test_parser.py +++ b/tests/test_parser.py @@ -15,13 +15,26 @@ """ import unittest -from waitress.compat import text_, tobytes +from waitress.adjustments import Adjustments +from waitress.parser import ( + HTTPRequestParser, + ParsingError, + TransferEncodingNotImplemented, + crack_first_line, + get_header_lines, + split_uri, + unquote_bytes_to_wsgi, +) +from waitress.utilities import ( + BadRequest, + RequestEntityTooLarge, + RequestHeaderFieldsTooLarge, + ServerNotImplemented, +) class TestHTTPRequestParser(unittest.TestCase): def setUp(self): - from waitress.adjustments import Adjustments - from waitress.parser import HTTPRequestParser my_adj = Adjustments() self.parser = HTTPRequestParser(my_adj) @@ -45,8 +58,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertEqual(self.parser.headers, {}) def test_received_bad_host_header(self): - from waitress.utilities import BadRequest - data = b"HTTP/1.0 GET /foobar\r\n Host: foo\r\n\r\n" result = self.parser.received(data) self.assertEqual(result, 36) @@ -54,8 +65,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertEqual(self.parser.error.__class__, BadRequest) def test_received_bad_transfer_encoding(self): - from waitress.utilities import ServerNotImplemented - data = ( b"GET /foobar HTTP/1.1\r\n" b"Transfer-Encoding: foo\r\n" @@ -89,7 +98,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertEqual(result, 0) def test_received_cl_too_large(self): - from waitress.utilities import RequestEntityTooLarge self.parser.adj.max_request_body_size = 2 data = b"GET /foobar HTTP/8.4\r\nContent-Length: 10\r\n\r\n" @@ -99,7 +107,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_headers_too_large(self): - from waitress.utilities import RequestHeaderFieldsTooLarge self.parser.adj.max_request_header_size = 2 data = b"GET /foobar HTTP/8.4\r\nX-Foo: 1\r\n\r\n" @@ -109,8 +116,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(isinstance(self.parser.error, RequestHeaderFieldsTooLarge)) def test_received_body_too_large(self): - from waitress.utilities import RequestEntityTooLarge - self.parser.adj.max_request_body_size = 2 data = ( b"GET /foobar HTTP/1.1\r\n" @@ -129,8 +134,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge)) def test_received_error_from_parser(self): - from waitress.utilities import BadRequest - data = ( b"GET /foobar HTTP/1.1\r\n" b"Transfer-Encoding: chunked\r\n" @@ -171,8 +174,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertEqual(self.parser.headers["FOO"], "bar") def test_parse_header_no_cr_in_headerplus(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/8.4" try: @@ -183,8 +184,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_bad_content_length(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/8.4\r\ncontent-length: abc\r\n" try: @@ -195,8 +194,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_multiple_content_length(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/8.4\r\ncontent-length: 10\r\ncontent-length: 20\r\n" try: @@ -213,8 +210,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertEqual(self.parser.body_rcv.__class__.__name__, "ChunkedReceiver") def test_parse_header_transfer_encoding_invalid(self): - from waitress.parser import TransferEncodingNotImplemented - data = b"GET /foobar HTTP/1.1\r\ntransfer-encoding: gzip\r\n" try: @@ -225,7 +220,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_transfer_encoding_invalid_multiple(self): - from waitress.parser import TransferEncodingNotImplemented data = b"GET /foobar HTTP/1.1\r\ntransfer-encoding: gzip\r\ntransfer-encoding: chunked\r\n" @@ -237,8 +231,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_transfer_encoding_invalid_whitespace(self): - from waitress.parser import TransferEncodingNotImplemented - data = b"GET /foobar HTTP/1.1\r\nTransfer-Encoding:\x85chunked\r\n" try: @@ -249,8 +241,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_transfer_encoding_invalid_unicode(self): - from waitress.parser import TransferEncodingNotImplemented - # This is the binary encoding for the UTF-8 character # https://www.compart.com/en/unicode/U+212A "unicode character "K"" # which if waitress were to accidentally do the wrong thing get @@ -286,8 +276,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.parser.close() # doesn't raise def test_parse_header_lf_only(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/8.4\nfoo: bar" try: @@ -298,8 +286,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_cr_only(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/8.4\rfoo: bar" try: self.parser.parse_header(data) @@ -309,8 +295,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_extra_lf_in_header(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/8.4\r\nfoo: \nbar\r\n" try: self.parser.parse_header(data) @@ -320,8 +304,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_extra_lf_in_first_line(self): - from waitress.parser import ParsingError - data = b"GET /foobar\n HTTP/8.4\r\n" try: self.parser.parse_header(data) @@ -331,8 +313,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_invalid_whitespace(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/8.4\r\nfoo : bar\r\n" try: self.parser.parse_header(data) @@ -342,8 +322,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_invalid_whitespace_vtab(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/1.1\r\nfoo:\x0bbar\r\n" try: self.parser.parse_header(data) @@ -353,8 +331,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_invalid_no_colon(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\nnotvalid\r\n" try: self.parser.parse_header(data) @@ -364,8 +340,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_invalid_folding_spacing(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\n\t\x0bbaz\r\n" try: self.parser.parse_header(data) @@ -375,8 +349,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_invalid_chars(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\nfoo: \x0bbaz\r\n" try: self.parser.parse_header(data) @@ -386,8 +358,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_empty(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\nempty:\r\n" self.parser.parse_header(data) @@ -397,8 +367,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertEqual(self.parser.headers["FOO"], "bar") def test_parse_header_multiple_values(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/1.1\r\nfoo: bar, whatever, more, please, yes\r\n" self.parser.parse_header(data) @@ -406,8 +374,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertEqual(self.parser.headers["FOO"], "bar, whatever, more, please, yes") def test_parse_header_multiple_values_header_folded(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/1.1\r\nfoo: bar, whatever,\r\n more, please, yes\r\n" self.parser.parse_header(data) @@ -415,8 +381,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertEqual(self.parser.headers["FOO"], "bar, whatever, more, please, yes") def test_parse_header_multiple_values_header_folded_multiple(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/1.1\r\nfoo: bar, whatever,\r\n more\r\nfoo: please, yes\r\n" self.parser.parse_header(data) @@ -425,8 +389,6 @@ class TestHTTPRequestParser(unittest.TestCase): def test_parse_header_multiple_values_extra_space(self): # Tests errata from: https://www.rfc-editor.org/errata_search.php?rfc=7230&eid=4189 - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/1.1\r\nfoo: abrowser/0.001 (C O M M E N T)\r\n" self.parser.parse_header(data) @@ -434,8 +396,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertEqual(self.parser.headers["FOO"], "abrowser/0.001 (C O M M E N T)") def test_parse_header_invalid_backtrack_bad(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/1.1\r\nfoo: bar\r\nfoo: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\x10\r\n" try: self.parser.parse_header(data) @@ -445,8 +405,6 @@ class TestHTTPRequestParser(unittest.TestCase): self.assertTrue(False) def test_parse_header_short_values(self): - from waitress.parser import ParsingError - data = b"GET /foobar HTTP/1.1\r\none: 1\r\ntwo: 22\r\n" self.parser.parse_header(data) @@ -458,8 +416,6 @@ class TestHTTPRequestParser(unittest.TestCase): class Test_split_uri(unittest.TestCase): def _callFUT(self, uri): - from waitress.parser import split_uri - ( self.proxy_scheme, self.proxy_netloc, @@ -499,7 +455,6 @@ class Test_split_uri(unittest.TestCase): def test_split_uri_unicode_error_raises_parsing_error(self): # See https://github.com/Pylons/waitress/issues/64 - from waitress.parser import ParsingError # Either pass or throw a ParsingError, just don't throw another type of # exception as that will cause the connection to close badly: @@ -535,8 +490,6 @@ class Test_split_uri(unittest.TestCase): class Test_get_header_lines(unittest.TestCase): def _callFUT(self, data): - from waitress.parser import get_header_lines - return get_header_lines(data) def test_get_header_lines(self): @@ -561,15 +514,11 @@ class Test_get_header_lines(unittest.TestCase): def test_get_header_lines_malformed(self): # https://corte.si/posts/code/pathod/pythonservers/index.html - from waitress.parser import ParsingError - self.assertRaises(ParsingError, self._callFUT, b" Host: localhost\r\n\r\n") class Test_crack_first_line(unittest.TestCase): def _callFUT(self, line): - from waitress.parser import crack_first_line - return crack_first_line(line) def test_crack_first_line_matchok(self): @@ -577,8 +526,6 @@ class Test_crack_first_line(unittest.TestCase): self.assertEqual(result, (b"GET", b"/", b"1.0")) def test_crack_first_line_lowercase_method(self): - from waitress.parser import ParsingError - self.assertRaises(ParsingError, self._callFUT, b"get / HTTP/1.0") def test_crack_first_line_nomatch(self): @@ -595,9 +542,6 @@ class Test_crack_first_line(unittest.TestCase): class TestHTTPRequestParserIntegration(unittest.TestCase): def setUp(self): - from waitress.adjustments import Adjustments - from waitress.parser import HTTPRequestParser - my_adj = Adjustments() self.parser = HTTPRequestParser(my_adj) @@ -657,8 +601,8 @@ class TestHTTPRequestParserIntegration(unittest.TestCase): ) # path should be utf-8 encoded self.assertEqual( - tobytes(parser.path).decode("utf-8"), - text_(b"/foo/a++/\xc3\xa4=&a:int", "utf-8"), + parser.path.encode("latin-1").decode("utf-8"), + b"/foo/a++/\xc3\xa4=&a:int".decode("utf-8"), ) self.assertEqual( parser.query, "d=b+%2B%2F%3D%26b%3Aint&c+%2B%2F%3D%26c%3Aint=6" @@ -721,6 +665,18 @@ class TestHTTPRequestParserIntegration(unittest.TestCase): self.assertEqual(self.parser.headers, {"CONTENT_LENGTH": "6",}) +class Test_unquote_bytes_to_wsgi(unittest.TestCase): + def _callFUT(self, v): + + return unquote_bytes_to_wsgi(v) + + def test_highorder(self): + val = b"/a%C5%9B" + result = self._callFUT(val) + # PEP 3333 urlunquoted-latin1-decoded-bytes + self.assertEqual(result, "/aÅ\x9b") + + class DummyBodyStream: def getfile(self): return self diff --git a/tests/test_proxy_headers.py b/tests/test_proxy_headers.py index 1aea477..e6f0ed6 100644 --- a/tests/test_proxy_headers.py +++ b/tests/test_proxy_headers.py @@ -1,7 +1,5 @@ import unittest -from waitress.compat import tobytes - class TestProxyHeadersMiddleware(unittest.TestCase): def _makeOne(self, app, **kw): @@ -18,7 +16,7 @@ class TestProxyHeadersMiddleware(unittest.TestCase): response.headers = response_headers response.steps = list(app(environ, start_response)) - response.body = b"".join(tobytes(s) for s in response.steps) + response.body = b"".join(s.encode("latin-1") for s in response.steps) return response def test_get_environment_values_w_scheme_override_untrusted(self): diff --git a/tests/test_runner.py b/tests/test_runner.py index e77d103..2ac302c 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -181,9 +181,9 @@ class Test_helper(unittest.TestCase): @contextlib.contextmanager def capture(): - from waitress.compat import NativeIO + from io import StringIO - fd = NativeIO() + fd = StringIO() sys.stdout = fd sys.stderr = fd yield fd diff --git a/tests/test_wasyncore.py b/tests/test_wasyncore.py index a7e2878..970e993 100644 --- a/tests/test_wasyncore.py +++ b/tests/test_wasyncore.py @@ -1,3 +1,4 @@ +import _thread as thread import contextlib import errno import functools @@ -23,6 +24,7 @@ HOSTv4 = "127.0.0.1" HOSTv6 = "::1" # Filename used for testing + if os.name == "java": # pragma: no cover # Jython disallows @ in module names TESTFN = "$test" @@ -66,6 +68,7 @@ def _filterwarnings(filters, quiet=False): # pragma: no cover # in order to re-raise the warnings. frame = sys._getframe(2) registry = frame.f_globals.get("__warningregistry__") + if registry: registry.clear() with warnings.catch_warnings(record=True) as w: @@ -77,19 +80,25 @@ def _filterwarnings(filters, quiet=False): # pragma: no cover # Filter the recorded warnings reraise = list(w) missing = [] + for msg, cat in filters: seen = False + for w in reraise[:]: warning = w.message # Filter out the matching messages + if re.match(msg, str(warning), re.I) and issubclass(warning.__class__, cat): seen = True reraise.remove(w) + if not seen and not quiet: # This filter caught nothing missing.append((msg, cat.__name__)) + if reraise: raise AssertionError("unhandled warning %s" % reraise[0]) + if missing: raise AssertionError("filter (%r, %s) did not catch any warning" % missing[0]) @@ -110,11 +119,14 @@ def check_warnings(*filters, **kwargs): # pragma: no cover check_warnings(("", Warning), quiet=True) """ quiet = kwargs.get("quiet") + if not filters: filters = (("", Warning),) # Preserve backward compatibility + if quiet is None: quiet = True + return _filterwarnings(filters, quiet) @@ -129,6 +141,7 @@ def gc_collect(): # pragma: no cover objects to disappear. """ gc.collect() + if sys.platform.startswith("java"): time.sleep(0.1) gc.collect() @@ -136,7 +149,7 @@ def gc_collect(): # pragma: no cover def threading_setup(): # pragma: no cover - return (compat.thread._count(), None) + return (thread._count(), None) def threading_cleanup(*original_values): # pragma: no cover @@ -145,7 +158,8 @@ def threading_cleanup(*original_values): # pragma: no cover _MAX_COUNT = 100 for count in range(_MAX_COUNT): - values = (compat.thread._count(), None) + values = (thread._count(), None) + if values == original_values: break @@ -185,6 +199,7 @@ def join_thread(thread, timeout=30.0): # pragma: no cover after timeout seconds. """ thread.join(timeout) + if thread.is_alive(): msg = "failed to join the thread in %.1f seconds" % timeout raise AssertionError(msg) @@ -212,6 +227,7 @@ def bind_port(sock, host=HOST): # pragma: no cover "tests should never set the SO_REUSEADDR " "socket option on TCP/IP sockets!" ) + if hasattr(socket, "SO_REUSEPORT"): try: if sock.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT) == 1: @@ -224,11 +240,13 @@ def bind_port(sock, host=HOST): # pragma: no cover # thus defining SO_REUSEPORT but this process is running # under an older kernel that does not support SO_REUSEPORT. pass + if hasattr(socket, "SO_EXCLUSIVEADDRUSE"): sock.setsockopt(socket.SOL_SOCKET, socket.SO_EXCLUSIVEADDRUSE, 1) sock.bind((host, 0)) port = sock.getsockname()[1] + return port @@ -302,13 +320,16 @@ def capture_server(evt, buf, serv): # pragma no cover else: n = 200 start = time.time() + while n > 0 and time.time() - start < 3.0: r, w, e = select.select([conn], [], [], 0.1) + if r: n -= 1 data = conn.recv(10) # keep everything except for the newline terminator buf.write(data.replace(b"\n", b"")) + if b"\n" in data: break time.sleep(0.01) @@ -331,6 +352,7 @@ def bind_unix_socket(sock, addr): # pragma: no cover def bind_af_aware(sock, addr): """Helper function to bind a socket according to its family.""" + if HAS_UNIX_SOCKETS and sock.family == socket.AF_UNIX: # Make sure the path doesn't exist. unlink(addr) @@ -345,6 +367,7 @@ if sys.platform.startswith("win"): # pragma: no cover # Perform the operation func(pathname) # Now setup the wait loop + if waitall: dirname = pathname else: @@ -357,6 +380,7 @@ if sys.platform.startswith("win"): # pragma: no cover # Testing on an i7@4.3GHz shows that usually only 1 iteration is # required when contention occurs. timeout = 0.001 + while timeout < 1.0: # Note we are only testing for the existence of the file(s) in # the contents of the directory regardless of any security or @@ -366,6 +390,7 @@ if sys.platform.startswith("win"): # pragma: no cover # Other Windows APIs can fail or give incorrect results when # dealing with files that are pending deletion. L = os.listdir(dirname) + if not (L if waitall else name in L): return # Increase the timeout and try again @@ -394,17 +419,20 @@ def unlink(filename): def _is_ipv6_enabled(): # pragma: no cover """Check whether IPv6 is enabled on this host.""" + if compat.HAS_IPV6: sock = None try: sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM) sock.bind(("::1", 0)) + return True except OSError: pass finally: if sock: sock.close() + return False @@ -486,6 +514,7 @@ class HelperFunctionTests(unittest.TestCase): # Only the attribute modified by the routine we expect to be # called should be True. + for attr in attributes: self.assertEqual(getattr(tobj, attr), attr == expectedattr) @@ -512,6 +541,7 @@ class HelperFunctionTests(unittest.TestCase): l = [] testmap = {} + for i in range(10): c = dummychannel() l.append(c) @@ -605,6 +635,7 @@ class DispatcherTests(unittest.TestCase): def test_strerror(self): # refers to bug #8573 err = asyncore._strerror(errno.EPERM) + if hasattr(os, "strerror"): self.assertEqual(err, os.strerror(errno.EPERM)) err = asyncore._strerror(-1) @@ -655,6 +686,7 @@ class DispatcherWithSendTests(unittest.TestCase): d.send(b"\n") n = 1000 + while d.out_buffer and n > 0: # pragma: no cover asyncore.poll() n -= 1 @@ -722,6 +754,7 @@ class FileWrapperTest(unittest.TestCase): def test_resource_warning(self): # Issue #11453 got_warning = False + while got_warning is False: # we try until we get the outcome we want because this # test is not deterministic (gc_collect() may not @@ -818,8 +851,10 @@ class BaseTestAPI: def loop_waiting_for_flag(self, instance, timeout=5): # pragma: no cover timeout = float(timeout) / 100 count = 100 + while asyncore.socket_map and count > 0: asyncore.loop(timeout=0.01, count=1, use_poll=self.use_poll) + if instance.flag: return count -= 1 @@ -965,6 +1000,7 @@ class BaseTestAPI: # Make sure handle_expt is called on OOB data received. # Note: this might fail on some platforms as OOB data is # tenuously supported and rarely used. + if HAS_UNIX_SOCKETS and self.family == socket.AF_UNIX: self.skipTest("Not applicable to AF_UNIX sockets.") @@ -979,7 +1015,7 @@ class BaseTestAPI: class TestHandler(BaseTestHandler): def __init__(self, conn): BaseTestHandler.__init__(self, conn) - self.socket.send(compat.tobytes(chr(244)), socket.MSG_OOB) + self.socket.send(chr(244).encode("latin-1"), socket.MSG_OOB) server = BaseServer(self.family, self.addr, TestHandler) client = TestClient(self.family, server.address) @@ -1081,6 +1117,7 @@ class BaseTestAPI: @reap_threads def test_quick_connect(self): # pragma: no cover # see: http://bugs.python.org/issue10340 + if self.family not in (socket.AF_INET, getattr(socket, "AF_INET6", object())): self.skipTest("test specific to AF_INET and AF_INET6") @@ -1692,16 +1729,19 @@ class DummyDispatcher: def handle_read_event(self): self.read_event_handled = True + if self.exc is not None: raise self.exc def handle_write_event(self): self.write_event_handled = True + if self.exc is not None: raise self.exc def handle_expt_event(self): self.expt_event_handled = True + if self.exc is not None: raise self.exc @@ -1740,6 +1780,7 @@ class DummySelect: def select(self, *arg): self.selected.append(arg) + if self.exc is not None: raise self.exc @@ -1754,6 +1795,7 @@ class DummyPollster: def poll(self, timeout): self.polled.append(timeout) + if self.exc is not None: raise self.exc else: # pragma: no cover |