view tests/test-commandserver.py @ 20713:6a1a4c212d50

revset: improve head revset performance Previously the head() revset would iterate over every item in the subset and check if it was a head. Since the subset is often the entire repo, this was slow on large repos. Now we iterate over each item in the head list and check if it's in the subset, which results in much less work. hg log -r 'head()' on a large repo: Before: 0.95s After: 0.28s
author Durham Goode <durham@fb.com>
date Thu, 13 Mar 2014 13:47:21 -0700
parents e811b93f2cb1
children 9336bc7dca8e
line wrap: on
line source

import sys, os, struct, subprocess, cStringIO, re, shutil

def connect(path=None):
    cmdline = ['hg', 'serve', '--cmdserver', 'pipe']
    if path:
        cmdline += ['-R', path]

    server = subprocess.Popen(cmdline, stdin=subprocess.PIPE,
                              stdout=subprocess.PIPE)

    return server

def writeblock(server, data):
    server.stdin.write(struct.pack('>I', len(data)))
    server.stdin.write(data)
    server.stdin.flush()

def readchannel(server):
    data = server.stdout.read(5)
    if not data:
        raise EOFError
    channel, length = struct.unpack('>cI', data)
    if channel in 'IL':
        return channel, length
    else:
        return channel, server.stdout.read(length)

def sep(text):
    return text.replace('\\', '/')

def runcommand(server, args, output=sys.stdout, error=sys.stderr, input=None,
               outfilter=lambda x: x):
    print ' runcommand', ' '.join(args)
    sys.stdout.flush()
    server.stdin.write('runcommand\n')
    writeblock(server, '\0'.join(args))

    if not input:
        input = cStringIO.StringIO()

    while True:
        ch, data = readchannel(server)
        if ch == 'o':
            output.write(outfilter(data))
            output.flush()
        elif ch == 'e':
            error.write(data)
            error.flush()
        elif ch == 'I':
            writeblock(server, input.read(data))
        elif ch == 'L':
            writeblock(server, input.readline(data))
        elif ch == 'r':
            ret, = struct.unpack('>i', data)
            if ret != 0:
                print ' [%d]' % ret
            return ret
        else:
            print "unexpected channel %c: %r" % (ch, data)
            if ch.isupper():
                return

def check(func, repopath=None):
    print
    print 'testing %s:' % func.__name__
    print
    sys.stdout.flush()
    server = connect(repopath)
    try:
        return func(server)
    finally:
        server.stdin.close()
        server.wait()

def unknowncommand(server):
    server.stdin.write('unknowncommand\n')

def hellomessage(server):
    ch, data = readchannel(server)
    # escaping python tests output not supported
    print '%c, %r' % (ch, re.sub('encoding: [a-zA-Z0-9-]+', 'encoding: ***',
                                 data))

    # run an arbitrary command to make sure the next thing the server sends
    # isn't part of the hello message
    runcommand(server, ['id'])

def checkruncommand(server):
    # hello block
    readchannel(server)

    # no args
    runcommand(server, [])

    # global options
    runcommand(server, ['id', '--quiet'])

    # make sure global options don't stick through requests
    runcommand(server, ['id'])

    # --config
    runcommand(server, ['id', '--config', 'ui.quiet=True'])

    # make sure --config doesn't stick
    runcommand(server, ['id'])

    # negative return code should be masked
    runcommand(server, ['id', '-runknown'])

def inputeof(server):
    readchannel(server)
    server.stdin.write('runcommand\n')
    # close stdin while server is waiting for input
    server.stdin.close()

    # server exits with 1 if the pipe closed while reading the command
    print 'server exit code =', server.wait()

def serverinput(server):
    readchannel(server)

    patch = """
# HG changeset patch
# User test
# Date 0 0
# Node ID c103a3dec114d882c98382d684d8af798d09d857
# Parent  0000000000000000000000000000000000000000
1

diff -r 000000000000 -r c103a3dec114 a
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/a	Thu Jan 01 00:00:00 1970 +0000
@@ -0,0 +1,1 @@
+1
"""

    runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
    runcommand(server, ['log'])

def cwd(server):
    """ check that --cwd doesn't persist between requests """
    readchannel(server)
    os.mkdir('foo')
    f = open('foo/bar', 'wb')
    f.write('a')
    f.close()
    runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
    runcommand(server, ['st', 'foo/bar'])
    os.remove('foo/bar')

def localhgrc(server):
    """ check that local configs for the cached repo aren't inherited when -R
    is used """
    readchannel(server)

    # the cached repo local hgrc contains ui.foo=bar, so showconfig should
    # show it
    runcommand(server, ['showconfig'])

    # but not for this repo
    runcommand(server, ['init', 'foo'])
    runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
    shutil.rmtree('foo')

def hook(**args):
    print 'hook talking'
    print 'now try to read something: %r' % sys.stdin.read()

def hookoutput(server):
    readchannel(server)
    runcommand(server, ['--config',
                        'hooks.pre-identify=python:test-commandserver.hook',
                        'id'],
               input=cStringIO.StringIO('some input'))

def outsidechanges(server):
    readchannel(server)
    f = open('a', 'ab')
    f.write('a\n')
    f.close()
    runcommand(server, ['status'])
    os.system('hg ci -Am2')
    runcommand(server, ['tip'])
    runcommand(server, ['status'])

def bookmarks(server):
    readchannel(server)
    runcommand(server, ['bookmarks'])

    # changes .hg/bookmarks
    os.system('hg bookmark -i bm1')
    os.system('hg bookmark -i bm2')
    runcommand(server, ['bookmarks'])

    # changes .hg/bookmarks.current
    os.system('hg upd bm1 -q')
    runcommand(server, ['bookmarks'])

    runcommand(server, ['bookmarks', 'bm3'])
    f = open('a', 'ab')
    f.write('a\n')
    f.close()
    runcommand(server, ['commit', '-Amm'])
    runcommand(server, ['bookmarks'])

def tagscache(server):
    readchannel(server)
    runcommand(server, ['id', '-t', '-r', '0'])
    os.system('hg tag -r 0 foo')
    runcommand(server, ['id', '-t', '-r', '0'])

def setphase(server):
    readchannel(server)
    runcommand(server, ['phase', '-r', '.'])
    os.system('hg phase -r . -p')
    runcommand(server, ['phase', '-r', '.'])

def rollback(server):
    readchannel(server)
    runcommand(server, ['phase', '-r', '.', '-p'])
    f = open('a', 'ab')
    f.write('a\n')
    f.close()
    runcommand(server, ['commit', '-Am.'])
    runcommand(server, ['rollback'])
    runcommand(server, ['phase', '-r', '.'])

def branch(server):
    readchannel(server)
    runcommand(server, ['branch'])
    os.system('hg branch foo')
    runcommand(server, ['branch'])
    os.system('hg branch default')

def hgignore(server):
    readchannel(server)
    f = open('.hgignore', 'ab')
    f.write('')
    f.close()
    runcommand(server, ['commit', '-Am.'])
    f = open('ignored-file', 'ab')
    f.write('')
    f.close()
    f = open('.hgignore', 'ab')
    f.write('ignored-file')
    f.close()
    runcommand(server, ['status', '-i', '-u'])

def phasecacheafterstrip(server):
    readchannel(server)

    # create new head, 5:731265503d86
    runcommand(server, ['update', '-C', '0'])
    f = open('a', 'ab')
    f.write('a\n')
    f.close()
    runcommand(server, ['commit', '-Am.', 'a'])
    runcommand(server, ['log', '-Gq'])

    # make it public; draft marker moves to 4:7966c8e3734d
    runcommand(server, ['phase', '-p', '.'])
    # load _phasecache.phaseroots
    runcommand(server, ['phase', '.'], outfilter=sep)

    # strip 1::4 outside server
    os.system('hg -q --config extensions.mq= strip 1')

    # shouldn't raise "7966c8e3734d: no node!"
    runcommand(server, ['branches'])

def obsolete(server):
    readchannel(server)

    runcommand(server, ['up', 'null'])
    runcommand(server, ['phase', '-df', 'tip'])
    cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
    if os.name == 'nt':
        cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
    os.system(cmd)
    runcommand(server, ['log', '--hidden'])
    runcommand(server, ['log'])

def mqoutsidechanges(server):
    readchannel(server)

    # load repo.mq
    runcommand(server, ['qapplied'])
    os.system('hg qnew 0.diff')
    # repo.mq should be invalidated
    runcommand(server, ['qapplied'])

    runcommand(server, ['qpop', '--all'])
    os.system('hg qqueue --create foo')
    # repo.mq should be recreated to point to new queue
    runcommand(server, ['qqueue', '--active'])

def startwithoutrepo(server):
    readchannel(server)
    runcommand(server, ['init', 'repo2'])
    runcommand(server, ['id', '-R', 'repo2'])

if __name__ == '__main__':
    os.system('hg init repo')
    os.chdir('repo')

    check(hellomessage)
    check(unknowncommand)
    check(checkruncommand)
    check(inputeof)
    check(serverinput)
    check(cwd)

    hgrc = open('.hg/hgrc', 'a')
    hgrc.write('[ui]\nfoo=bar\n')
    hgrc.close()
    check(localhgrc)
    check(hookoutput)
    check(outsidechanges)
    check(bookmarks)
    check(tagscache)
    check(setphase)
    check(rollback)
    check(branch)
    check(hgignore)
    check(phasecacheafterstrip)
    obs = open('obs.py', 'w')
    obs.write('import mercurial.obsolete\nmercurial.obsolete._enabled = True\n')
    obs.close()
    hgrc = open('.hg/hgrc', 'a')
    hgrc.write('[extensions]\nobs=obs.py\n')
    hgrc.close()
    check(obsolete)
    hgrc = open('.hg/hgrc', 'a')
    hgrc.write('[extensions]\nmq=\n')
    hgrc.close()
    check(mqoutsidechanges)

    os.chdir('..')
    check(hellomessage)
    check(startwithoutrepo)