New Upstream Release - waitress
Ready changes
Summary
Merged new upstream version: 2.1.2 (was: 2.1.1).
Resulting package
Built on 2022-10-01T10:46 (took 3m19s)
The resulting binary packages can be installed (if you have the apt repository enabled) by running one of:
apt install -t fresh-releases python-waitress-docapt install -t fresh-releases python3-waitress
Lintian Result
Diff
diff --git a/CHANGES.txt b/CHANGES.txt
index eb7093c..17ca87e 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,3 +1,28 @@
+2.1.2
+-----
+
+Bugfix
+~~~~~~
+
+- When expose_tracebacks is enabled waitress would fail to properly encode
+ unicode thereby causing another error during error handling. See
+ https://github.com/Pylons/waitress/pull/378
+
+- Header length checking had a calculation that was done incorrectly when the
+ data was received across multple socket reads. This calculation has been
+ corrected, and no longer will Waitress send back a 413 Request Entity Too
+ Large. See https://github.com/Pylons/waitress/pull/376
+
+Security Bugfix
+~~~~~~~~~~~~~~~
+
+- in 2.1.0 a new feature was introduced that allowed the WSGI thread to start
+ sending data to the socket. However this introduced a race condition whereby
+ a socket may be closed in the sending thread while the main thread is about
+ to call select() therey causing the entire application to be taken down.
+ Waitress will no longer close the socket in the WSGI thread, instead waking
+ up the main thread to cleanup. See https://github.com/Pylons/waitress/pull/377
+
2.1.1
-----
diff --git a/debian/changelog b/debian/changelog
index f076515..b70f7fc 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,9 @@
+waitress (2.1.2-1) UNRELEASED; urgency=low
+
+ * New upstream release.
+
+ -- Debian Janitor <janitor@jelmer.uk> Sat, 01 Oct 2022 10:43:35 -0000
+
waitress (2.1.1-3) unstable; urgency=medium
* Team Upload.
diff --git a/debian/patches/01-fix-sphinxdoc-conf.patch b/debian/patches/01-fix-sphinxdoc-conf.patch
index f0869a9..e2b6dc9 100644
--- a/debian/patches/01-fix-sphinxdoc-conf.patch
+++ b/debian/patches/01-fix-sphinxdoc-conf.patch
@@ -7,10 +7,10 @@ Forwarded: not-needed
docs/conf.py | 32 +++++++++++++++++---------------
1 file changed, 17 insertions(+), 15 deletions(-)
-diff --git a/docs/conf.py b/docs/conf.py
-index cf0ff9b..6929b78 100644
---- a/docs/conf.py
-+++ b/docs/conf.py
+Index: waitress/docs/conf.py
+===================================================================
+--- waitress.orig/docs/conf.py
++++ waitress/docs/conf.py
@@ -18,8 +18,8 @@
# sys.path.append(os.path.abspath('some/directory'))
@@ -22,7 +22,7 @@ index cf0ff9b..6929b78 100644
# General configuration
# ---------------------
-@@ -53,7 +53,9 @@ copyright = "2012-%s, Agendaless Consulting <chrism@plope.com>" % thisyear
+@@ -53,7 +53,9 @@ copyright = "2012-%s, Agendaless Consult
# other places throughout the built documents.
#
# The short X.Y version.
diff --git a/setup.cfg b/setup.cfg
index 69086dc..333766a 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,6 +1,6 @@
[metadata]
name = waitress
-version = 2.1.1
+version = 2.1.2
description = Waitress WSGI server
long_description = file: README.rst, CHANGES.txt
long_description_content_type = text/x-rst
diff --git a/src/waitress/adjustments.py b/src/waitress/adjustments.py
index 466b5c4..f2a852c 100644
--- a/src/waitress/adjustments.py
+++ b/src/waitress/adjustments.py
@@ -147,7 +147,7 @@ class Adjustments:
# TCP port to listen on
port = _int_marker(8080)
- listen = ["{}:{}".format(host, port)]
+ listen = [f"{host}:{port}"]
# number of threads available for tasks
threads = 4
@@ -327,7 +327,7 @@ class Adjustments:
if not isinstance(self.host, _str_marker) or not isinstance(
self.port, _int_marker
):
- self.listen = ["{}:{}".format(self.host, self.port)]
+ self.listen = [f"{self.host}:{self.port}"]
enabled_families = socket.AF_UNSPEC
diff --git a/src/waitress/channel.py b/src/waitress/channel.py
index 948b498..eb59dd3 100644
--- a/src/waitress/channel.py
+++ b/src/waitress/channel.py
@@ -126,10 +126,10 @@ class HTTPChannel(wasyncore.dispatcher):
if self.will_close:
self.handle_close()
- def _flush_exception(self, flush):
+ def _flush_exception(self, flush, do_close=True):
if flush:
try:
- return (flush(), False)
+ return (flush(do_close=do_close), False)
except OSError:
if self.adj.log_socket_errors:
self.logger.exception("Socket error")
@@ -240,20 +240,20 @@ class HTTPChannel(wasyncore.dispatcher):
return True
- def _flush_some_if_lockable(self):
+ def _flush_some_if_lockable(self, do_close=True):
# Since our task may be appending to the outbuf, we try to acquire
# the lock, but we don't block if we can't.
if self.outbuf_lock.acquire(False):
try:
- self._flush_some()
+ self._flush_some(do_close=do_close)
if self.total_outbufs_len < self.adj.outbuf_high_watermark:
self.outbuf_lock.notify()
finally:
self.outbuf_lock.release()
- def _flush_some(self):
+ def _flush_some(self, do_close=True):
# Send as much data as possible to our client
sent = 0
@@ -267,7 +267,7 @@ class HTTPChannel(wasyncore.dispatcher):
while outbuflen > 0:
chunk = outbuf.get(self.sendbuf_len)
- num_sent = self.send(chunk)
+ num_sent = self.send(chunk, do_close=do_close)
if num_sent:
outbuf.skip(num_sent, True)
@@ -374,7 +374,9 @@ class HTTPChannel(wasyncore.dispatcher):
self.total_outbufs_len += num_bytes
if self.total_outbufs_len >= self.adj.send_bytes:
- (flushed, exception) = self._flush_exception(self._flush_some)
+ (flushed, exception) = self._flush_exception(
+ self._flush_some, do_close=False
+ )
if (
exception
@@ -392,7 +394,7 @@ class HTTPChannel(wasyncore.dispatcher):
if self.total_outbufs_len > self.adj.outbuf_high_watermark:
with self.outbuf_lock:
- (_, exception) = self._flush_exception(self._flush_some)
+ (_, exception) = self._flush_exception(self._flush_some, do_close=False)
if exception:
# An exception happened while flushing, wake up the main
diff --git a/src/waitress/parser.py b/src/waitress/parser.py
index ff16a40..b31b5cc 100644
--- a/src/waitress/parser.py
+++ b/src/waitress/parser.py
@@ -103,7 +103,7 @@ class HTTPRequestParser:
# If the headers have ended, and we also have part of the body
# message in data we still want to validate we aren't going
# over our limit for received headers.
- self.header_bytes_received += index
+ self.header_bytes_received = index
consumed = datalen - (len(s) - index)
else:
self.header_bytes_received += datalen
diff --git a/src/waitress/proxy_headers.py b/src/waitress/proxy_headers.py
index 5d61646..652ca0b 100644
--- a/src/waitress/proxy_headers.py
+++ b/src/waitress/proxy_headers.py
@@ -52,7 +52,7 @@ def proxy_headers_middleware(
ex.reason,
ex.value,
)
- error = BadRequest('Header "{}" malformed.'.format(ex.header))
+ error = BadRequest(f'Header "{ex.header}" malformed.')
return error.wsgi_response(environ, start_response)
# Clear out the untrusted proxy headers
@@ -95,7 +95,7 @@ def parse_proxy_headers(
if "." not in forward_hop and (
":" in forward_hop and forward_hop[-1] != "]"
):
- forwarded_for.append("[{}]".format(forward_hop))
+ forwarded_for.append(f"[{forward_hop}]")
else:
forwarded_for.append(forward_hop)
diff --git a/src/waitress/runner.py b/src/waitress/runner.py
index 9a5f0e3..22f70f3 100644
--- a/src/waitress/runner.py
+++ b/src/waitress/runner.py
@@ -198,7 +198,7 @@ RUNNER_PATTERN = re.compile(
def match(obj_name):
matches = RUNNER_PATTERN.match(obj_name)
if not matches:
- raise ValueError("Malformed application '{}'".format(obj_name))
+ raise ValueError(f"Malformed application '{obj_name}'")
return matches.group("module"), matches.group("object")
@@ -223,7 +223,7 @@ def resolve(module_name, object_name):
def show_help(stream, name, error=None): # pragma: no cover
if error is not None:
- print("Error: {}\n".format(error), file=stream)
+ print(f"Error: {error}\n", file=stream)
print(HELP.format(name), file=stream)
@@ -239,7 +239,7 @@ def show_exception(stream):
if args:
print("It had these arguments: ", file=stream)
for idx, arg in enumerate(args, start=1):
- print("{}. {}\n".format(idx, arg), file=stream)
+ print(f"{idx}. {arg}\n", file=stream)
else:
print("It had no arguments.", file=stream)
@@ -282,11 +282,11 @@ def run(argv=sys.argv, _serve=serve):
try:
app = resolve(module, obj_name)
except ImportError:
- show_help(sys.stderr, name, "Bad module '{}'".format(module))
+ show_help(sys.stderr, name, f"Bad module '{module}'")
show_exception(sys.stderr)
return 1
except AttributeError:
- show_help(sys.stderr, name, "Bad object name '{}'".format(obj_name))
+ show_help(sys.stderr, name, f"Bad object name '{obj_name}'")
show_exception(sys.stderr)
return 1
if kw["call"]:
diff --git a/src/waitress/server.py b/src/waitress/server.py
index 55cffe9..0a0f876 100644
--- a/src/waitress/server.py
+++ b/src/waitress/server.py
@@ -157,7 +157,7 @@ class MultiSocketServer:
l = list(l)
if ":" in l[0]:
- l[0] = "[{}]".format(l[0])
+ l[0] = f"[{l[0]}]"
self.log_info(format_str.format(*l))
diff --git a/src/waitress/task.py b/src/waitress/task.py
index a003919..574532f 100644
--- a/src/waitress/task.py
+++ b/src/waitress/task.py
@@ -57,7 +57,7 @@ class ThreadedTaskDispatcher:
def start_new_thread(self, target, thread_no):
t = threading.Thread(
- target=target, name="waitress-{}".format(thread_no), args=(thread_no,)
+ target=target, name=f"waitress-{thread_no}", args=(thread_no,)
)
t.daemon = True
t.start()
@@ -266,7 +266,7 @@ class Task:
self.response_headers = response_headers
- first_line = "HTTP/%s %s" % (self.version, self.status)
+ first_line = f"HTTP/{self.version} {self.status}"
# NB: sorting headers needs to preserve same-named-header order
# as per RFC 2616 section 4.2; thus the key=lambda x: x[0] here;
# rely on stable sort to keep relative position of same-named headers
@@ -355,7 +355,7 @@ class ErrorTask(Task):
self.response_headers.append(("Connection", "close"))
self.close_on_finish = True
self.content_length = len(body)
- self.write(body.encode("latin-1"))
+ self.write(body)
class WSGITask(Task):
@@ -400,11 +400,11 @@ class WSGITask(Task):
for k, v in headers:
if not k.__class__ is str:
raise AssertionError(
- "Header name %r is not a string in %r" % (k, (k, v))
+ f"Header name {k!r} is not a string in {(k, v)!r}"
)
if not v.__class__ is str:
raise AssertionError(
- "Header value %r is not a string in %r" % (v, (k, v))
+ f"Header value {v!r} is not a string in {(k, v)!r}"
)
if "\n" in v or "\r" in v:
diff --git a/src/waitress/trigger.py b/src/waitress/trigger.py
index 49b2034..73ac31c 100644
--- a/src/waitress/trigger.py
+++ b/src/waitress/trigger.py
@@ -106,9 +106,7 @@ class _triggerbase:
thunk()
except:
nil, t, v, tbinfo = wasyncore.compact_traceback()
- self.log_info(
- "exception in trigger thunk: (%s:%s %s)" % (t, v, tbinfo)
- )
+ self.log_info(f"exception in trigger thunk: ({t}:{v} {tbinfo})")
self.thunks = []
diff --git a/src/waitress/utilities.py b/src/waitress/utilities.py
index 6ae4742..164752f 100644
--- a/src/waitress/utilities.py
+++ b/src/waitress/utilities.py
@@ -259,11 +259,11 @@ class Error:
self.body = body
def to_response(self):
- status = "%s %s" % (self.code, self.reason)
- body = "%s\r\n\r\n%s" % (self.reason, self.body)
+ status = f"{self.code} {self.reason}"
+ body = f"{self.reason}\r\n\r\n{self.body}"
tag = "\r\n\r\n(generated by waitress)"
- body = body + tag
- headers = [("Content-Type", "text/plain")]
+ body = (body + tag).encode("utf-8")
+ headers = [("Content-Type", "text/plain; charset=utf-8")]
return status, headers, body
diff --git a/src/waitress/wasyncore.py b/src/waitress/wasyncore.py
index 9a68c51..b3459e0 100644
--- a/src/waitress/wasyncore.py
+++ b/src/waitress/wasyncore.py
@@ -328,7 +328,7 @@ class dispatcher:
status.append("%s:%d" % self.addr)
except TypeError: # pragma: no cover
status.append(repr(self.addr))
- return "<%s at %#x>" % (" ".join(status), id(self))
+ return "<{} at {:#x}>".format(" ".join(status), id(self))
__str__ = __repr__
@@ -426,7 +426,7 @@ class dispatcher:
else:
return conn, addr
- def send(self, data):
+ def send(self, data, do_close=True):
try:
result = self.socket.send(data)
return result
@@ -434,7 +434,8 @@ class dispatcher:
if why.args[0] == EWOULDBLOCK:
return 0
elif why.args[0] in _DISCONNECTED:
- self.handle_close()
+ if do_close:
+ self.handle_close()
return 0
else:
raise
diff --git a/tests/fixtureapps/error_traceback.py b/tests/fixtureapps/error_traceback.py
new file mode 100644
index 0000000..24e4cbf
--- /dev/null
+++ b/tests/fixtureapps/error_traceback.py
@@ -0,0 +1,2 @@
+def app(environ, start_response): # pragma: no cover
+ raise ValueError("Invalid application: " + chr(8364))
diff --git a/tests/test_channel.py b/tests/test_channel.py
index b1c317d..8467ae7 100644
--- a/tests/test_channel.py
+++ b/tests/test_channel.py
@@ -376,7 +376,7 @@ class TestHTTPChannel(unittest.TestCase):
inst.total_outbufs_len = len(inst.outbufs[0])
inst.adj.send_bytes = 1
inst.adj.outbuf_high_watermark = 2
- sock.send = lambda x: False
+ sock.send = lambda x, do_close=True: False
inst.will_close = False
inst.last_activity = 0
result = inst.handle_write()
@@ -453,7 +453,7 @@ class TestHTTPChannel(unittest.TestCase):
buf = DummyHugeOutbuffer()
inst.outbufs = [buf]
- inst.send = lambda *arg: 0
+ inst.send = lambda *arg, do_close: 0
result = inst._flush_some()
# we are testing that _flush_some doesn't raise an OverflowError
# when one of its outbufs has a __len__ that returns gt sys.maxint
diff --git a/tests/test_functional.py b/tests/test_functional.py
index 60eb24a..1dfd889 100644
--- a/tests/test_functional.py
+++ b/tests/test_functional.py
@@ -359,7 +359,7 @@ class EchoTests:
sorted(headers.keys()),
["connection", "content-length", "content-type", "date", "server"],
)
- self.assertEqual(headers["content-type"], "text/plain")
+ self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -381,7 +381,7 @@ class EchoTests:
sorted(headers.keys()),
["connection", "content-length", "content-type", "date", "server"],
)
- self.assertEqual(headers["content-type"], "text/plain")
+ self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -403,7 +403,7 @@ class EchoTests:
sorted(headers.keys()),
["connection", "content-length", "content-type", "date", "server"],
)
- self.assertEqual(headers["content-type"], "text/plain")
+ self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -428,7 +428,7 @@ class EchoTests:
sorted(headers.keys()),
["connection", "content-length", "content-type", "date", "server"],
)
- self.assertEqual(headers["content-type"], "text/plain")
+ self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -1121,7 +1121,7 @@ class TooLargeTests:
self.assertline(line, "413", "Request Entity Too Large", "HTTP/1.1")
cl = int(headers["content-length"])
self.assertEqual(cl, len(response_body))
- self.assertEqual(headers["content-type"], "text/plain")
+ self.assertEqual(headers["content-type"], "text/plain; charset=utf-8")
# connection has been closed
self.send_check_error(to_send)
self.assertRaises(ConnectionClosed, read_http, fp)
@@ -1269,6 +1269,49 @@ class InternalServerErrorTests:
self.assertRaises(ConnectionClosed, read_http, fp)
+class InternalServerErrorTestsWithTraceback:
+ def setUp(self):
+ from tests.fixtureapps import error_traceback
+
+ self.start_subprocess(error_traceback.app, expose_tracebacks=True)
+
+ def tearDown(self):
+ self.stop_subprocess()
+
+ def test_expose_tracebacks_http_10(self):
+ to_send = b"GET / HTTP/1.0\r\n\r\n"
+ self.connect()
+ self.sock.send(to_send)
+ with self.sock.makefile("rb", 0) as fp:
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "500", "Internal Server Error", "HTTP/1.0")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ self.assertTrue(response_body.startswith(b"Internal Server Error"))
+ self.assertEqual(headers["connection"], "close")
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+ def test_expose_tracebacks_http_11(self):
+ to_send = b"GET / HTTP/1.1\r\n\r\n"
+ self.connect()
+ self.sock.send(to_send)
+ with self.sock.makefile("rb", 0) as fp:
+ line, headers, response_body = read_http(fp)
+ self.assertline(line, "500", "Internal Server Error", "HTTP/1.1")
+ cl = int(headers["content-length"])
+ self.assertEqual(cl, len(response_body))
+ self.assertTrue(response_body.startswith(b"Internal Server Error"))
+ self.assertEqual(
+ sorted(headers.keys()),
+ ["connection", "content-length", "content-type", "date", "server"],
+ )
+ # connection has been closed
+ self.send_check_error(to_send)
+ self.assertRaises(ConnectionClosed, read_http, fp)
+
+
class FileWrapperTests:
def setUp(self):
from tests.fixtureapps import filewrapper
@@ -1538,6 +1581,12 @@ class TcpInternalServerErrorTests(
pass
+class TcpInternalServerErrorTestsWithTraceback(
+ InternalServerErrorTestsWithTraceback, TcpTests, unittest.TestCase
+):
+ pass
+
+
class TcpFileWrapperTests(FileWrapperTests, TcpTests, unittest.TestCase):
pass
@@ -1604,6 +1653,11 @@ if hasattr(socket, "AF_UNIX"):
):
pass
+ class UnixInternalServerErrorTestsWithTraceback(
+ InternalServerErrorTestsWithTraceback, UnixTests, unittest.TestCase
+ ):
+ pass
+
class UnixFileWrapperTests(FileWrapperTests, UnixTests, unittest.TestCase):
pass
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 4461bde..9e9f1cd 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -106,6 +106,18 @@ class TestHTTPRequestParser(unittest.TestCase):
self.assertTrue(self.parser.completed)
self.assertTrue(isinstance(self.parser.error, RequestEntityTooLarge))
+ def test_received_headers_not_too_large_multiple_chunks(self):
+
+ data = b"GET /foobar HTTP/8.4\r\nX-Foo: 1\r\n"
+ data2 = b"X-Foo-Other: 3\r\n\r\n"
+ self.parser.adj.max_request_header_size = len(data) + len(data2) + 1
+ result = self.parser.received(data)
+ self.assertEqual(result, 32)
+ result = self.parser.received(data2)
+ self.assertEqual(result, 18)
+ self.assertTrue(self.parser.completed)
+ self.assertFalse(self.parser.error)
+
def test_received_headers_too_large(self):
self.parser.adj.max_request_header_size = 2
diff --git a/tests/test_proxy_headers.py b/tests/test_proxy_headers.py
index 9ed131e..45f9878 100644
--- a/tests/test_proxy_headers.py
+++ b/tests/test_proxy_headers.py
@@ -16,7 +16,7 @@ class TestProxyHeadersMiddleware(unittest.TestCase):
response.headers = response_headers
response.steps = list(app(environ, start_response))
- response.body = b"".join(s.encode("latin-1") for s in response.steps)
+ response.body = b"".join(s for s in response.steps)
return response
def test_get_environment_values_w_scheme_override_untrusted(self):
@@ -727,7 +727,7 @@ class DummyApp:
def __call__(self, environ, start_response):
self.environ = environ
start_response("200 OK", [("Content-Type", "text/plain")])
- yield "hello"
+ yield b"hello"
class DummyResponse:
diff --git a/tests/test_task.py b/tests/test_task.py
index cc579b0..47868e1 100644
--- a/tests/test_task.py
+++ b/tests/test_task.py
@@ -869,7 +869,7 @@ class TestErrorTask(unittest.TestCase):
self.assertEqual(lines[0], b"HTTP/1.0 432 Too Ugly")
self.assertEqual(lines[1], b"Connection: close")
self.assertEqual(lines[2], b"Content-Length: 43")
- self.assertEqual(lines[3], b"Content-Type: text/plain")
+ self.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
self.assertTrue(lines[4])
self.assertEqual(lines[5], b"Server: waitress")
self.assertEqual(lines[6], b"Too Ugly")
@@ -885,7 +885,7 @@ class TestErrorTask(unittest.TestCase):
self.assertEqual(lines[0], b"HTTP/1.1 432 Too Ugly")
self.assertEqual(lines[1], b"Connection: close")
self.assertEqual(lines[2], b"Content-Length: 43")
- self.assertEqual(lines[3], b"Content-Type: text/plain")
+ self.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
self.assertTrue(lines[4])
self.assertEqual(lines[5], b"Server: waitress")
self.assertEqual(lines[6], b"Too Ugly")
@@ -902,7 +902,7 @@ class TestErrorTask(unittest.TestCase):
self.assertEqual(lines[0], b"HTTP/1.1 432 Too Ugly")
self.assertEqual(lines[1], b"Connection: close")
self.assertEqual(lines[2], b"Content-Length: 43")
- self.assertEqual(lines[3], b"Content-Type: text/plain")
+ self.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
self.assertTrue(lines[4])
self.assertEqual(lines[5], b"Server: waitress")
self.assertEqual(lines[6], b"Too Ugly")
@@ -919,7 +919,7 @@ class TestErrorTask(unittest.TestCase):
self.assertEqual(lines[0], b"HTTP/1.1 432 Too Ugly")
self.assertEqual(lines[1], b"Connection: close")
self.assertEqual(lines[2], b"Content-Length: 43")
- self.assertEqual(lines[3], b"Content-Type: text/plain")
+ self.assertEqual(lines[3], b"Content-Type: text/plain; charset=utf-8")
self.assertTrue(lines[4])
self.assertEqual(lines[5], b"Server: waitress")
self.assertEqual(lines[6], b"Too Ugly")
diff --git a/tests/test_wasyncore.py b/tests/test_wasyncore.py
index 5e0559f..e833c7e 100644
--- a/tests/test_wasyncore.py
+++ b/tests/test_wasyncore.py
@@ -31,7 +31,7 @@ if os.name == "java": # pragma: no cover
else:
TESTFN = "@test"
-TESTFN = "{}_{}_tmp".format(TESTFN, os.getpid())
+TESTFN = f"{TESTFN}_{os.getpid()}_tmp"
class DummyLogger: # pragma: no cover
@@ -574,7 +574,7 @@ class HelperFunctionTests(unittest.TestCase):
self.assertEqual(function, "test_compact_traceback")
self.assertEqual(t, real_t)
self.assertEqual(v, real_v)
- self.assertEqual(info, "[%s|%s|%s]" % (f, function, line))
+ self.assertEqual(info, f"[{f}|{function}|{line}]")
class DispatcherTests(unittest.TestCase):
Debdiff
[The following lists of changes regard files as different if they have different names, permissions or owners.]
Files in second set of .debs but not in first
-rw-r--r-- root/root /usr/lib/python3/dist-packages/waitress-2.1.2.dist-info/METADATA -rw-r--r-- root/root /usr/lib/python3/dist-packages/waitress-2.1.2.dist-info/RECORD -rw-r--r-- root/root /usr/lib/python3/dist-packages/waitress-2.1.2.dist-info/WHEEL -rw-r--r-- root/root /usr/lib/python3/dist-packages/waitress-2.1.2.dist-info/entry_points.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/waitress-2.1.2.dist-info/top_level.txt
Files in first set of .debs but not in second
-rw-r--r-- root/root /usr/lib/python3/dist-packages/waitress-2.1.1.dist-info/METADATA -rw-r--r-- root/root /usr/lib/python3/dist-packages/waitress-2.1.1.dist-info/RECORD -rw-r--r-- root/root /usr/lib/python3/dist-packages/waitress-2.1.1.dist-info/WHEEL -rw-r--r-- root/root /usr/lib/python3/dist-packages/waitress-2.1.1.dist-info/entry_points.txt -rw-r--r-- root/root /usr/lib/python3/dist-packages/waitress-2.1.1.dist-info/top_level.txt
No differences were encountered between the control files of package python-waitress-doc
No differences were encountered between the control files of package python3-waitress