Patchwork [3,of,6] tests: add tests for poorly behaving HTTP server

login
register
mail settings
Submitter Gregory Szorc
Date April 14, 2017, 7:44 a.m.
Message ID <80bd24abcf67d5dc8b5f.1492155847@ubuntu-vm-main>
Download mbox | patch
Permalink /patch/20196/
State Accepted
Headers show

Comments

Gregory Szorc - April 14, 2017, 7:44 a.m.
# HG changeset patch
# User Gregory Szorc <gregory.szorc@gmail.com>
# Date 1492147168 25200
#      Thu Apr 13 22:19:28 2017 -0700
# Node ID 80bd24abcf67d5dc8b5f5bf83c796e1f71fc5bd9
# Parent  283a95fe4ce5b91af4e2ed8ed1d3fe18ef3544bb
tests: add tests for poorly behaving HTTP server

I've spent several hours over the past few weeks investigating
networking failures involving hg.mozilla.org. As part of this, it
has become clear that the Mercurial client's error handling when
it encounters network failures is far from robust.

To prove this is true, I've devised a battery of tests simulating
various network failures, notably premature connection closes. To
achieve this, I've implemented an extension that monkeypatches the
built-in HTTP server and hooks in at the socket level and allows
various events to occur based on config options. For example, you
can refuse to accept() a client socket or you can close() the socket
after N bytes have been sent or received. The latter effectively
simulates an unexpected connection drop (and these occur all the
time in the real world).

The new test file launches servers exhibiting various "bad" behaviors
and points a client at them. As the many TODO comments in the test
call attention to, Mercurial often displays unhelpful errors when
network-related failures occur. This makes it difficult for users
to understand what's going on and difficult for server administrators
to pinpoint root causes without packet tracing.

Upcoming patches will attempt to fix these error handling
deficiencies.
Gregory Szorc - April 14, 2017, 7:51 a.m.
On Fri, Apr 14, 2017 at 12:44 AM, Gregory Szorc <gregory.szorc@gmail.com>
wrote:

> # HG changeset patch
> # User Gregory Szorc <gregory.szorc@gmail.com>
> # Date 1492147168 25200
> #      Thu Apr 13 22:19:28 2017 -0700
> # Node ID 80bd24abcf67d5dc8b5f5bf83c796e1f71fc5bd9
> # Parent  283a95fe4ce5b91af4e2ed8ed1d3fe18ef3544bb
> tests: add tests for poorly behaving HTTP server
>
> I've spent several hours over the past few weeks investigating
> networking failures involving hg.mozilla.org. As part of this, it
> has become clear that the Mercurial client's error handling when
> it encounters network failures is far from robust.
>
> To prove this is true, I've devised a battery of tests simulating
> various network failures, notably premature connection closes. To
> achieve this, I've implemented an extension that monkeypatches the
> built-in HTTP server and hooks in at the socket level and allows
> various events to occur based on config options. For example, you
> can refuse to accept() a client socket or you can close() the socket
> after N bytes have been sent or received. The latter effectively
> simulates an unexpected connection drop (and these occur all the
> time in the real world).
>
> The new test file launches servers exhibiting various "bad" behaviors
> and points a client at them. As the many TODO comments in the test
> call attention to, Mercurial often displays unhelpful errors when
> network-related failures occur. This makes it difficult for users
> to understand what's going on and difficult for server administrators
> to pinpoint root causes without packet tracing.
>
> Upcoming patches will attempt to fix these error handling
> deficiencies.
>
> diff --git a/tests/badserverext.py b/tests/badserverext.py
> new file mode 100644
> --- /dev/null
> +++ b/tests/badserverext.py
> @@ -0,0 +1,269 @@
> +# badserverext.py - Extension making servers behave badly
> +#
> +# Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
> +#
> +# This software may be used and distributed according to the terms of the
> +# GNU General Public License version 2 or any later version.
> +
> +# no-check-code
> +
> +"""Extension to make servers behave badly.
> +
> +This extension is useful for testing Mercurial behavior when various
> network
> +events occur.
> +
> +Various config options in the [badserver] section influence behavior:
> +
> +closebeforeaccept
> +   If true, close() the server socket when a new connection arrives before
> +   accept() is called. The server will then exit.
> +
> +closeafteraccept
> +   If true, the server will close() the client socket immediately after
> +   accept().
> +
> +closeafterrecvbytes
> +   If defined, close the client socket after receiving this many bytes.
> +
> +closeaftersendbytes
> +   If defined, close the client socket after sending this many bytes.
> +"""
> +
> +from __future__ import absolute_import
> +
> +import socket
> +
> +from mercurial.hgweb import (
> +    server,
> +)
> +
> +# We can't adjust __class__ on a socket instance. So we define a proxy
> type.
> +class socketproxy(object):
> +    __slots__ = (
> +        '_orig',
> +        '_logfp',
> +        '_closeafterrecvbytes',
> +        '_closeaftersendbytes',
> +    )
> +
> +    def __init__(self, obj, logfp, closeafterrecvbytes=0,
> +                 closeaftersendbytes=0):
> +        object.__setattr__(self, '_orig', obj)
> +        object.__setattr__(self, '_logfp', logfp)
> +        object.__setattr__(self, '_closeafterrecvbytes',
> closeafterrecvbytes)
> +        object.__setattr__(self, '_closeaftersendbytes',
> closeaftersendbytes)
> +
> +    def __getattribute__(self, name):
> +        if name in ('makefile',):
> +            return object.__getattribute__(self, name)
> +
> +        return getattr(object.__getattribute__(self, '_orig'), name)
> +
> +    def __delattr__(self, name):
> +        delattr(object.__getattribute__(self, '_orig'), name)
> +
> +    def __setattr__(self, name, value):
> +        setattr(object.__getattribute__(self, '_orig'), name, value)
> +
> +    def makefile(self, mode, bufsize):
> +        f = object.__getattribute__(self, '_orig').makefile(mode, bufsize)
> +
> +        logfp = object.__getattribute__(self, '_logfp')
> +        closeafterrecvbytes = object.__getattribute__(self,
> +
> '_closeafterrecvbytes')
> +        closeaftersendbytes = object.__getattribute__(self,
> +
> '_closeaftersendbytes')
> +
> +        return fileobjectproxy(f, logfp,
> +                               closeafterrecvbytes=closeafterrecvbytes,
> +                               closeaftersendbytes=closeaftersendbytes)
> +
> +# We can't adjust __class__ on socket._fileobject, so define a proxy.
> +class fileobjectproxy(object):
> +    __slots__ = (
> +        '_orig',
> +        '_logfp',
> +        '_closeafterrecvbytes',
> +        '_closeaftersendbytes',
> +    )
> +
> +    def __init__(self, obj, logfp, closeafterrecvbytes=0,
> +                 closeaftersendbytes=0):
> +        object.__setattr__(self, '_orig', obj)
> +        object.__setattr__(self, '_logfp', logfp)
> +        object.__setattr__(self, '_closeafterrecvbytes',
> closeafterrecvbytes)
> +        object.__setattr__(self, '_closeaftersendbytes',
> closeaftersendbytes)
> +
> +    def __getattribute__(self, name):
> +        if name in ('read', 'readline', 'write', '_writelog'):
> +            return object.__getattribute__(self, name)
> +
> +        return getattr(object.__getattribute__(self, '_orig'), name)
> +
> +    def __delattr__(self, name):
> +        delattr(object.__getattribute__(self, '_orig'), name)
> +
> +    def __setattr__(self, name, value):
> +        setattr(object.__getattribute__(self, '_orig'), name, value)
> +
> +    def _writelog(self, msg):
> +        msg = msg.replace('\r', '\\r').replace('\n', '\\n')
> +
> +        object.__getattribute__(self, '_logfp').write(msg)
> +        object.__getattribute__(self, '_logfp').write('\n')
> +
> +    def read(self, size=-1):
> +        remaining = object.__getattribute__(self, '_closeafterrecvbytes')
> +
> +        # No read limit. Call original function.
> +        if not remaining:
> +            result = object.__getattribute__(self, '_orig').read(size)
> +            self._writelog('read(%d) -> (%d) (%s) %s' % (size,
> +                                                           len(result),
> +                                                           result))
> +            return result
> +
> +        origsize = size
> +
> +        if size < 0:
> +            size = remaining
> +        else:
> +            size = min(remaining, size)
> +
> +        result = object.__getattribute__(self, '_orig').read(size)
> +        remaining -= len(result)
> +
> +        self._writelog('read(%d from %d) -> (%d) %s' % (
> +            size, origsize, len(result), result))
> +
> +        object.__setattr__(self, '_closeafterrecvbytes', remaining)
> +
> +        if remaining <= 0:
> +            self._writelog('read limit reached, closing socket')
> +            self._sock.close()
> +            # This is the easiest way to abort the current request.
> +            raise Exception('connection closed after receiving N bytes')
> +
> +        return result
> +
> +    def readline(self, size=-1):
> +        remaining = object.__getattribute__(self, '_closeafterrecvbytes')
> +
> +        # No read limit. Call original function.
> +        if not remaining:
> +            result = object.__getattribute__(self, '_orig').readline(size)
> +            self._writelog('readline(%d) -> (%d) %s' % (
> +                size, len(result), result))
> +            return result
> +
> +        origsize = size
> +
> +        if size < 0:
> +            size = remaining
> +        else:
> +            size = min(remaining, size)
> +
> +        result = object.__getattribute__(self, '_orig').readline(size)
> +        remaining -= len(result)
> +
> +        self._writelog('readline(%d from %d) -> (%d) %s' % (
> +            size, origsize, len(result), result))
> +
> +        object.__setattr__(self, '_closeafterrecvbytes', remaining)
> +
> +        if remaining <= 0:
> +            self._writelog('read limit reached; closing socket')
> +            self._sock.close()
> +            # This is the easiest way to abort the current request.
> +            raise Exception('connection closed after receiving N bytes')
> +
> +        return result
> +
> +    def write(self, data):
> +        remaining = object.__getattribute__(self, '_closeaftersendbytes')
> +
> +        # No byte limit on this operation. Call original function.
> +        if not remaining:
> +            self._writelog('write(%d) -> %s' % (len(data), data))
> +            result = object.__getattribute__(self, '_orig').write(data)
> +            return result
> +
> +        if len(data) > remaining:
> +            newdata = data[0:remaining]
> +        else:
> +            newdata = data
> +
> +        remaining -= len(newdata)
> +
> +        self._writelog('write(%d from %d) -> (%d) %s' % (
> +            len(newdata), len(data), remaining, newdata))
> +
> +        result = object.__getattribute__(self, '_orig').write(newdata)
> +
> +        object.__setattr__(self, '_closeaftersendbytes', remaining)
> +
> +        if remaining <= 0:
> +            self._writelog('write limit reached; closing socket')
> +            self._sock.close()
> +            raise Exception('connection closed after sending N bytes')
> +
> +        return result
> +
> +def extsetup(ui):
> +    # Change the base HTTP server class so various events can be
> performed.
> +    # See SocketServer.BaseServer for how the specially named methods
> work.
> +    class badserver(server.MercurialHTTPServer):
> +        def __init__(self, ui, *args, **kwargs):
> +            self._ui = ui
> +            super(badserver, self).__init__(ui, *args, **kwargs)
> +
> +            # Need to inherit object so super() works.
> +            class badrequesthandler(self.RequestHandlerClass, object):
> +                def send_header(self, name, value):
> +                    # Make headers deterministic to facilitate testing.
> +                    if name.lower() == 'date':
> +                        value = 'Fri, 14 Apr 2017 00:00:00 GMT'
> +                    elif name.lower() == 'server':
> +                        value = 'badhttpserver'
> +
> +                    return super(badrequesthandler,
> self).send_header(name,
> +
> value)
> +
> +            self.RequestHandlerClass = badrequesthandler
> +
> +        # Called to accept() a pending socket.
> +        def get_request(self):
> +            if self._ui.configbool('badserver', 'closebeforeaccept'):
> +                self.socket.close()
> +
> +                # Tells the server to stop processing more requests.
> +                self.__shutdown_request = True
> +
> +                # Simulate failure to stop processing this request.
> +                raise socket.error('close before accept')
> +
> +            if self._ui.configbool('badserver', 'closeafteraccept'):
> +                request, client_address = super(badserver,
> self).get_request()
> +                request.close()
> +                raise socket.error('close after accept')
> +
> +            return super(badserver, self).get_request()
> +
> +        # Does heavy lifting of processing a request. Invokes
> +        # self.finish_request() which calls self.RequestHandlerClass()
> which
> +        # is a hgweb.server._httprequesthandler.
> +        def process_request(self, socket, address):
> +            # Wrap socket in a proxy if we need to count bytes.
> +            closeafterrecvbytes = self._ui.configint('badserver',
> +
>  'closeafterrecvbytes', 0)
> +            closeaftersendbytes = self._ui.configint('badserver',
> +
>  'closeaftersendbytes', 0)
> +
> +            if closeafterrecvbytes or closeaftersendbytes:
> +                socket = socketproxy(socket, self.errorlog,
> +                                     closeafterrecvbytes=
> closeafterrecvbytes,
> +                                     closeaftersendbytes=
> closeaftersendbytes)
> +
> +            return super(badserver, self).process_request(socket, address)
> +
> +    server.MercurialHTTPServer = badserver
> diff --git a/tests/test-check-code.t b/tests/test-check-code.t
> --- a/tests/test-check-code.t
> +++ b/tests/test-check-code.t
> @@ -40,6 +40,7 @@ New errors are not allowed. Warnings are
>     >     policy = os.environ.get('HGMODULEPOLICY', policy)
>     use encoding.environ instead (py3)
>    Skipping mercurial/statprof.py it has no-che?k-code (glob)
> +  Skipping tests/badserverext.py it has no-che?k-code (glob)
>    [1]
>
>  @commands in debugcommands.py should be in alphabetical order.
> diff --git a/tests/test-http-bad-server.t b/tests/test-http-bad-server.t
> new file mode 100644
> --- /dev/null
> +++ b/tests/test-http-bad-server.t
> @@ -0,0 +1,896 @@
> +#require killdaemons serve zstd
> +
> +Client version is embedded in HTTP request and is effectively dynamic.
> Pin the
> +version so behavior is deterministic.
> +
> +  $ cat > fakeversion.py << EOF
> +  > from mercurial import util
> +  > util.version = lambda: '4.2'
> +  > EOF
> +
> +  $ cat >> $HGRCPATH << EOF
> +  > [extensions]
> +  > fakeversion = `pwd`/fakeversion.py
> +  > EOF
> +
> +  $ hg init server0
> +  $ cd server0
> +  $ touch foo
> +  $ hg -q commit -A -m initial
> +
> +Also disable compression because zstd is optional and causes output to
> vary
> +and because debugging partial responses is hard when compression is
> involved
> +
> +  $ cat > .hg/hgrc << EOF
> +  > [extensions]
> +  > badserver = $TESTDIR/badserverext.py
> +  > [server]
> +  > compressionengines = none
> +  > EOF
> +
> +Failure to accept() socket should result in connection related error
> message
> +
> +  $ hg --config badserver.closebeforeaccept=true serve -p $HGPORT -d
> --pid-file=hg.pid
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  abort: error: Connection reset by peer
> +  [255]
> +
> +(The server exits on its own, but there is a race between that and
> starting a new server.
> +So ensure the process is dead.)
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +Failure immediately after accept() should yield connection related error
> message
> +
> +  $ hg --config badserver.closeafteraccept=true serve -p $HGPORT -d
> --pid-file=hg.pid
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  abort: error: Connection reset by peer
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +Failure to read all bytes in initial HTTP request should yield connection
> related error message
> +
> +  $ hg --config badserver.closeafterrecvbytes=1 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +TODO this error message is not very good
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  abort: error: ''
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(1 from 65537) -> (1) G
> +  read limit reached; closing socket
> +
> +  $ rm -f error.log
> +
> +Same failure, but server reads full HTTP request line
> +
> +  $ hg --config badserver.closeafterrecvbytes=40 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  abort: error: ''
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(40 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
> +  readline(7 from -1) -> (7) Accept-
> +  read limit reached; closing socket
> +
> +  $ rm -f error.log
> +
> +Failure on subsequent HTTP request on the same socket (cmd?batch)
> +
> +  $ hg --config badserver.closeafterrecvbytes=210 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  abort: error: ''
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(210 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
> +  readline(177 from -1) -> (27) Accept-Encoding: identity\r\n
> +  readline(150 from -1) -> (8) vary: \r\n
> +  readline(142 from -1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(107 from -1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(84 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
> 4.2)\r\n
> +  readline(35 from -1) -> (2) \r\n
> +  write(36) -> HTTP/1.1 200 Script output follows\r\n
> +  write(23) -> Server: badhttpserver\r\n
> +  write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41) -> Content-Type: application/mercurial-0.1\r\n
> +  write(21) -> Content-Length: 405\r\n
> +  write(2) -> \r\n
> +  write(405) -> lookup changegroupsubset branchmap pushkey known
> getbundle unbundlehash batch streamreqs=generaldelta,revlogv1
> bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%
> 2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%
> 2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
> unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx
> compression=none
> +  readline(33 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> +  readline(7 from -1) -> (7) Accept-
> +  read limit reached; closing socket
> +  readline(210 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> +  readline(184 from -1) -> (27) Accept-Encoding: identity\r\n
> +  readline(157 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(128 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\
> r\n
> +  readline(87 from -1) -> (48) x-hgproto-1: 0.1 0.2
> comp=zstd,zlib,none,bzip2\r\n
> +  readline(39 from -1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(4 from -1) -> (4) host
> +  read limit reached; closing socket
> +
> +  $ rm -f error.log
> +
> +Failure to read getbundle HTTP request
> +
> +  $ hg --config badserver.closeafterrecvbytes=300 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  abort: error: ''
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(300 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
> +  readline(267 from -1) -> (27) Accept-Encoding: identity\r\n
> +  readline(240 from -1) -> (8) vary: \r\n
> +  readline(232 from -1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(197 from -1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(174 from -1) -> (49) user-agent: mercurial/proto-1.0
> (Mercurial 4.2)\r\n
> +  readline(125 from -1) -> (2) \r\n
> +  write(36) -> HTTP/1.1 200 Script output follows\r\n
> +  write(23) -> Server: badhttpserver\r\n
> +  write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41) -> Content-Type: application/mercurial-0.1\r\n
> +  write(21) -> Content-Length: 405\r\n
> +  write(2) -> \r\n
> +  write(405) -> lookup changegroupsubset branchmap pushkey known
> getbundle unbundlehash batch streamreqs=generaldelta,revlogv1
> bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%
> 2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%
> 2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
> unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx
> compression=none
> +  readline(123 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> +  readline(97 from -1) -> (27) Accept-Encoding: identity\r\n
> +  readline(70 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(41 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\
> r\n
> +  read limit reached; closing socket
> +  readline(300 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> +  readline(274 from -1) -> (27) Accept-Encoding: identity\r\n
> +  readline(247 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(218 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\
> r\n
> +  readline(177 from -1) -> (48) x-hgproto-1: 0.1 0.2
> comp=zstd,zlib,none,bzip2\r\n
> +  readline(129 from -1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(94 from -1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(71 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
> 4.2)\r\n
> +  readline(22 from -1) -> (2) \r\n
> +  write(36) -> HTTP/1.1 200 Script output follows\r\n
> +  write(23) -> Server: badhttpserver\r\n
> +  write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41) -> Content-Type: application/mercurial-0.1\r\n
> +  write(20) -> Content-Length: 42\r\n
> +  write(2) -> \r\n
> +  write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
> +  readline(20 from 65537) -> (20) GET /?cmd=getbundle
> +  read limit reached; closing socket
> +  readline(300 from 65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
> +  readline(270 from -1) -> (27) Accept-Encoding: identity\r\n
> +  readline(243 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(214 from -1) -> (214) x-hgarg-1: bundlecaps=HG20%2Cbundle2%
> 3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%
> 252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%
> 252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%2
> +  read limit reached; closing socket
> +
> +  $ rm -f error.log
> +
> +Now do a variation using POST to send arguments
> +
> +  $ hg --config experimental.httppostargs=true --config
> badserver.closeafterrecvbytes=315 serve -p $HGPORT -d --pid-file=hg.pid
> -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  abort: error: ''
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(315 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
> +  readline(282 from -1) -> (27) Accept-Encoding: identity\r\n
> +  readline(255 from -1) -> (8) vary: \r\n
> +  readline(247 from -1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(212 from -1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(189 from -1) -> (49) user-agent: mercurial/proto-1.0
> (Mercurial 4.2)\r\n
> +  readline(140 from -1) -> (2) \r\n
> +  write(36) -> HTTP/1.1 200 Script output follows\r\n
> +  write(23) -> Server: badhttpserver\r\n
> +  write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41) -> Content-Type: application/mercurial-0.1\r\n
> +  write(21) -> Content-Length: 418\r\n
> +  write(2) -> \r\n
> +  write(418) -> lookup changegroupsubset branchmap pushkey known
> getbundle unbundlehash batch streamreqs=generaldelta,revlogv1
> bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%
> 2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%
> 2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
> unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httppostargs
> httpmediatype=0.1rx,0.1tx,0.2tx compression=none
> +  readline(138 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
> +  readline(111 from -1) -> (27) Accept-Encoding: identity\r\n
> +  readline(84 from -1) -> (41) content-type: application/mercurial-0.1\r\n
> +  readline(43 from -1) -> (19) vary: X-HgProto-1\r\n
> +  readline(24 from -1) -> (19) x-hgargs-post: 28\r\n
> +  readline(5 from -1) -> (5) x-hgp
> +  read limit reached; closing socket
> +  readline(315 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
> +  readline(288 from -1) -> (27) Accept-Encoding: identity\r\n
> +  readline(261 from -1) -> (41) content-type:
> application/mercurial-0.1\r\n
> +  readline(220 from -1) -> (19) vary: X-HgProto-1\r\n
> +  readline(201 from -1) -> (19) x-hgargs-post: 28\r\n
> +  readline(182 from -1) -> (48) x-hgproto-1: 0.1 0.2
> comp=zstd,zlib,none,bzip2\r\n
> +  readline(134 from -1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(99 from -1) -> (20) content-length: 28\r\n
> +  readline(79 from -1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(56 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
> 4.2)\r\n
> +  readline(7 from -1) -> (2) \r\n
> +  read(5 from 28) -> (5) cmds=
> +  read limit reached, closing socket
> +  write(36) -> HTTP/1.1 500 Internal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Now move on to partial server responses
> +
> +Server sends a single character from the HTTP response line
> +
> +  $ hg --config badserver.closeaftersendbytes=1 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  abort: error: H
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (8) vary: \r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(1 from 36) -> (0) H
> +  write limit reached; closing socket
> +  write(36) -> HTTP/1.1 500 Internal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server sends an incomplete capabilities response body
> +
> +  $ hg --config badserver.closeaftersendbytes=180 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +TODO client spews a stack due to uncaught httplib.IncompleteRead
> +
> +  $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
> +  [1]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (8) vary: \r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (144) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (121) Server: badhttpserver\r\n
> +  write(37 from 37) -> (84) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (43) Content-Type: application/mercurial-0.1\r\n
> +  write(21 from 21) -> (22) Content-Length: 405\r\n
> +  write(2 from 2) -> (20) \r\n
> +  write(20 from 405) -> (0) lookup changegroupsu
> +  write limit reached; closing socket
> +
> +  $ rm -f error.log
> +
> +Server sends incomplete headers for batch request
> +
> +  $ hg --config badserver.closeaftersendbytes=695 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +TODO this output is horrible
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  abort: 'http://localhost:$HGPORT/' does not appear to be an hg
> repository:
> +  ---%<--- (application/mercuria)
> +
> +  ---%<---
> +  !
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (8) vary: \r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (659) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (636) Server: badhttpserver\r\n
> +  write(37 from 37) -> (599) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (558) Content-Type: application/mercurial-0.1\r\n
> +  write(21 from 21) -> (537) Content-Length: 405\r\n
> +  write(2 from 2) -> (535) \r\n
> +  write(405 from 405) -> (130) lookup changegroupsubset branchmap pushkey
> known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1
> bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%
> 2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%
> 2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
> unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx
> compression=none
> +  readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
> +  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (94) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (71) Server: badhttpserver\r\n
> +  write(37 from 37) -> (34) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(34 from 41) -> (0) Content-Type: application/mercuria
> +  write limit reached; closing socket
> +  write(36) -> HTTP/1.1 500 Internal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server sends an incomplete HTTP response body to batch request
> +
> +  $ hg --config badserver.closeaftersendbytes=760 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +TODO client spews a stack due to uncaught ValueError in batch.results()
> +  $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
> +  [1]
>

I didn't fix this failure in this series. The reason is it is a bit of a
rabbit hole.

wirepeer._submitbatch() is essentially doing read(1024) in a loop until it
sees an empty string. The problem with this is that HTTPResponse never
attempts to verify we've actually hit EOF. It is capable of knowing when
we've hit the end of an HTTP body because it either knows the
Content-Length or has encountered a 0 sized chunked-transfer chunk. So what
ends up happening in this test is we have a partial HTTP response body not
fulfilling the Content-Length obligation. HTTPResponse.read() happily
returns a partial read followed by an empty string. And
wirepeer._submitbatch() thinks it has hit EOF. This results in a parse
failure later because we're operating on incomplete data.

I think we need to teach keepalive.HTTPResponse about Content-Length and 0
sized chunks so it can raise IncompleteRead when a read() operation can't
return anything and the HTTP body hasn't been exhausted. This is not going
to be fun to implement. I figured I'd write up a description of the problem
because Augie has dabbled in this code and may be able to warn me about the
dragons.


> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (8) vary: \r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (724) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (701) Server: badhttpserver\r\n
> +  write(37 from 37) -> (664) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (623) Content-Type: application/mercurial-0.1\r\n
> +  write(21 from 21) -> (602) Content-Length: 405\r\n
> +  write(2 from 2) -> (600) \r\n
> +  write(405 from 405) -> (195) lookup changegroupsubset branchmap pushkey
> known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1
> bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%
> 2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%
> 2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
> unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx
> compression=none
> +  readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
> +  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (159) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (136) Server: badhttpserver\r\n
> +  write(37 from 37) -> (99) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (58) Content-Type: application/mercurial-0.1\r\n
> +  write(20 from 20) -> (38) Content-Length: 42\r\n
> +  write(2 from 2) -> (36) \r\n
> +  write(36 from 42) -> (0) 96ee1d7354c4ad7372047672c36a1f561e3a
> +  write limit reached; closing socket
> +
> +  $ rm -f error.log
> +
> +Server sends incomplete headers for getbundle response
> +
> +  $ hg --config badserver.closeaftersendbytes=895 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +TODO this output is terrible
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  abort: 'http://localhost:$HGPORT/' does not appear to be an hg
> repository:
> +  ---%<--- (application/mercuri)
> +
> +  ---%<---
> +  !
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (8) vary: \r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (859) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (836) Server: badhttpserver\r\n
> +  write(37 from 37) -> (799) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (758) Content-Type: application/mercurial-0.1\r\n
> +  write(21 from 21) -> (737) Content-Length: 405\r\n
> +  write(2 from 2) -> (735) \r\n
> +  write(405 from 405) -> (330) lookup changegroupsubset branchmap pushkey
> known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1
> bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%
> 2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%
> 2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
> unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx
> compression=none
> +  readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
> +  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (294) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (271) Server: badhttpserver\r\n
> +  write(37 from 37) -> (234) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (193) Content-Type: application/mercurial-0.1\r\n
> +  write(20 from 20) -> (173) Content-Length: 42\r\n
> +  write(2 from 2) -> (171) \r\n
> +  write(42 from 42) -> (129) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
> +  readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%
> 3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%
> 252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%
> 252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%
> 250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=
> 0000000000000000000000000000000000000000&heads=
> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
> +  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (93) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (70) Server: badhttpserver\r\n
> +  write(37 from 37) -> (33) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(33 from 41) -> (0) Content-Type: application/mercuri
> +  write limit reached; closing socket
> +  write(36) -> HTTP/1.1 500 Internal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server sends empty HTTP body for getbundle
> +
> +  $ hg --config badserver.closeaftersendbytes=933 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +TODO client spews a stack due to uncaught httplib.IncompleteRead
> +
> +  $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
> +  requesting all changes
> +  [1]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (8) vary: \r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (897) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (874) Server: badhttpserver\r\n
> +  write(37 from 37) -> (837) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (796) Content-Type: application/mercurial-0.1\r\n
> +  write(21 from 21) -> (775) Content-Length: 405\r\n
> +  write(2 from 2) -> (773) \r\n
> +  write(405 from 405) -> (368) lookup changegroupsubset branchmap pushkey
> known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1
> bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%
> 2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%
> 2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
> unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx
> compression=none
> +  readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
> +  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (332) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (309) Server: badhttpserver\r\n
> +  write(37 from 37) -> (272) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (231) Content-Type: application/mercurial-0.1\r\n
> +  write(20 from 20) -> (211) Content-Length: 42\r\n
> +  write(2 from 2) -> (209) \r\n
> +  write(42 from 42) -> (167) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
> +  readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%
> 3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%
> 252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%
> 252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%
> 250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=
> 0000000000000000000000000000000000000000&heads=
> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
> +  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (131) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (108) Server: badhttpserver\r\n
> +  write(37 from 37) -> (71) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (30) Content-Type: application/mercurial-0.2\r\n
> +  write(28 from 28) -> (2) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (0) \r\n
> +  write limit reached; closing socket
> +  write(36) -> HTTP/1.1 500 Internal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server sends partial compression string
> +
> +  $ hg --config badserver.closeaftersendbytes=945 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +TODO client spews a stack due to uncaught httplib.IncompleteRead
> +
> +  $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
> +  requesting all changes
> +  [1]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ cat error.log
> +  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (8) vary: \r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (909) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (886) Server: badhttpserver\r\n
> +  write(37 from 37) -> (849) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (808) Content-Type: application/mercurial-0.1\r\n
> +  write(21 from 21) -> (787) Content-Length: 405\r\n
> +  write(2 from 2) -> (785) \r\n
> +  write(405 from 405) -> (380) lookup changegroupsubset branchmap pushkey
> known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1
> bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%
> 2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%
> 2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
> unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx
> compression=none
> +  readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
> +  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (344) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (321) Server: badhttpserver\r\n
> +  write(37 from 37) -> (284) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (243) Content-Type: application/mercurial-0.1\r\n
> +  write(20 from 20) -> (223) Content-Length: 42\r\n
> +  write(2 from 2) -> (221) \r\n
> +  write(42 from 42) -> (179) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
> +  readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
> +  readline(-1) -> (27) Accept-Encoding: identity\r\n
> +  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%
> 3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%
> 252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%
> 252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%
> 250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=
> 0000000000000000000000000000000000000000&heads=
> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
> +  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
> +  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> +  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(-1) -> (2) \r\n
> +  write(36 from 36) -> (143) HTTP/1.1 200 Script output follows\r\n
> +  write(23 from 23) -> (120) Server: badhttpserver\r\n
> +  write(37 from 37) -> (83) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> +  write(41 from 41) -> (42) Content-Type: application/mercurial-0.2\r\n
> +  write(28 from 28) -> (14) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (12) \r\n
> +  write(6 from 6) -> (6) 1\\r\\n\x04\\r\\n (esc)
> +  write(6 from 9) -> (0) 4\r\nnon
> +  write limit reached; closing socket
> +  write(27) -> 15\r\nInternal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server sends partial bundle2 header magic
> +
> +  $ hg --config badserver.closeaftersendbytes=954 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  abort: connection ended unexpectedly
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ tail -7 error.log
> +  write(28 from 28) -> (23) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (21) \r\n
> +  write(6 from 6) -> (15) 1\\r\\n\x04\\r\\n (esc)
> +  write(9 from 9) -> (6) 4\r\nnone\r\n
> +  write(6 from 9) -> (0) 4\r\nHG2
> +  write limit reached; closing socket
> +  write(27) -> 15\r\nInternal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server sends incomplete bundle2 stream params length
> +
> +  $ hg --config badserver.closeaftersendbytes=963 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  abort: connection ended unexpectedly
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ tail -8 error.log
> +  write(28 from 28) -> (32) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (30) \r\n
> +  write(6 from 6) -> (24) 1\\r\\n\x04\\r\\n (esc)
> +  write(9 from 9) -> (15) 4\r\nnone\r\n
> +  write(9 from 9) -> (6) 4\r\nHG20\r\n
> +  write(6 from 9) -> (0) 4\\r\\n\x00\x00\x00 (esc)
> +  write limit reached; closing socket
> +  write(27) -> 15\r\nInternal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Servers stops after bundle2 stream params header
> +
> +  $ hg --config badserver.closeaftersendbytes=966 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  abort: connection ended unexpectedly
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ tail -8 error.log
> +  write(28 from 28) -> (35) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (33) \r\n
> +  write(6 from 6) -> (27) 1\\r\\n\x04\\r\\n (esc)
> +  write(9 from 9) -> (18) 4\r\nnone\r\n
> +  write(9 from 9) -> (9) 4\r\nHG20\r\n
> +  write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write limit reached; closing socket
> +  write(27) -> 15\r\nInternal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server stops sending after bundle2 part header length
> +
> +  $ hg --config badserver.closeaftersendbytes=975 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  abort: connection ended unexpectedly
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ tail -9 error.log
> +  write(28 from 28) -> (44) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (42) \r\n
> +  write(6 from 6) -> (36) 1\\r\\n\x04\\r\\n (esc)
> +  write(9 from 9) -> (27) 4\r\nnone\r\n
> +  write(9 from 9) -> (18) 4\r\nHG20\r\n
> +  write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
> +  write limit reached; closing socket
> +  write(27) -> 15\r\nInternal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server stops sending after bundle2 part header
> +
> +  $ hg --config badserver.closeaftersendbytes=1022 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  adding changesets
> +  transaction abort!
> +  rollback completed
> +  abort: stream ended unexpectedly (got 0 bytes, expected 4)
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ tail -10 error.log
> +  write(28 from 28) -> (91) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (89) \r\n
> +  write(6 from 6) -> (83) 1\\r\\n\x04\\r\\n (esc)
> +  write(9 from 9) -> (74) 4\r\nnone\r\n
> +  write(9 from 9) -> (65) 4\r\nHG20\r\n
> +  write(9 from 9) -> (56) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (47) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
> +  write(47 from 47) -> (0) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02
>    \x01version02nbchanges1\\r\\n (esc)
> +  write limit reached; closing socket
> +  write(27) -> 15\r\nInternal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server stops after bundle2 part payload chunk size
> +
> +  $ hg --config badserver.closeaftersendbytes=1031 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  adding changesets
> +  transaction abort!
> +  rollback completed
> +  abort: stream ended unexpectedly (got 0 bytes, expected 4)
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ tail -11 error.log
> +  write(28 from 28) -> (100) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (98) \r\n
> +  write(6 from 6) -> (92) 1\\r\\n\x04\\r\\n (esc)
> +  write(9 from 9) -> (83) 4\r\nnone\r\n
> +  write(9 from 9) -> (74) 4\r\nHG20\r\n
> +  write(9 from 9) -> (65) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (56) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
> +  write(47 from 47) -> (9) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02
>    \x01version02nbchanges1\\r\\n (esc)
> +  write(9 from 9) -> (0) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
> +  write limit reached; closing socket
> +  write(27) -> 15\r\nInternal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server stops sending in middle of bundle2 payload chunk
> +
> +  $ hg --config badserver.closeaftersendbytes=1504 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  adding changesets
> +  transaction abort!
> +  rollback completed
> +  abort: stream ended unexpectedly (got 0 bytes, expected 4)
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ tail -12 error.log
> +  write(28 from 28) -> (573) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (571) \r\n
> +  write(6 from 6) -> (565) 1\\r\\n\x04\\r\\n (esc)
> +  write(9 from 9) -> (556) 4\r\nnone\r\n
> +  write(9 from 9) -> (547) 4\r\nHG20\r\n
> +  write(9 from 9) -> (538) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (529) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
> +  write(47 from 47) -> (482) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02
>  \x01version02nbchanges1\\r\\n (esc)
> +  write(9 from 9) -> (473) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
> +  write(473 from 473) -> (0) 1d2\\r\\n\x00\x00\x00\xb2\x96\
> xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0
> 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\
> xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad1
> 44ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\
> x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\
> xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\
> x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
> +  write limit reached; closing socket
> +  write(27) -> 15\r\nInternal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server stops sending after 0 length payload chunk size
> +
> +  $ hg --config badserver.closeaftersendbytes=1513 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  adding changesets
> +  adding manifests
> +  adding file changes
> +  added 1 changesets with 1 changes to 1 files
> +  transaction abort!
> +  rollback completed
> +  abort: connection ended unexpectedly
> +  [255]
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ tail -13 error.log
> +  write(28 from 28) -> (582) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (580) \r\n
> +  write(6 from 6) -> (574) 1\\r\\n\x04\\r\\n (esc)
> +  write(9 from 9) -> (565) 4\r\nnone\r\n
> +  write(9 from 9) -> (556) 4\r\nHG20\r\n
> +  write(9 from 9) -> (547) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (538) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
> +  write(47 from 47) -> (491) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02
>  \x01version02nbchanges1\\r\\n (esc)
> +  write(9 from 9) -> (482) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
> +  write(473 from 473) -> (9) 1d2\\r\\n\x00\x00\x00\xb2\x96\
> xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0
> 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\
> xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad1
> 44ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\
> x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\
> xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\
> x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write limit reached; closing socket
> +  write(27) -> 15\r\nInternal Server Error\r\n
> +
> +  $ rm -f error.log
> +
> +Server stops sending after 0 part bundle part header (indicating end of
> bundle2 payload)
> +This is before the 0 size chunked transfer part that signals end of HTTP
> response.
> +
> +  $ hg --config badserver.closeaftersendbytes=1710 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  adding changesets
> +  adding manifests
> +  adding file changes
> +  added 1 changesets with 1 changes to 1 files
> +  updating to branch default
> +  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ tail -22 error.log
> +  write(28 from 28) -> (779) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (777) \r\n
> +  write(6 from 6) -> (771) 1\\r\\n\x04\\r\\n (esc)
> +  write(9 from 9) -> (762) 4\r\nnone\r\n
> +  write(9 from 9) -> (753) 4\r\nHG20\r\n
> +  write(9 from 9) -> (744) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (735) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
> +  write(47 from 47) -> (688) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02
>  \x01version02nbchanges1\\r\\n (esc)
> +  write(9 from 9) -> (679) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
> +  write(473 from 473) -> (206) 1d2\\r\\n\x00\x00\x00\xb2\x96\
> xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0
> 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\
> xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad1
> 44ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\
> x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\
> xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\
> x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (197) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (188) 4\\r\\n\x00\x00\x00 \\r\\n (esc)
> +  write(38 from 38) -> (150) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00
>     \x06namespacephases\\r\\n (esc)
> +  write(9 from 9) -> (141) 4\\r\\n\x00\x00\x00:\\r\\n (esc)
> +  write(64 from 64) -> (77) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c
>    1\npublishing   True\r\n
> +  write(9 from 9) -> (68) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (59) 4\\r\\n\x00\x00\x00#\\r\\n (esc)
> +  write(41 from 41) -> (18) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00
>              namespacebookmarks\\r\\n (esc)
> +  write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write limit reached; closing socket
> +  write(27) -> 15\r\nInternal Server Error\r\n
> +
> +  $ rm -f error.log
> +  $ rm -rf clone
> +
> +Server sends a size 0 chunked-transfer size without terminating \r\n
> +
> +  $ hg --config badserver.closeaftersendbytes=1713 serve -p $HGPORT -d
> --pid-file=hg.pid -E error.log
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  requesting all changes
> +  adding changesets
> +  adding manifests
> +  adding file changes
> +  added 1 changesets with 1 changes to 1 files
> +  updating to branch default
> +  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +  $ tail -23 error.log
> +  write(28 from 28) -> (782) Transfer-Encoding: chunked\r\n
> +  write(2 from 2) -> (780) \r\n
> +  write(6 from 6) -> (774) 1\\r\\n\x04\\r\\n (esc)
> +  write(9 from 9) -> (765) 4\r\nnone\r\n
> +  write(9 from 9) -> (756) 4\r\nHG20\r\n
> +  write(9 from 9) -> (747) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (738) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
> +  write(47 from 47) -> (691) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02
>  \x01version02nbchanges1\\r\\n (esc)
> +  write(9 from 9) -> (682) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
> +  write(473 from 473) -> (209) 1d2\\r\\n\x00\x00\x00\xb2\x96\
> xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0
> 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\
> xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad1
> 44ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\
> x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\
> xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\
> x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\
> x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (200) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (191) 4\\r\\n\x00\x00\x00 \\r\\n (esc)
> +  write(38 from 38) -> (153) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00
>     \x06namespacephases\\r\\n (esc)
> +  write(9 from 9) -> (144) 4\\r\\n\x00\x00\x00:\\r\\n (esc)
> +  write(64 from 64) -> (80) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c
>    1\npublishing   True\r\n
> +  write(9 from 9) -> (71) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (62) 4\\r\\n\x00\x00\x00#\\r\\n (esc)
> +  write(41 from 41) -> (21) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00
>              namespacebookmarks\\r\\n (esc)
> +  write(9 from 9) -> (12) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(9 from 9) -> (3) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
> +  write(3 from 5) -> (0) 0\r\n
> +  write limit reached; closing socket
> +  write(27) -> 15\r\nInternal Server Error\r\n
> +
> +  $ rm -f error.log
> +  $ rm -rf clone
>
Yuya Nishihara - April 16, 2017, 3:30 a.m.
On Fri, 14 Apr 2017 00:44:07 -0700, Gregory Szorc wrote:
> # HG changeset patch
> # User Gregory Szorc <gregory.szorc@gmail.com>
> # Date 1492147168 25200
> #      Thu Apr 13 22:19:28 2017 -0700
> # Node ID 80bd24abcf67d5dc8b5f5bf83c796e1f71fc5bd9
> # Parent  283a95fe4ce5b91af4e2ed8ed1d3fe18ef3544bb
> tests: add tests for poorly behaving HTTP server

> +closebeforeaccept
> +   If true, close() the server socket when a new connection arrives before
> +   accept() is called. The server will then exit.
> +
> +closeafteraccept
> +   If true, the server will close() the client socket immediately after
> +   accept().

Just a nitpick. IIRC, it's the same on TCP layer to close before/after accept()
because the initial handshake would be handled in background. But these options
might be good for test coverage.
Pierre-Yves David - April 17, 2017, 7:11 p.m.
On 04/14/2017 09:44 AM, Gregory Szorc wrote:
> # HG changeset patch
> # User Gregory Szorc <gregory.szorc@gmail.com>
> # Date 1492147168 25200
> #      Thu Apr 13 22:19:28 2017 -0700
> # Node ID 80bd24abcf67d5dc8b5f5bf83c796e1f71fc5bd9
> # Parent  283a95fe4ce5b91af4e2ed8ed1d3fe18ef3544bb
> tests: add tests for poorly behaving HTTP server

I'm seeing failure for the test introduced by this test. I had no time 
to investigated further, the machine is a power8 running centos7:

Linux gcc2-power8.osuosl.org 3.10.0-514.10.2.el7.ppc64le #1 SMP Fri Mar 
3 16:16:38 GMT 2017 ppc64le ppc64le ppc64le GNU/Linux

Observed failure:

> --- /home/marmoute/mercurial-testing/tests/test-http-bad-server.t
> +++ /home/marmoute/mercurial-testing/tests/test-http-bad-server.t.err
> @@ -105,9 +105,9 @@
>    readline(210 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>    readline(177 from -1) -> (27) Accept-Encoding: identity\r\n
>    readline(150 from -1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(115 from -1) -> (23) host: localhost:$HGPORT\r\n
> -  readline(92 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> -  readline(43 from -1) -> (2) \r\n
> +  readline(115 from -1) -> (22) host: localhost:$HGPORT\r\n
> +  readline(93 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(44 from -1) -> (2) \r\n
>    write(36) -> HTTP/1.1 200 Script output follows\r\n
>    write(23) -> Server: badhttpserver\r\n
>    write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> @@ -115,8 +115,8 @@
>    write(21) -> Content-Length: 405\r\n
>    write(2) -> \r\n
>    write(405) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
> -  readline(41 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> -  readline(15 from -1) -> (15) Accept-Encoding
> +  readline(42 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> +  readline(16 from -1) -> (16) Accept-Encoding:
>    read limit reached; closing socket
>    readline(210 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
>    readline(184 from -1) -> (27) Accept-Encoding: identity\r\n
> @@ -144,9 +144,9 @@
>    readline(292 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>    readline(259 from -1) -> (27) Accept-Encoding: identity\r\n
>    readline(232 from -1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(197 from -1) -> (23) host: localhost:$HGPORT\r\n
> -  readline(174 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> -  readline(125 from -1) -> (2) \r\n
> +  readline(197 from -1) -> (22) host: localhost:$HGPORT\r\n
> +  readline(175 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(126 from -1) -> (2) \r\n
>    write(36) -> HTTP/1.1 200 Script output follows\r\n
>    write(23) -> Server: badhttpserver\r\n
>    write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> @@ -154,10 +154,11 @@
>    write(21) -> Content-Length: 405\r\n
>    write(2) -> \r\n
>    write(405) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
> -  readline(123 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> -  readline(97 from -1) -> (27) Accept-Encoding: identity\r\n
> -  readline(70 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> -  readline(41 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
> +  readline(124 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
> +  readline(98 from -1) -> (27) Accept-Encoding: identity\r\n
> +  readline(71 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
> +  readline(42 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
> +  readline(1 from -1) -> (1) x
>    read limit reached; closing socket
>    readline(292 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
>    readline(266 from -1) -> (27) Accept-Encoding: identity\r\n
> @@ -165,9 +166,9 @@
>    readline(210 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
>    readline(169 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>    readline(121 from -1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(86 from -1) -> (23) host: localhost:$HGPORT\r\n
> -  readline(63 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> -  readline(14 from -1) -> (2) \r\n
> +  readline(86 from -1) -> (22) host: localhost:$HGPORT\r\n
> +  readline(64 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(15 from -1) -> (2) \r\n
>    write(36) -> HTTP/1.1 200 Script output follows\r\n
>    write(23) -> Server: badhttpserver\r\n
>    write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> @@ -175,7 +176,7 @@
>    write(20) -> Content-Length: 42\r\n
>    write(2) -> \r\n
>    write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
> -  readline(12 from 65537) -> (12) GET /?cmd=ge
> +  readline(13 from 65537) -> (13) GET /?cmd=get
>    read limit reached; closing socket
>    readline(292 from 65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
>    readline(262 from -1) -> (27) Accept-Encoding: identity\r\n
> @@ -200,9 +201,9 @@
>    readline(315 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>    readline(282 from -1) -> (27) Accept-Encoding: identity\r\n
>    readline(255 from -1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(220 from -1) -> (23) host: localhost:$HGPORT\r\n
> -  readline(197 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> -  readline(148 from -1) -> (2) \r\n
> +  readline(220 from -1) -> (22) host: localhost:$HGPORT\r\n
> +  readline(198 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(149 from -1) -> (2) \r\n
>    write(36) -> HTTP/1.1 200 Script output follows\r\n
>    write(23) -> Server: badhttpserver\r\n
>    write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
> @@ -210,12 +211,12 @@
>    write(21) -> Content-Length: 418\r\n
>    write(2) -> \r\n
>    write(418) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httppostargs httpmediatype=0.1rx,0.1tx,0.2tx compression=none
> -  readline(146 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
> -  readline(119 from -1) -> (27) Accept-Encoding: identity\r\n
> -  readline(92 from -1) -> (41) content-type: application/mercurial-0.1\r\n
> -  readline(51 from -1) -> (19) vary: X-HgProto-1\r\n
> -  readline(32 from -1) -> (19) x-hgargs-post: 28\r\n
> -  readline(13 from -1) -> (13) x-hgproto-1:
> +  readline(147 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
> +  readline(120 from -1) -> (27) Accept-Encoding: identity\r\n
> +  readline(93 from -1) -> (41) content-type: application/mercurial-0.1\r\n
> +  readline(52 from -1) -> (19) vary: X-HgProto-1\r\n
> +  readline(33 from -1) -> (19) x-hgargs-post: 28\r\n
> +  readline(14 from -1) -> (14) x-hgproto-1: 0
>    read limit reached; closing socket
>    readline(315 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
>    readline(288 from -1) -> (27) Accept-Encoding: identity\r\n
> @@ -225,10 +226,10 @@
>    readline(182 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>    readline(134 from -1) -> (35) accept: application/mercurial-0.1\r\n
>    readline(99 from -1) -> (20) content-length: 28\r\n
> -  readline(79 from -1) -> (23) host: localhost:$HGPORT\r\n
> -  readline(56 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> -  readline(7 from -1) -> (2) \r\n
> -  read(5 from 28) -> (5) cmds=
> +  readline(79 from -1) -> (22) host: localhost:$HGPORT\r\n
> +  readline(57 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
> +  readline(8 from -1) -> (2) \r\n
> +  read(6 from 28) -> (6) cmds=h
>    read limit reached, closing socket
>    write(36) -> HTTP/1.1 500 Internal Server Error\r\n
>
> @@ -251,7 +252,7 @@
>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(1 from 36) -> (0) H
> @@ -276,7 +277,7 @@
>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (144) HTTP/1.1 200 Script output follows\r\n
> @@ -311,7 +312,7 @@
>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (659) HTTP/1.1 200 Script output follows\r\n
> @@ -327,7 +328,7 @@
>    readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (94) HTTP/1.1 200 Script output follows\r\n
> @@ -354,7 +355,7 @@
>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (724) HTTP/1.1 200 Script output follows\r\n
> @@ -370,7 +371,7 @@
>    readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (159) HTTP/1.1 200 Script output follows\r\n
> @@ -406,7 +407,7 @@
>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (859) HTTP/1.1 200 Script output follows\r\n
> @@ -422,7 +423,7 @@
>    readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (294) HTTP/1.1 200 Script output follows\r\n
> @@ -438,7 +439,7 @@
>    readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (93) HTTP/1.1 200 Script output follows\r\n
> @@ -467,7 +468,7 @@
>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (897) HTTP/1.1 200 Script output follows\r\n
> @@ -483,7 +484,7 @@
>    readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (332) HTTP/1.1 200 Script output follows\r\n
> @@ -499,7 +500,7 @@
>    readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (131) HTTP/1.1 200 Script output follows\r\n
> @@ -530,7 +531,7 @@
>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (909) HTTP/1.1 200 Script output follows\r\n
> @@ -546,7 +547,7 @@
>    readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (344) HTTP/1.1 200 Script output follows\r\n
> @@ -562,7 +563,7 @@
>    readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
>    readline(-1) -> (2) \r\n
>    write(36 from 36) -> (143) HTTP/1.1 200 Script output follows\r\n
>
> ERROR: test-http-bad-server.t output changed
Gregory Szorc - April 17, 2017, 10:13 p.m.
On Mon, Apr 17, 2017 at 12:11 PM, Pierre-Yves David <
pierre-yves.david@ens-lyon.org> wrote:

>
>
> On 04/14/2017 09:44 AM, Gregory Szorc wrote:
>
>> # HG changeset patch
>> # User Gregory Szorc <gregory.szorc@gmail.com>
>> # Date 1492147168 25200
>> #      Thu Apr 13 22:19:28 2017 -0700
>> # Node ID 80bd24abcf67d5dc8b5f5bf83c796e1f71fc5bd9
>> # Parent  283a95fe4ce5b91af4e2ed8ed1d3fe18ef3544bb
>> tests: add tests for poorly behaving HTTP server
>>
>
> I'm seeing failure for the test introduced by this test. I had no time to
> investigated further, the machine is a power8 running centos7:
>
> Linux gcc2-power8.osuosl.org 3.10.0-514.10.2.el7.ppc64le #1 SMP Fri Mar 3
> 16:16:38 GMT 2017 ppc64le ppc64le ppc64le GNU/Linux
>
> Observed failure:
>
> --- /home/marmoute/mercurial-testing/tests/test-http-bad-server.t
>> +++ /home/marmoute/mercurial-testing/tests/test-http-bad-server.t.err
>> @@ -105,9 +105,9 @@
>>    readline(210 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>>    readline(177 from -1) -> (27) Accept-Encoding: identity\r\n
>>    readline(150 from -1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(115 from -1) -> (23) host: localhost:$HGPORT\r\n
>> -  readline(92 from -1) -> (49) user-agent: mercurial/proto-1.0
>> (Mercurial 4.2)\r\n
>> -  readline(43 from -1) -> (2) \r\n
>> +  readline(115 from -1) -> (22) host: localhost:$HGPORT\r\n
>> +  readline(93 from -1) -> (49) user-agent: mercurial/proto-1.0
>> (Mercurial 4.2)\r\n
>> +  readline(44 from -1) -> (2) \r\n
>>
>
$HGPORT is variable length. This is going to be a problem.


>    write(36) -> HTTP/1.1 200 Script output follows\r\n
>>    write(23) -> Server: badhttpserver\r\n
>>    write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
>> @@ -115,8 +115,8 @@
>>    write(21) -> Content-Length: 405\r\n
>>    write(2) -> \r\n
>>    write(405) -> lookup changegroupsubset branchmap pushkey known
>> getbundle unbundlehash batch streamreqs=generaldelta,revlogv1
>> bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%
>> 2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushrac
>> ed%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
>> unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx
>> compression=none
>> -  readline(41 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
>> -  readline(15 from -1) -> (15) Accept-Encoding
>> +  readline(42 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
>> +  readline(16 from -1) -> (16) Accept-Encoding:
>>    read limit reached; closing socket
>>    readline(210 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
>>    readline(184 from -1) -> (27) Accept-Encoding: identity\r\n
>> @@ -144,9 +144,9 @@
>>    readline(292 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>>    readline(259 from -1) -> (27) Accept-Encoding: identity\r\n
>>    readline(232 from -1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(197 from -1) -> (23) host: localhost:$HGPORT\r\n
>> -  readline(174 from -1) -> (49) user-agent: mercurial/proto-1.0
>> (Mercurial 4.2)\r\n
>> -  readline(125 from -1) -> (2) \r\n
>> +  readline(197 from -1) -> (22) host: localhost:$HGPORT\r\n
>> +  readline(175 from -1) -> (49) user-agent: mercurial/proto-1.0
>> (Mercurial 4.2)\r\n
>> +  readline(126 from -1) -> (2) \r\n
>>    write(36) -> HTTP/1.1 200 Script output follows\r\n
>>    write(23) -> Server: badhttpserver\r\n
>>    write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
>> @@ -154,10 +154,11 @@
>>    write(21) -> Content-Length: 405\r\n
>>    write(2) -> \r\n
>>    write(405) -> lookup changegroupsubset branchmap pushkey known
>> getbundle unbundlehash batch streamreqs=generaldelta,revlogv1
>> bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%
>> 2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushrac
>> ed%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
>> unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx
>> compression=none
>> -  readline(123 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
>> -  readline(97 from -1) -> (27) Accept-Encoding: identity\r\n
>> -  readline(70 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
>> -  readline(41 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r
>> \n
>> +  readline(124 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
>> +  readline(98 from -1) -> (27) Accept-Encoding: identity\r\n
>> +  readline(71 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
>> +  readline(42 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r
>> \n
>> +  readline(1 from -1) -> (1) x
>>    read limit reached; closing socket
>>    readline(292 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
>>    readline(266 from -1) -> (27) Accept-Encoding: identity\r\n
>> @@ -165,9 +166,9 @@
>>    readline(210 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r
>> \n
>>    readline(169 from -1) -> (48) x-hgproto-1: 0.1 0.2
>> comp=zstd,zlib,none,bzip2\r\n
>>    readline(121 from -1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(86 from -1) -> (23) host: localhost:$HGPORT\r\n
>> -  readline(63 from -1) -> (49) user-agent: mercurial/proto-1.0
>> (Mercurial 4.2)\r\n
>> -  readline(14 from -1) -> (2) \r\n
>> +  readline(86 from -1) -> (22) host: localhost:$HGPORT\r\n
>> +  readline(64 from -1) -> (49) user-agent: mercurial/proto-1.0
>> (Mercurial 4.2)\r\n
>> +  readline(15 from -1) -> (2) \r\n
>>    write(36) -> HTTP/1.1 200 Script output follows\r\n
>>    write(23) -> Server: badhttpserver\r\n
>>    write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
>> @@ -175,7 +176,7 @@
>>    write(20) -> Content-Length: 42\r\n
>>    write(2) -> \r\n
>>    write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
>> -  readline(12 from 65537) -> (12) GET /?cmd=ge
>> +  readline(13 from 65537) -> (13) GET /?cmd=get
>>    read limit reached; closing socket
>>    readline(292 from 65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
>>    readline(262 from -1) -> (27) Accept-Encoding: identity\r\n
>> @@ -200,9 +201,9 @@
>>    readline(315 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>>    readline(282 from -1) -> (27) Accept-Encoding: identity\r\n
>>    readline(255 from -1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(220 from -1) -> (23) host: localhost:$HGPORT\r\n
>> -  readline(197 from -1) -> (49) user-agent: mercurial/proto-1.0
>> (Mercurial 4.2)\r\n
>> -  readline(148 from -1) -> (2) \r\n
>> +  readline(220 from -1) -> (22) host: localhost:$HGPORT\r\n
>> +  readline(198 from -1) -> (49) user-agent: mercurial/proto-1.0
>> (Mercurial 4.2)\r\n
>> +  readline(149 from -1) -> (2) \r\n
>>    write(36) -> HTTP/1.1 200 Script output follows\r\n
>>    write(23) -> Server: badhttpserver\r\n
>>    write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
>> @@ -210,12 +211,12 @@
>>    write(21) -> Content-Length: 418\r\n
>>    write(2) -> \r\n
>>    write(418) -> lookup changegroupsubset branchmap pushkey known
>> getbundle unbundlehash batch streamreqs=generaldelta,revlogv1
>> bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%
>> 2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushrac
>> ed%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
>> unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httppostargs
>> httpmediatype=0.1rx,0.1tx,0.2tx compression=none
>> -  readline(146 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
>> -  readline(119 from -1) -> (27) Accept-Encoding: identity\r\n
>> -  readline(92 from -1) -> (41) content-type:
>> application/mercurial-0.1\r\n
>> -  readline(51 from -1) -> (19) vary: X-HgProto-1\r\n
>> -  readline(32 from -1) -> (19) x-hgargs-post: 28\r\n
>> -  readline(13 from -1) -> (13) x-hgproto-1:
>> +  readline(147 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
>> +  readline(120 from -1) -> (27) Accept-Encoding: identity\r\n
>> +  readline(93 from -1) -> (41) content-type:
>> application/mercurial-0.1\r\n
>> +  readline(52 from -1) -> (19) vary: X-HgProto-1\r\n
>> +  readline(33 from -1) -> (19) x-hgargs-post: 28\r\n
>> +  readline(14 from -1) -> (14) x-hgproto-1: 0
>>    read limit reached; closing socket
>>    readline(315 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
>>    readline(288 from -1) -> (27) Accept-Encoding: identity\r\n
>> @@ -225,10 +226,10 @@
>>    readline(182 from -1) -> (48) x-hgproto-1: 0.1 0.2
>> comp=zstd,zlib,none,bzip2\r\n
>>    readline(134 from -1) -> (35) accept: application/mercurial-0.1\r\n
>>    readline(99 from -1) -> (20) content-length: 28\r\n
>> -  readline(79 from -1) -> (23) host: localhost:$HGPORT\r\n
>> -  readline(56 from -1) -> (49) user-agent: mercurial/proto-1.0
>> (Mercurial 4.2)\r\n
>> -  readline(7 from -1) -> (2) \r\n
>> -  read(5 from 28) -> (5) cmds=
>> +  readline(79 from -1) -> (22) host: localhost:$HGPORT\r\n
>> +  readline(57 from -1) -> (49) user-agent: mercurial/proto-1.0
>> (Mercurial 4.2)\r\n
>> +  readline(8 from -1) -> (2) \r\n
>> +  read(6 from 28) -> (6) cmds=h
>>    read limit reached, closing socket
>>    write(36) -> HTTP/1.1 500 Internal Server Error\r\n
>>
>> @@ -251,7 +252,7 @@
>>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(1 from 36) -> (0) H
>> @@ -276,7 +277,7 @@
>>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (144) HTTP/1.1 200 Script output follows\r\n
>> @@ -311,7 +312,7 @@
>>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (659) HTTP/1.1 200 Script output follows\r\n
>> @@ -327,7 +328,7 @@
>>    readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
>>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (94) HTTP/1.1 200 Script output follows\r\n
>> @@ -354,7 +355,7 @@
>>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (724) HTTP/1.1 200 Script output follows\r\n
>> @@ -370,7 +371,7 @@
>>    readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
>>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (159) HTTP/1.1 200 Script output follows\r\n
>> @@ -406,7 +407,7 @@
>>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (859) HTTP/1.1 200 Script output follows\r\n
>> @@ -422,7 +423,7 @@
>>    readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
>>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (294) HTTP/1.1 200 Script output follows\r\n
>> @@ -438,7 +439,7 @@
>>    readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG
>> 20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha
>> 1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252C
>> pushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apush
>> key%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&
>> common=0000000000000000000000000000000000000000&heads=96ee1d
>> 7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
>>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (93) HTTP/1.1 200 Script output follows\r\n
>> @@ -467,7 +468,7 @@
>>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (897) HTTP/1.1 200 Script output follows\r\n
>> @@ -483,7 +484,7 @@
>>    readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
>>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (332) HTTP/1.1 200 Script output follows\r\n
>> @@ -499,7 +500,7 @@
>>    readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG
>> 20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha
>> 1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252C
>> pushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apush
>> key%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&
>> common=0000000000000000000000000000000000000000&heads=96ee1d
>> 7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
>>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (131) HTTP/1.1 200 Script output follows\r\n
>> @@ -530,7 +531,7 @@
>>    readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
>>    readline(-1) -> (27) Accept-Encoding: identity\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (909) HTTP/1.1 200 Script output follows\r\n
>> @@ -546,7 +547,7 @@
>>    readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
>>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (344) HTTP/1.1 200 Script output follows\r\n
>> @@ -562,7 +563,7 @@
>>    readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG
>> 20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha
>> 1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252C
>> pushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apush
>> key%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&
>> common=0000000000000000000000000000000000000000&heads=96ee1d
>> 7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
>>    readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
>>    readline(-1) -> (35) accept: application/mercurial-0.1\r\n
>> -  readline(-1) -> (23) host: localhost:$HGPORT\r\n
>> +  readline(-1) -> (22) host: localhost:$HGPORT\r\n
>>    readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial
>> 4.2)\r\n
>>    readline(-1) -> (2) \r\n
>>    write(36 from 36) -> (143) HTTP/1.1 200 Script output follows\r\n
>>
>> ERROR: test-http-bad-server.t output changed
>>
>
> --
> Pierre-Yves David
>
Danek Duvall - April 18, 2017, 9:41 p.m.
Gregory Szorc wrote:

> +Failure to accept() socket should result in connection related error message
> +
> +  $ hg --config badserver.closebeforeaccept=true serve -p $HGPORT -d --pid-file=hg.pid
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  abort: error: Connection reset by peer
> +  [255]
> +
> +(The server exits on its own, but there is a race between that and starting a new server.
> +So ensure the process is dead.)
> +
> +  $ killdaemons.py $DAEMON_PIDS
> +
> +Failure immediately after accept() should yield connection related error message
> +
> +  $ hg --config badserver.closeafteraccept=true serve -p $HGPORT -d --pid-file=hg.pid
> +  $ cat hg.pid > $DAEMON_PIDS
> +
> +  $ hg clone http://localhost:$HGPORT/ clone
> +  abort: error: Connection reset by peer
> +  [255]

I'm seeing

      abort: error: ''

for this on Solaris (just the closeafteraccept case).  I've traced it back
to getting a BadStatusLine exception instead of a read (or whatever)
resulting in ECONNRESET:

   Traceback (most recent call last):
     File ".../mercurial/keepalive.py", line 239, in do_open
       r = h.getresponse()
     File ".../mercurial/url.py", line 158, in getresponse
       return keepalive.HTTPConnection.getresponse(self)
     File ".../mercurial/keepalive.py", line 578, in safegetresponse
       return cls.getresponse(self)
     File "/usr/lib/python2.7/httplib.py", line 1121, in getresponse
       response.begin()
     File "/usr/lib/python2.7/httplib.py", line 438, in begin
       version, status, reason = self._read_status()
     File "/usr/lib/python2.7/httplib.py", line 402, in _read_status
       raise BadStatusLine(line)

Any thoughts on this before I spend more time digging?

Thanks,
Danek

Patch

diff --git a/tests/badserverext.py b/tests/badserverext.py
new file mode 100644
--- /dev/null
+++ b/tests/badserverext.py
@@ -0,0 +1,269 @@ 
+# badserverext.py - Extension making servers behave badly
+#
+# Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+# no-check-code
+
+"""Extension to make servers behave badly.
+
+This extension is useful for testing Mercurial behavior when various network
+events occur.
+
+Various config options in the [badserver] section influence behavior:
+
+closebeforeaccept
+   If true, close() the server socket when a new connection arrives before
+   accept() is called. The server will then exit.
+
+closeafteraccept
+   If true, the server will close() the client socket immediately after
+   accept().
+   
+closeafterrecvbytes
+   If defined, close the client socket after receiving this many bytes.
+
+closeaftersendbytes
+   If defined, close the client socket after sending this many bytes.
+"""
+
+from __future__ import absolute_import
+
+import socket
+
+from mercurial.hgweb import (
+    server,
+)
+
+# We can't adjust __class__ on a socket instance. So we define a proxy type.
+class socketproxy(object):
+    __slots__ = (
+        '_orig',
+        '_logfp',
+        '_closeafterrecvbytes',
+        '_closeaftersendbytes',
+    )
+
+    def __init__(self, obj, logfp, closeafterrecvbytes=0,
+                 closeaftersendbytes=0):
+        object.__setattr__(self, '_orig', obj)
+        object.__setattr__(self, '_logfp', logfp)
+        object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes)
+        object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes)
+
+    def __getattribute__(self, name):
+        if name in ('makefile',):
+            return object.__getattribute__(self, name)
+
+        return getattr(object.__getattribute__(self, '_orig'), name)
+
+    def __delattr__(self, name):
+        delattr(object.__getattribute__(self, '_orig'), name)
+
+    def __setattr__(self, name, value):
+        setattr(object.__getattribute__(self, '_orig'), name, value)
+
+    def makefile(self, mode, bufsize):
+        f = object.__getattribute__(self, '_orig').makefile(mode, bufsize)
+
+        logfp = object.__getattribute__(self, '_logfp')
+        closeafterrecvbytes = object.__getattribute__(self,
+                                                      '_closeafterrecvbytes')
+        closeaftersendbytes = object.__getattribute__(self,
+                                                      '_closeaftersendbytes')
+
+        return fileobjectproxy(f, logfp,
+                               closeafterrecvbytes=closeafterrecvbytes,
+                               closeaftersendbytes=closeaftersendbytes)
+
+# We can't adjust __class__ on socket._fileobject, so define a proxy.
+class fileobjectproxy(object):
+    __slots__ = (
+        '_orig',
+        '_logfp',
+        '_closeafterrecvbytes',
+        '_closeaftersendbytes',
+    )
+
+    def __init__(self, obj, logfp, closeafterrecvbytes=0,
+                 closeaftersendbytes=0):
+        object.__setattr__(self, '_orig', obj)
+        object.__setattr__(self, '_logfp', logfp)
+        object.__setattr__(self, '_closeafterrecvbytes', closeafterrecvbytes)
+        object.__setattr__(self, '_closeaftersendbytes', closeaftersendbytes)
+
+    def __getattribute__(self, name):
+        if name in ('read', 'readline', 'write', '_writelog'):
+            return object.__getattribute__(self, name)
+
+        return getattr(object.__getattribute__(self, '_orig'), name)
+
+    def __delattr__(self, name):
+        delattr(object.__getattribute__(self, '_orig'), name)
+
+    def __setattr__(self, name, value):
+        setattr(object.__getattribute__(self, '_orig'), name, value)
+
+    def _writelog(self, msg):
+        msg = msg.replace('\r', '\\r').replace('\n', '\\n')
+
+        object.__getattribute__(self, '_logfp').write(msg)
+        object.__getattribute__(self, '_logfp').write('\n')
+
+    def read(self, size=-1):
+        remaining = object.__getattribute__(self, '_closeafterrecvbytes')
+
+        # No read limit. Call original function.
+        if not remaining:
+            result = object.__getattribute__(self, '_orig').read(size)
+            self._writelog('read(%d) -> (%d) (%s) %s' % (size,
+                                                           len(result),
+                                                           result))
+            return result
+
+        origsize = size
+
+        if size < 0:
+            size = remaining
+        else:
+            size = min(remaining, size)
+
+        result = object.__getattribute__(self, '_orig').read(size)
+        remaining -= len(result)
+
+        self._writelog('read(%d from %d) -> (%d) %s' % (
+            size, origsize, len(result), result))
+
+        object.__setattr__(self, '_closeafterrecvbytes', remaining)
+
+        if remaining <= 0:
+            self._writelog('read limit reached, closing socket')
+            self._sock.close()
+            # This is the easiest way to abort the current request.
+            raise Exception('connection closed after receiving N bytes')
+
+        return result
+
+    def readline(self, size=-1):
+        remaining = object.__getattribute__(self, '_closeafterrecvbytes')
+
+        # No read limit. Call original function.
+        if not remaining:
+            result = object.__getattribute__(self, '_orig').readline(size)
+            self._writelog('readline(%d) -> (%d) %s' % (
+                size, len(result), result))
+            return result
+
+        origsize = size
+
+        if size < 0:
+            size = remaining
+        else:
+            size = min(remaining, size)
+
+        result = object.__getattribute__(self, '_orig').readline(size)
+        remaining -= len(result)
+
+        self._writelog('readline(%d from %d) -> (%d) %s' % (
+            size, origsize, len(result), result))
+
+        object.__setattr__(self, '_closeafterrecvbytes', remaining)
+
+        if remaining <= 0:
+            self._writelog('read limit reached; closing socket')
+            self._sock.close()
+            # This is the easiest way to abort the current request.
+            raise Exception('connection closed after receiving N bytes')
+
+        return result
+
+    def write(self, data):
+        remaining = object.__getattribute__(self, '_closeaftersendbytes')
+
+        # No byte limit on this operation. Call original function.
+        if not remaining:
+            self._writelog('write(%d) -> %s' % (len(data), data))
+            result = object.__getattribute__(self, '_orig').write(data)
+            return result
+
+        if len(data) > remaining:
+            newdata = data[0:remaining]
+        else:
+            newdata = data
+
+        remaining -= len(newdata)
+
+        self._writelog('write(%d from %d) -> (%d) %s' % (
+            len(newdata), len(data), remaining, newdata))
+
+        result = object.__getattribute__(self, '_orig').write(newdata)
+
+        object.__setattr__(self, '_closeaftersendbytes', remaining)
+
+        if remaining <= 0:
+            self._writelog('write limit reached; closing socket')
+            self._sock.close()
+            raise Exception('connection closed after sending N bytes')
+
+        return result
+
+def extsetup(ui):
+    # Change the base HTTP server class so various events can be performed.
+    # See SocketServer.BaseServer for how the specially named methods work.
+    class badserver(server.MercurialHTTPServer):
+        def __init__(self, ui, *args, **kwargs):
+            self._ui = ui
+            super(badserver, self).__init__(ui, *args, **kwargs)
+
+            # Need to inherit object so super() works.
+            class badrequesthandler(self.RequestHandlerClass, object):
+                def send_header(self, name, value):
+                    # Make headers deterministic to facilitate testing.
+                    if name.lower() == 'date':
+                        value = 'Fri, 14 Apr 2017 00:00:00 GMT'
+                    elif name.lower() == 'server':
+                        value = 'badhttpserver'
+
+                    return super(badrequesthandler, self).send_header(name,
+                                                                      value)
+
+            self.RequestHandlerClass = badrequesthandler
+
+        # Called to accept() a pending socket.
+        def get_request(self):
+            if self._ui.configbool('badserver', 'closebeforeaccept'):
+                self.socket.close()
+
+                # Tells the server to stop processing more requests.
+                self.__shutdown_request = True
+
+                # Simulate failure to stop processing this request.
+                raise socket.error('close before accept')
+
+            if self._ui.configbool('badserver', 'closeafteraccept'):
+                request, client_address = super(badserver, self).get_request()
+                request.close()
+                raise socket.error('close after accept')
+
+            return super(badserver, self).get_request()
+
+        # Does heavy lifting of processing a request. Invokes
+        # self.finish_request() which calls self.RequestHandlerClass() which
+        # is a hgweb.server._httprequesthandler.
+        def process_request(self, socket, address):
+            # Wrap socket in a proxy if we need to count bytes.
+            closeafterrecvbytes = self._ui.configint('badserver',
+                                                     'closeafterrecvbytes', 0)
+            closeaftersendbytes = self._ui.configint('badserver',
+                                                     'closeaftersendbytes', 0)
+
+            if closeafterrecvbytes or closeaftersendbytes:
+                socket = socketproxy(socket, self.errorlog,
+                                     closeafterrecvbytes=closeafterrecvbytes,
+                                     closeaftersendbytes=closeaftersendbytes)
+
+            return super(badserver, self).process_request(socket, address)
+
+    server.MercurialHTTPServer = badserver
diff --git a/tests/test-check-code.t b/tests/test-check-code.t
--- a/tests/test-check-code.t
+++ b/tests/test-check-code.t
@@ -40,6 +40,7 @@  New errors are not allowed. Warnings are
    >     policy = os.environ.get('HGMODULEPOLICY', policy)
    use encoding.environ instead (py3)
   Skipping mercurial/statprof.py it has no-che?k-code (glob)
+  Skipping tests/badserverext.py it has no-che?k-code (glob)
   [1]
 
 @commands in debugcommands.py should be in alphabetical order.
diff --git a/tests/test-http-bad-server.t b/tests/test-http-bad-server.t
new file mode 100644
--- /dev/null
+++ b/tests/test-http-bad-server.t
@@ -0,0 +1,896 @@ 
+#require killdaemons serve zstd
+
+Client version is embedded in HTTP request and is effectively dynamic. Pin the
+version so behavior is deterministic.
+
+  $ cat > fakeversion.py << EOF
+  > from mercurial import util
+  > util.version = lambda: '4.2'
+  > EOF
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > fakeversion = `pwd`/fakeversion.py
+  > EOF
+
+  $ hg init server0
+  $ cd server0
+  $ touch foo
+  $ hg -q commit -A -m initial
+
+Also disable compression because zstd is optional and causes output to vary
+and because debugging partial responses is hard when compression is involved
+
+  $ cat > .hg/hgrc << EOF
+  > [extensions]
+  > badserver = $TESTDIR/badserverext.py
+  > [server]
+  > compressionengines = none
+  > EOF
+
+Failure to accept() socket should result in connection related error message
+
+  $ hg --config badserver.closebeforeaccept=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  abort: error: Connection reset by peer
+  [255]
+
+(The server exits on its own, but there is a race between that and starting a new server.
+So ensure the process is dead.)
+
+  $ killdaemons.py $DAEMON_PIDS
+
+Failure immediately after accept() should yield connection related error message
+
+  $ hg --config badserver.closeafteraccept=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  abort: error: Connection reset by peer
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+Failure to read all bytes in initial HTTP request should yield connection related error message
+
+  $ hg --config badserver.closeafterrecvbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+TODO this error message is not very good
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  abort: error: ''
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(1 from 65537) -> (1) G
+  read limit reached; closing socket
+
+  $ rm -f error.log
+
+Same failure, but server reads full HTTP request line
+
+  $ hg --config badserver.closeafterrecvbytes=40 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+  $ hg clone http://localhost:$HGPORT/ clone
+  abort: error: ''
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(40 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
+  readline(7 from -1) -> (7) Accept-
+  read limit reached; closing socket
+
+  $ rm -f error.log
+
+Failure on subsequent HTTP request on the same socket (cmd?batch)
+
+  $ hg --config badserver.closeafterrecvbytes=210 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+  $ hg clone http://localhost:$HGPORT/ clone
+  abort: error: ''
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(210 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
+  readline(177 from -1) -> (27) Accept-Encoding: identity\r\n
+  readline(150 from -1) -> (8) vary: \r\n
+  readline(142 from -1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(107 from -1) -> (23) host: localhost:$HGPORT\r\n
+  readline(84 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(35 from -1) -> (2) \r\n
+  write(36) -> HTTP/1.1 200 Script output follows\r\n
+  write(23) -> Server: badhttpserver\r\n
+  write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41) -> Content-Type: application/mercurial-0.1\r\n
+  write(21) -> Content-Length: 405\r\n
+  write(2) -> \r\n
+  write(405) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
+  readline(33 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
+  readline(7 from -1) -> (7) Accept-
+  read limit reached; closing socket
+  readline(210 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
+  readline(184 from -1) -> (27) Accept-Encoding: identity\r\n
+  readline(157 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(128 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
+  readline(87 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(39 from -1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(4 from -1) -> (4) host
+  read limit reached; closing socket
+
+  $ rm -f error.log
+
+Failure to read getbundle HTTP request
+
+  $ hg --config badserver.closeafterrecvbytes=300 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  abort: error: ''
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(300 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
+  readline(267 from -1) -> (27) Accept-Encoding: identity\r\n
+  readline(240 from -1) -> (8) vary: \r\n
+  readline(232 from -1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(197 from -1) -> (23) host: localhost:$HGPORT\r\n
+  readline(174 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(125 from -1) -> (2) \r\n
+  write(36) -> HTTP/1.1 200 Script output follows\r\n
+  write(23) -> Server: badhttpserver\r\n
+  write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41) -> Content-Type: application/mercurial-0.1\r\n
+  write(21) -> Content-Length: 405\r\n
+  write(2) -> \r\n
+  write(405) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
+  readline(123 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
+  readline(97 from -1) -> (27) Accept-Encoding: identity\r\n
+  readline(70 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(41 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
+  read limit reached; closing socket
+  readline(300 from 65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
+  readline(274 from -1) -> (27) Accept-Encoding: identity\r\n
+  readline(247 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(218 from -1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
+  readline(177 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(129 from -1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(94 from -1) -> (23) host: localhost:$HGPORT\r\n
+  readline(71 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(22 from -1) -> (2) \r\n
+  write(36) -> HTTP/1.1 200 Script output follows\r\n
+  write(23) -> Server: badhttpserver\r\n
+  write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41) -> Content-Type: application/mercurial-0.1\r\n
+  write(20) -> Content-Length: 42\r\n
+  write(2) -> \r\n
+  write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
+  readline(20 from 65537) -> (20) GET /?cmd=getbundle 
+  read limit reached; closing socket
+  readline(300 from 65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
+  readline(270 from -1) -> (27) Accept-Encoding: identity\r\n
+  readline(243 from -1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(214 from -1) -> (214) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%2
+  read limit reached; closing socket
+
+  $ rm -f error.log
+
+Now do a variation using POST to send arguments
+
+  $ hg --config experimental.httppostargs=true --config badserver.closeafterrecvbytes=315 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  abort: error: ''
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(315 from 65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
+  readline(282 from -1) -> (27) Accept-Encoding: identity\r\n
+  readline(255 from -1) -> (8) vary: \r\n
+  readline(247 from -1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(212 from -1) -> (23) host: localhost:$HGPORT\r\n
+  readline(189 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(140 from -1) -> (2) \r\n
+  write(36) -> HTTP/1.1 200 Script output follows\r\n
+  write(23) -> Server: badhttpserver\r\n
+  write(37) -> Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41) -> Content-Type: application/mercurial-0.1\r\n
+  write(21) -> Content-Length: 418\r\n
+  write(2) -> \r\n
+  write(418) -> lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httppostargs httpmediatype=0.1rx,0.1tx,0.2tx compression=none
+  readline(138 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
+  readline(111 from -1) -> (27) Accept-Encoding: identity\r\n
+  readline(84 from -1) -> (41) content-type: application/mercurial-0.1\r\n
+  readline(43 from -1) -> (19) vary: X-HgProto-1\r\n
+  readline(24 from -1) -> (19) x-hgargs-post: 28\r\n
+  readline(5 from -1) -> (5) x-hgp
+  read limit reached; closing socket
+  readline(315 from 65537) -> (27) POST /?cmd=batch HTTP/1.1\r\n
+  readline(288 from -1) -> (27) Accept-Encoding: identity\r\n
+  readline(261 from -1) -> (41) content-type: application/mercurial-0.1\r\n
+  readline(220 from -1) -> (19) vary: X-HgProto-1\r\n
+  readline(201 from -1) -> (19) x-hgargs-post: 28\r\n
+  readline(182 from -1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(134 from -1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(99 from -1) -> (20) content-length: 28\r\n
+  readline(79 from -1) -> (23) host: localhost:$HGPORT\r\n
+  readline(56 from -1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(7 from -1) -> (2) \r\n
+  read(5 from 28) -> (5) cmds=
+  read limit reached, closing socket
+  write(36) -> HTTP/1.1 500 Internal Server Error\r\n
+
+  $ rm -f error.log
+
+Now move on to partial server responses
+
+Server sends a single character from the HTTP response line
+
+  $ hg --config badserver.closeaftersendbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  abort: error: H
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (8) vary: \r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(1 from 36) -> (0) H
+  write limit reached; closing socket
+  write(36) -> HTTP/1.1 500 Internal Server Error\r\n
+
+  $ rm -f error.log
+
+Server sends an incomplete capabilities response body
+
+  $ hg --config badserver.closeaftersendbytes=180 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+TODO client spews a stack due to uncaught httplib.IncompleteRead
+
+  $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
+  [1]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (8) vary: \r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (144) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (121) Server: badhttpserver\r\n
+  write(37 from 37) -> (84) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (43) Content-Type: application/mercurial-0.1\r\n
+  write(21 from 21) -> (22) Content-Length: 405\r\n
+  write(2 from 2) -> (20) \r\n
+  write(20 from 405) -> (0) lookup changegroupsu
+  write limit reached; closing socket
+
+  $ rm -f error.log
+
+Server sends incomplete headers for batch request
+
+  $ hg --config badserver.closeaftersendbytes=695 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+TODO this output is horrible
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  abort: 'http://localhost:$HGPORT/' does not appear to be an hg repository:
+  ---%<--- (application/mercuria)
+  
+  ---%<---
+  !
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (8) vary: \r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (659) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (636) Server: badhttpserver\r\n
+  write(37 from 37) -> (599) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (558) Content-Type: application/mercurial-0.1\r\n
+  write(21 from 21) -> (537) Content-Length: 405\r\n
+  write(2 from 2) -> (535) \r\n
+  write(405 from 405) -> (130) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
+  readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (94) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (71) Server: badhttpserver\r\n
+  write(37 from 37) -> (34) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(34 from 41) -> (0) Content-Type: application/mercuria
+  write limit reached; closing socket
+  write(36) -> HTTP/1.1 500 Internal Server Error\r\n
+
+  $ rm -f error.log
+
+Server sends an incomplete HTTP response body to batch request
+
+  $ hg --config badserver.closeaftersendbytes=760 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+TODO client spews a stack due to uncaught ValueError in batch.results()
+  $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
+  [1]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (8) vary: \r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (724) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (701) Server: badhttpserver\r\n
+  write(37 from 37) -> (664) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (623) Content-Type: application/mercurial-0.1\r\n
+  write(21 from 21) -> (602) Content-Length: 405\r\n
+  write(2 from 2) -> (600) \r\n
+  write(405 from 405) -> (195) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
+  readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (159) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (136) Server: badhttpserver\r\n
+  write(37 from 37) -> (99) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (58) Content-Type: application/mercurial-0.1\r\n
+  write(20 from 20) -> (38) Content-Length: 42\r\n
+  write(2 from 2) -> (36) \r\n
+  write(36 from 42) -> (0) 96ee1d7354c4ad7372047672c36a1f561e3a
+  write limit reached; closing socket
+
+  $ rm -f error.log
+
+Server sends incomplete headers for getbundle response
+
+  $ hg --config badserver.closeaftersendbytes=895 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+TODO this output is terrible
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  abort: 'http://localhost:$HGPORT/' does not appear to be an hg repository:
+  ---%<--- (application/mercuri)
+  
+  ---%<---
+  !
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (8) vary: \r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (859) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (836) Server: badhttpserver\r\n
+  write(37 from 37) -> (799) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (758) Content-Type: application/mercurial-0.1\r\n
+  write(21 from 21) -> (737) Content-Length: 405\r\n
+  write(2 from 2) -> (735) \r\n
+  write(405 from 405) -> (330) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
+  readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (294) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (271) Server: badhttpserver\r\n
+  write(37 from 37) -> (234) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (193) Content-Type: application/mercurial-0.1\r\n
+  write(20 from 20) -> (173) Content-Length: 42\r\n
+  write(2 from 2) -> (171) \r\n
+  write(42 from 42) -> (129) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
+  readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (93) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (70) Server: badhttpserver\r\n
+  write(37 from 37) -> (33) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(33 from 41) -> (0) Content-Type: application/mercuri
+  write limit reached; closing socket
+  write(36) -> HTTP/1.1 500 Internal Server Error\r\n
+
+  $ rm -f error.log
+
+Server sends empty HTTP body for getbundle
+
+  $ hg --config badserver.closeaftersendbytes=933 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+TODO client spews a stack due to uncaught httplib.IncompleteRead
+
+  $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
+  requesting all changes
+  [1]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (8) vary: \r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (897) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (874) Server: badhttpserver\r\n
+  write(37 from 37) -> (837) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (796) Content-Type: application/mercurial-0.1\r\n
+  write(21 from 21) -> (775) Content-Length: 405\r\n
+  write(2 from 2) -> (773) \r\n
+  write(405 from 405) -> (368) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
+  readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (332) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (309) Server: badhttpserver\r\n
+  write(37 from 37) -> (272) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (231) Content-Type: application/mercurial-0.1\r\n
+  write(20 from 20) -> (211) Content-Length: 42\r\n
+  write(2 from 2) -> (209) \r\n
+  write(42 from 42) -> (167) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
+  readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (131) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (108) Server: badhttpserver\r\n
+  write(37 from 37) -> (71) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (30) Content-Type: application/mercurial-0.2\r\n
+  write(28 from 28) -> (2) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (0) \r\n
+  write limit reached; closing socket
+  write(36) -> HTTP/1.1 500 Internal Server Error\r\n
+
+  $ rm -f error.log
+
+Server sends partial compression string
+
+  $ hg --config badserver.closeaftersendbytes=945 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+TODO client spews a stack due to uncaught httplib.IncompleteRead
+
+  $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
+  requesting all changes
+  [1]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ cat error.log
+  readline(65537) -> (33) GET /?cmd=capabilities HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (8) vary: \r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (909) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (886) Server: badhttpserver\r\n
+  write(37 from 37) -> (849) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (808) Content-Type: application/mercurial-0.1\r\n
+  write(21 from 21) -> (787) Content-Length: 405\r\n
+  write(2 from 2) -> (785) \r\n
+  write(405 from 405) -> (380) lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=none
+  readline(65537) -> (26) GET /?cmd=batch HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(-1) -> (41) x-hgarg-1: cmds=heads+%3Bknown+nodes%3D\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (344) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (321) Server: badhttpserver\r\n
+  write(37 from 37) -> (284) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (243) Content-Type: application/mercurial-0.1\r\n
+  write(20 from 20) -> (223) Content-Length: 42\r\n
+  write(2 from 2) -> (221) \r\n
+  write(42 from 42) -> (179) 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n;
+  readline(65537) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
+  readline(-1) -> (27) Accept-Encoding: identity\r\n
+  readline(-1) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n
+  readline(-1) -> (396) x-hgarg-1: bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=96ee1d7354c4ad7372047672c36a1f561e3a6a4c&listkeys=phases%2Cbookmarks\r\n
+  readline(-1) -> (48) x-hgproto-1: 0.1 0.2 comp=zstd,zlib,none,bzip2\r\n
+  readline(-1) -> (35) accept: application/mercurial-0.1\r\n
+  readline(-1) -> (23) host: localhost:$HGPORT\r\n
+  readline(-1) -> (49) user-agent: mercurial/proto-1.0 (Mercurial 4.2)\r\n
+  readline(-1) -> (2) \r\n
+  write(36 from 36) -> (143) HTTP/1.1 200 Script output follows\r\n
+  write(23 from 23) -> (120) Server: badhttpserver\r\n
+  write(37 from 37) -> (83) Date: Fri, 14 Apr 2017 00:00:00 GMT\r\n
+  write(41 from 41) -> (42) Content-Type: application/mercurial-0.2\r\n
+  write(28 from 28) -> (14) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (12) \r\n
+  write(6 from 6) -> (6) 1\\r\\n\x04\\r\\n (esc)
+  write(6 from 9) -> (0) 4\r\nnon
+  write limit reached; closing socket
+  write(27) -> 15\r\nInternal Server Error\r\n
+
+  $ rm -f error.log
+
+Server sends partial bundle2 header magic
+
+  $ hg --config badserver.closeaftersendbytes=954 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  abort: connection ended unexpectedly
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ tail -7 error.log
+  write(28 from 28) -> (23) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (21) \r\n
+  write(6 from 6) -> (15) 1\\r\\n\x04\\r\\n (esc)
+  write(9 from 9) -> (6) 4\r\nnone\r\n
+  write(6 from 9) -> (0) 4\r\nHG2
+  write limit reached; closing socket
+  write(27) -> 15\r\nInternal Server Error\r\n
+
+  $ rm -f error.log
+
+Server sends incomplete bundle2 stream params length
+
+  $ hg --config badserver.closeaftersendbytes=963 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  abort: connection ended unexpectedly
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ tail -8 error.log
+  write(28 from 28) -> (32) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (30) \r\n
+  write(6 from 6) -> (24) 1\\r\\n\x04\\r\\n (esc)
+  write(9 from 9) -> (15) 4\r\nnone\r\n
+  write(9 from 9) -> (6) 4\r\nHG20\r\n
+  write(6 from 9) -> (0) 4\\r\\n\x00\x00\x00 (esc)
+  write limit reached; closing socket
+  write(27) -> 15\r\nInternal Server Error\r\n
+
+  $ rm -f error.log
+
+Servers stops after bundle2 stream params header
+
+  $ hg --config badserver.closeaftersendbytes=966 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  abort: connection ended unexpectedly
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ tail -8 error.log
+  write(28 from 28) -> (35) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (33) \r\n
+  write(6 from 6) -> (27) 1\\r\\n\x04\\r\\n (esc)
+  write(9 from 9) -> (18) 4\r\nnone\r\n
+  write(9 from 9) -> (9) 4\r\nHG20\r\n
+  write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write limit reached; closing socket
+  write(27) -> 15\r\nInternal Server Error\r\n
+
+  $ rm -f error.log
+
+Server stops sending after bundle2 part header length
+
+  $ hg --config badserver.closeaftersendbytes=975 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  abort: connection ended unexpectedly
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ tail -9 error.log
+  write(28 from 28) -> (44) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (42) \r\n
+  write(6 from 6) -> (36) 1\\r\\n\x04\\r\\n (esc)
+  write(9 from 9) -> (27) 4\r\nnone\r\n
+  write(9 from 9) -> (18) 4\r\nHG20\r\n
+  write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
+  write limit reached; closing socket
+  write(27) -> 15\r\nInternal Server Error\r\n
+
+  $ rm -f error.log
+
+Server stops sending after bundle2 part header
+
+  $ hg --config badserver.closeaftersendbytes=1022 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  adding changesets
+  transaction abort!
+  rollback completed
+  abort: stream ended unexpectedly (got 0 bytes, expected 4)
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ tail -10 error.log
+  write(28 from 28) -> (91) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (89) \r\n
+  write(6 from 6) -> (83) 1\\r\\n\x04\\r\\n (esc)
+  write(9 from 9) -> (74) 4\r\nnone\r\n
+  write(9 from 9) -> (65) 4\r\nHG20\r\n
+  write(9 from 9) -> (56) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (47) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
+  write(47 from 47) -> (0) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02	\x01version02nbchanges1\\r\\n (esc)
+  write limit reached; closing socket
+  write(27) -> 15\r\nInternal Server Error\r\n
+
+  $ rm -f error.log
+
+Server stops after bundle2 part payload chunk size
+
+  $ hg --config badserver.closeaftersendbytes=1031 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  adding changesets
+  transaction abort!
+  rollback completed
+  abort: stream ended unexpectedly (got 0 bytes, expected 4)
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ tail -11 error.log
+  write(28 from 28) -> (100) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (98) \r\n
+  write(6 from 6) -> (92) 1\\r\\n\x04\\r\\n (esc)
+  write(9 from 9) -> (83) 4\r\nnone\r\n
+  write(9 from 9) -> (74) 4\r\nHG20\r\n
+  write(9 from 9) -> (65) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (56) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
+  write(47 from 47) -> (9) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02	\x01version02nbchanges1\\r\\n (esc)
+  write(9 from 9) -> (0) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
+  write limit reached; closing socket
+  write(27) -> 15\r\nInternal Server Error\r\n
+
+  $ rm -f error.log
+
+Server stops sending in middle of bundle2 payload chunk
+
+  $ hg --config badserver.closeaftersendbytes=1504 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  adding changesets
+  transaction abort!
+  rollback completed
+  abort: stream ended unexpectedly (got 0 bytes, expected 4)
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ tail -12 error.log
+  write(28 from 28) -> (573) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (571) \r\n
+  write(6 from 6) -> (565) 1\\r\\n\x04\\r\\n (esc)
+  write(9 from 9) -> (556) 4\r\nnone\r\n
+  write(9 from 9) -> (547) 4\r\nHG20\r\n
+  write(9 from 9) -> (538) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (529) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
+  write(47 from 47) -> (482) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02	\x01version02nbchanges1\\r\\n (esc)
+  write(9 from 9) -> (473) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
+  write(473 from 473) -> (0) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
+  write limit reached; closing socket
+  write(27) -> 15\r\nInternal Server Error\r\n
+
+  $ rm -f error.log
+
+Server stops sending after 0 length payload chunk size
+
+  $ hg --config badserver.closeaftersendbytes=1513 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  transaction abort!
+  rollback completed
+  abort: connection ended unexpectedly
+  [255]
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ tail -13 error.log
+  write(28 from 28) -> (582) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (580) \r\n
+  write(6 from 6) -> (574) 1\\r\\n\x04\\r\\n (esc)
+  write(9 from 9) -> (565) 4\r\nnone\r\n
+  write(9 from 9) -> (556) 4\r\nHG20\r\n
+  write(9 from 9) -> (547) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (538) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
+  write(47 from 47) -> (491) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02	\x01version02nbchanges1\\r\\n (esc)
+  write(9 from 9) -> (482) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
+  write(473 from 473) -> (9) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write limit reached; closing socket
+  write(27) -> 15\r\nInternal Server Error\r\n
+
+  $ rm -f error.log
+
+Server stops sending after 0 part bundle part header (indicating end of bundle2 payload)
+This is before the 0 size chunked transfer part that signals end of HTTP response.
+
+  $ hg --config badserver.closeaftersendbytes=1710 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ tail -22 error.log
+  write(28 from 28) -> (779) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (777) \r\n
+  write(6 from 6) -> (771) 1\\r\\n\x04\\r\\n (esc)
+  write(9 from 9) -> (762) 4\r\nnone\r\n
+  write(9 from 9) -> (753) 4\r\nHG20\r\n
+  write(9 from 9) -> (744) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (735) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
+  write(47 from 47) -> (688) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02	\x01version02nbchanges1\\r\\n (esc)
+  write(9 from 9) -> (679) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
+  write(473 from 473) -> (206) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (197) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (188) 4\\r\\n\x00\x00\x00 \\r\\n (esc)
+  write(38 from 38) -> (150) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00	\x06namespacephases\\r\\n (esc)
+  write(9 from 9) -> (141) 4\\r\\n\x00\x00\x00:\\r\\n (esc)
+  write(64 from 64) -> (77) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c	1\npublishing	True\r\n
+  write(9 from 9) -> (68) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (59) 4\\r\\n\x00\x00\x00#\\r\\n (esc)
+  write(41 from 41) -> (18) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00		namespacebookmarks\\r\\n (esc)
+  write(9 from 9) -> (9) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (0) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write limit reached; closing socket
+  write(27) -> 15\r\nInternal Server Error\r\n
+
+  $ rm -f error.log
+  $ rm -rf clone
+
+Server sends a size 0 chunked-transfer size without terminating \r\n
+
+  $ hg --config badserver.closeaftersendbytes=1713 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ cat hg.pid > $DAEMON_PIDS
+
+  $ hg clone http://localhost:$HGPORT/ clone
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ killdaemons.py $DAEMON_PIDS
+
+  $ tail -23 error.log
+  write(28 from 28) -> (782) Transfer-Encoding: chunked\r\n
+  write(2 from 2) -> (780) \r\n
+  write(6 from 6) -> (774) 1\\r\\n\x04\\r\\n (esc)
+  write(9 from 9) -> (765) 4\r\nnone\r\n
+  write(9 from 9) -> (756) 4\r\nHG20\r\n
+  write(9 from 9) -> (747) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (738) 4\\r\\n\x00\x00\x00)\\r\\n (esc)
+  write(47 from 47) -> (691) 29\\r\\n\x0bCHANGEGROUP\x00\x00\x00\x00\x01\x01\x07\x02	\x01version02nbchanges1\\r\\n (esc)
+  write(9 from 9) -> (682) 4\\r\\n\x00\x00\x01\xd2\\r\\n (esc)
+  write(473 from 473) -> (209) 1d2\\r\\n\x00\x00\x00\xb2\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00>6a3df4de388f3c4f8e28f4f9a814299a3cbb5f50\\ntest\\n0 0\\nfoo\\n\\ninitial\x00\x00\x00\x00\x00\x00\x00\xa1j=\xf4\xde8\x8f<O\x8e(\xf4\xf9\xa8\x14)\x9a<\xbb_P\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00-foo\x00b80de5d138758541c5f05265ad144ab9fa86d1db\\n\x00\x00\x00\x00\x00\x00\x00\x07foo\x00\x00\x00h\xb8\\r\xe5\xd18u\x85A\xc5\xf0Re\xad\x14J\xb9\xfa\x86\xd1\xdb\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x96\xee\x1dsT\xc4\xadsr\x04vr\xc3j\x1fV\x1e:jL\x00\x00\x00\x00\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (200) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (191) 4\\r\\n\x00\x00\x00 \\r\\n (esc)
+  write(38 from 38) -> (153) 20\\r\\n\x08LISTKEYS\x00\x00\x00\x01\x01\x00	\x06namespacephases\\r\\n (esc)
+  write(9 from 9) -> (144) 4\\r\\n\x00\x00\x00:\\r\\n (esc)
+  write(64 from 64) -> (80) 3a\r\n96ee1d7354c4ad7372047672c36a1f561e3a6a4c	1\npublishing	True\r\n
+  write(9 from 9) -> (71) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (62) 4\\r\\n\x00\x00\x00#\\r\\n (esc)
+  write(41 from 41) -> (21) 23\\r\\n\x08LISTKEYS\x00\x00\x00\x02\x01\x00		namespacebookmarks\\r\\n (esc)
+  write(9 from 9) -> (12) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(9 from 9) -> (3) 4\\r\\n\x00\x00\x00\x00\\r\\n (esc)
+  write(3 from 5) -> (0) 0\r\n
+  write limit reached; closing socket
+  write(27) -> 15\r\nInternal Server Error\r\n
+
+  $ rm -f error.log
+  $ rm -rf clone