############################################################################## # # Copyright (c) 2005 Zope Foundation and Contributors. # All Rights Reserved. # # This software is subject to the provisions of the Zope Public License, # Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS # FOR A PARTICULAR PURPOSE. # ############################################################################## """Tests for waitress.channel maintenance logic """ import doctest class FakeSocket: # pragma: no cover data = '' setblocking = lambda *_: None close = lambda *_: None def __init__(self, no): self.no = no def fileno(self): return self.no def getpeername(self): return ('localhost', self.no) def send(self, data): self.data += data return len(data) def recv(self, data): return 'data' def zombies_test(): """Regression test for HTTPChannel.maintenance method Bug: This method checks for channels that have been "inactive" for a configured time. The bug was that last_activity is set at creation time but never updated during async channel activity (reads and writes), so any channel older than the configured timeout will be closed when a new channel is created, regardless of activity. >>> import time >>> import waitress.adjustments >>> config = waitress.adjustments.Adjustments() >>> from waitress.server import HTTPServer >>> class TestServer(HTTPServer): ... def bind(self, (ip, port)): ... print "Listening on %s:%d" % (ip or '*', port) >>> sb = TestServer('127.0.0.1', 80, start=False, verbose=True) Listening on 127.0.0.1:80 First we confirm the correct behavior, where a channel with no activity for the timeout duration gets closed. >>> from waitress.channel import HTTPChannel >>> socket = FakeSocket(42) >>> channel = HTTPChannel(sb, socket, ('localhost', 42)) >>> channel.connected True >>> channel.last_activity -= int(config.channel_timeout) + 1 >>> channel.next_channel_cleanup[0] = channel.creation_time - int( ... config.cleanup_interval) - 1 >>> socket2 = FakeSocket(7) >>> channel2 = HTTPChannel(sb, socket2, ('localhost', 7)) >>> channel.connected False Write Activity -------------- Now we make sure that if there is activity the channel doesn't get closed incorrectly. >>> channel2.connected True >>> channel2.last_activity -= int(config.channel_timeout) + 1 >>> channel2.handle_write() >>> channel2.next_channel_cleanup[0] = channel2.creation_time - int( ... config.cleanup_interval) - 1 >>> socket3 = FakeSocket(3) >>> channel3 = HTTPChannel(sb, socket3, ('localhost', 3)) >>> channel2.connected True Read Activity -------------- We should test to see that read activity will update a channel as well. >>> channel3.connected True >>> channel3.last_activity -= int(config.channel_timeout) + 1 >>> import waitress.parser >>> channel3.parser_class = ( ... waitress.parser.HTTPRequestParser) >>> channel3.handle_read() >>> channel3.next_channel_cleanup[0] = channel3.creation_time - int( ... config.cleanup_interval) - 1 >>> socket4 = FakeSocket(4) >>> channel4 = HTTPChannel(sb, socket4, ('localhost', 4)) >>> channel3.connected True Main loop window ---------------- There is also a corner case we'll do a shallow test for where a channel can be closed waiting for the main loop. >>> channel4.last_activity -= 1 >>> last_active = channel4.last_activity >>> channel4.set_async() >>> channel4.last_activity != last_active True """ def test_suite(): return doctest.DocTestSuite()