sapling/tests/test-fb-hgext-remotefilelog-histpack.py

404 lines
14 KiB
Python
Raw Normal View History

#!/usr/bin/env python
from __future__ import absolute_import
[remotefilelog] use hashlib to compute sha1 hashes Summary: hg-crew's c27dc3c3122 and c27dc3c3122^ were breaking our extensions: ``` $ hg log -r c27dc3c3122^ changeset: 9010734b79911d2d2e7405d91a4df479b35b3841 user: Augie Fackler <raf@durin42.com> date: Thu, 09 Jun 2016 21:12:33 -0700 s.ummary: cleanup: replace uses of util.(md5|sha1|sha256|sha512) with hashlib.\1 ``` ``` $ hg log -r c27dc3c3122 changeset: 0d55a7b8d07bf948c935822e6eea85b044383f00 user: Augie Fackler <raf@durin42.com> date: Thu, 09 Jun 2016 21:13:23 -0700 s.ummary: util: drop local aliases for md5, sha1, sha256, and sha512 ``` I did a grep over facebook-hg-rpms to see what was affected: ``` $ grep "util\.\(md5\|sha1\|sha256\|sha512\)" -r ~/facebook-hg-rpms /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/basestore.py: sha = util.sha1(filename).digest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/basestore.py: sha = util.sha1(filename).digest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/shallowutil.py: pathhash = util.sha1(file).hexdigest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/shallowutil.py: pathhash = util.sha1(file).hexdigest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/debugcommands.py: filekey = util.sha1(file).hexdigest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/historypack.py: namehash = util.sha1(name).digest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/historypack.py: node = util.sha1(filename).digest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/historypack.py: files = ((util.sha1(filename).digest(), offset, size) /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/fileserverclient.py: pathhash = util.sha1(file).hexdigest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/fileserverclient.py: pathhash = util.sha1(file).hexdigest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/basepack.py: self.sha = util.sha1() /home/jeroenv/facebook-hg-rpms/remotefilelog/tests/test-datapack.py: return util.sha1(content).digest() /home/jeroenv/facebook-hg-rpms/remotefilelog/tests/test-histpack.py: return util.sha1(content).digest() Binary file /home/jeroenv/facebook-hg-rpms/hg-crew/.hg/store/data/mercurial/revlog.py.i matches /home/jeroenv/facebook-hg-rpms/fb-hgext/sparse.py: return util.sha1(fh.read()).hexdigest() /home/jeroenv/facebook-hg-rpms/fb-hgext/sparse.py: sha1 = util.sha1() /home/jeroenv/facebook-hg-rpms/fb-hgext/sparse.py: sha1 = util.sha1() /home/jeroenv/facebook-hg-rpms/fb-hgext/sparse.py: sha1 = util.sha1() /home/jeroenv/facebook-hg-rpms/fb-hgext/sparse.py: sha1 = util.sha1() /home/jeroenv/facebook-hg-rpms/mutable-history/hgext/simple4server.py: sha = util.sha1() /home/jeroenv/facebook-hg-rpms/mutable-history/hgext/evolve.py: sha = util.sha1() ``` This diff is part of the fix. Test Plan: Ran the tests. ``` $MERCURIALRUNTEST -S -j 48 --with-hg ~/local/facebook-hg-rpms/hg-crew/hg ``` Reviewers: #sourcecontrol, ttung Differential Revision: https://phabricator.intern.facebook.com/D3440041 Tasks: 11762191
2016-06-16 01:48:16 +03:00
import hashlib
import os
import random
import shutil
import stat
import struct
import tempfile
import unittest
import silenttestrunner
from hgext.remotefilelog.basepack import LARGEFANOUTPREFIX, SMALLFANOUTCUTOFF
2018-01-10 02:23:52 +03:00
from hgext.remotefilelog.historypack import historypack, mutablehistorypack
from mercurial import error, ui as uimod
from mercurial.node import nullid
flake8: enable F821 check Summary: This check is useful and detects real errors (ex. fbconduit). Unfortunately `arc lint` will run it with both py2 and py3 so a lot of py2 builtins will still be warned. I didn't find a clean way to disable py3 check. So this diff tries to fix them. For `xrange`, the change was done by a script: ``` import sys import redbaron headertypes = {'comment', 'endl', 'from_import', 'import', 'string', 'assignment', 'atomtrailers'} xrangefix = '''try: xrange(0) except NameError: xrange = range ''' def isxrange(x): try: return x[0].value == 'xrange' except Exception: return False def main(argv): for i, path in enumerate(argv): print('(%d/%d) scanning %s' % (i + 1, len(argv), path)) content = open(path).read() try: red = redbaron.RedBaron(content) except Exception: print(' warning: failed to parse') continue hasxrange = red.find('atomtrailersnode', value=isxrange) hasxrangefix = 'xrange = range' in content if hasxrangefix or not hasxrange: print(' no need to change') continue # find a place to insert the compatibility statement changed = False for node in red: if node.type in headertypes: continue # node.insert_before is an easier API, but it has bugs changing # other "finally" and "except" positions. So do the insert # manually. # # node.insert_before(xrangefix) line = node.absolute_bounding_box.top_left.line - 1 lines = content.splitlines(1) content = ''.join(lines[:line]) + xrangefix + ''.join(lines[line:]) changed = True break if changed: # "content" is faster than "red.dumps()" open(path, 'w').write(content) print(' updated') if __name__ == "__main__": sys.exit(main(sys.argv[1:])) ``` For other py2 builtins that do not have a py3 equivalent, some `# noqa` were added as a workaround for now. Reviewed By: DurhamG Differential Revision: D6934535 fbshipit-source-id: 546b62830af144bc8b46788d2e0fd00496838939
2018-02-10 04:31:44 +03:00
try:
xrange(0)
except NameError:
xrange = range
class histpacktests(unittest.TestCase):
def setUp(self):
self.tempdirs = []
def tearDown(self):
for d in self.tempdirs:
shutil.rmtree(d)
def makeTempDir(self):
tempdir = tempfile.mkdtemp()
self.tempdirs.append(tempdir)
return tempdir
def getHash(self, content):
[remotefilelog] use hashlib to compute sha1 hashes Summary: hg-crew's c27dc3c3122 and c27dc3c3122^ were breaking our extensions: ``` $ hg log -r c27dc3c3122^ changeset: 9010734b79911d2d2e7405d91a4df479b35b3841 user: Augie Fackler <raf@durin42.com> date: Thu, 09 Jun 2016 21:12:33 -0700 s.ummary: cleanup: replace uses of util.(md5|sha1|sha256|sha512) with hashlib.\1 ``` ``` $ hg log -r c27dc3c3122 changeset: 0d55a7b8d07bf948c935822e6eea85b044383f00 user: Augie Fackler <raf@durin42.com> date: Thu, 09 Jun 2016 21:13:23 -0700 s.ummary: util: drop local aliases for md5, sha1, sha256, and sha512 ``` I did a grep over facebook-hg-rpms to see what was affected: ``` $ grep "util\.\(md5\|sha1\|sha256\|sha512\)" -r ~/facebook-hg-rpms /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/basestore.py: sha = util.sha1(filename).digest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/basestore.py: sha = util.sha1(filename).digest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/shallowutil.py: pathhash = util.sha1(file).hexdigest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/shallowutil.py: pathhash = util.sha1(file).hexdigest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/debugcommands.py: filekey = util.sha1(file).hexdigest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/historypack.py: namehash = util.sha1(name).digest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/historypack.py: node = util.sha1(filename).digest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/historypack.py: files = ((util.sha1(filename).digest(), offset, size) /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/fileserverclient.py: pathhash = util.sha1(file).hexdigest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/fileserverclient.py: pathhash = util.sha1(file).hexdigest() /home/jeroenv/facebook-hg-rpms/remotefilelog/remotefilelog/basepack.py: self.sha = util.sha1() /home/jeroenv/facebook-hg-rpms/remotefilelog/tests/test-datapack.py: return util.sha1(content).digest() /home/jeroenv/facebook-hg-rpms/remotefilelog/tests/test-histpack.py: return util.sha1(content).digest() Binary file /home/jeroenv/facebook-hg-rpms/hg-crew/.hg/store/data/mercurial/revlog.py.i matches /home/jeroenv/facebook-hg-rpms/fb-hgext/sparse.py: return util.sha1(fh.read()).hexdigest() /home/jeroenv/facebook-hg-rpms/fb-hgext/sparse.py: sha1 = util.sha1() /home/jeroenv/facebook-hg-rpms/fb-hgext/sparse.py: sha1 = util.sha1() /home/jeroenv/facebook-hg-rpms/fb-hgext/sparse.py: sha1 = util.sha1() /home/jeroenv/facebook-hg-rpms/fb-hgext/sparse.py: sha1 = util.sha1() /home/jeroenv/facebook-hg-rpms/mutable-history/hgext/simple4server.py: sha = util.sha1() /home/jeroenv/facebook-hg-rpms/mutable-history/hgext/evolve.py: sha = util.sha1() ``` This diff is part of the fix. Test Plan: Ran the tests. ``` $MERCURIALRUNTEST -S -j 48 --with-hg ~/local/facebook-hg-rpms/hg-crew/hg ``` Reviewers: #sourcecontrol, ttung Differential Revision: https://phabricator.intern.facebook.com/D3440041 Tasks: 11762191
2016-06-16 01:48:16 +03:00
return hashlib.sha1(content).digest()
def getFakeHash(self):
return "".join(chr(random.randint(0, 255)) for _ in range(20))
def createPack(self, revisions=None):
"""Creates and returns a historypack containing the specified revisions.
`revisions` is a list of tuples, where each tuple contains a filanem,
node, p1node, p2node, and linknode.
"""
if revisions is None:
revisions = [
(
"filename",
self.getFakeHash(),
nullid,
nullid,
self.getFakeHash(),
None,
)
]
packdir = self.makeTempDir()
packer = mutablehistorypack(uimod.ui(), packdir, version=1)
for filename, node, p1, p2, linknode, copyfrom in revisions:
packer.add(filename, node, p1, p2, linknode, copyfrom)
path = packer.close()
return historypack(path)
def testAddSingle(self):
"""Test putting a single entry into a pack and reading it out.
"""
filename = "foo"
node = self.getFakeHash()
p1 = self.getFakeHash()
p2 = self.getFakeHash()
linknode = self.getFakeHash()
revisions = [(filename, node, p1, p2, linknode, None)]
pack = self.createPack(revisions)
actual = pack.getancestors(filename, node)[node]
self.assertEquals(p1, actual[0])
self.assertEquals(p2, actual[1])
self.assertEquals(linknode, actual[2])
def testAddMultiple(self):
"""Test putting multiple unrelated revisions into a pack and reading
them out.
"""
revisions = []
for i in range(10):
filename = "foo-%s" % i
node = self.getFakeHash()
p1 = self.getFakeHash()
p2 = self.getFakeHash()
linknode = self.getFakeHash()
revisions.append((filename, node, p1, p2, linknode, None))
pack = self.createPack(revisions)
for filename, node, p1, p2, linknode, copyfrom in revisions:
actual = pack.getancestors(filename, node)[node]
self.assertEquals(p1, actual[0])
self.assertEquals(p2, actual[1])
self.assertEquals(linknode, actual[2])
self.assertEquals(copyfrom, actual[3])
def testAddAncestorChain(self):
"""Test putting multiple revisions in into a pack and read the ancestor
chain.
"""
revisions = []
filename = "foo"
lastnode = nullid
for i in range(10):
node = self.getFakeHash()
revisions.append((filename, node, lastnode, nullid, nullid, None))
lastnode = node
# revisions must be added in topological order, newest first
revisions = list(reversed(revisions))
pack = self.createPack(revisions)
# Test that the chain has all the entries
ancestors = pack.getancestors(revisions[0][0], revisions[0][1])
for filename, node, p1, p2, linknode, copyfrom in revisions:
ap1, ap2, alinknode, acopyfrom = ancestors[node]
self.assertEquals(ap1, p1)
self.assertEquals(ap2, p2)
self.assertEquals(alinknode, linknode)
self.assertEquals(acopyfrom, copyfrom)
def testPackMany(self):
"""Pack many related and unrelated ancestors.
"""
# Build a random pack file
allentries = {}
ancestorcounts = {}
revisions = []
random.seed(0)
for i in range(100):
filename = "filename-%s" % i
entries = []
p2 = nullid
linknode = nullid
for j in range(random.randint(1, 100)):
node = self.getFakeHash()
p1 = nullid
if len(entries) > 0:
p1 = entries[random.randint(0, len(entries) - 1)]
entries.append(node)
revisions.append((filename, node, p1, p2, linknode, None))
allentries[(filename, node)] = (p1, p2, linknode)
if p1 == nullid:
ancestorcounts[(filename, node)] = 1
else:
newcount = ancestorcounts[(filename, p1)] + 1
ancestorcounts[(filename, node)] = newcount
# Must add file entries in reverse topological order
revisions = list(reversed(revisions))
pack = self.createPack(revisions)
# Verify the pack contents
for (filename, node), (p1, p2, lastnode) in allentries.iteritems():
ancestors = pack.getancestors(filename, node)
self.assertEquals(ancestorcounts[(filename, node)], len(ancestors))
for anode, (ap1, ap2, alinknode, copyfrom) in ancestors.iteritems():
ep1, ep2, elinknode = allentries[(filename, anode)]
self.assertEquals(ap1, ep1)
self.assertEquals(ap2, ep2)
self.assertEquals(alinknode, elinknode)
self.assertEquals(copyfrom, None)
def testGetNodeInfo(self):
revisions = []
filename = "foo"
lastnode = nullid
for i in range(10):
node = self.getFakeHash()
revisions.append((filename, node, lastnode, nullid, nullid, None))
lastnode = node
pack = self.createPack(revisions)
# Test that getnodeinfo returns the expected results
for filename, node, p1, p2, linknode, copyfrom in revisions:
ap1, ap2, alinknode, acopyfrom = pack.getnodeinfo(filename, node)
self.assertEquals(ap1, p1)
self.assertEquals(ap2, p2)
self.assertEquals(alinknode, linknode)
self.assertEquals(acopyfrom, copyfrom)
def testGetMissing(self):
"""Test the getmissing() api.
"""
revisions = []
filename = "foo"
for i in range(10):
node = self.getFakeHash()
p1 = self.getFakeHash()
p2 = self.getFakeHash()
linknode = self.getFakeHash()
revisions.append((filename, node, p1, p2, linknode, None))
pack = self.createPack(revisions)
missing = pack.getmissing([(filename, revisions[0][1])])
self.assertFalse(missing)
missing = pack.getmissing(
[(filename, revisions[0][1]), (filename, revisions[1][1])]
)
self.assertFalse(missing)
fakenode = self.getFakeHash()
missing = pack.getmissing([(filename, revisions[0][1]), (filename, fakenode)])
self.assertEquals(missing, [(filename, fakenode)])
# Test getmissing on a non-existant filename
missing = pack.getmissing([("bar", fakenode)])
self.assertEquals(missing, [("bar", fakenode)])
def testAddThrows(self):
pack = self.createPack()
try:
pack.add("filename", nullid, nullid, nullid, nullid, None)
self.assertTrue(False, "historypack.add should throw")
except RuntimeError:
pass
def testBadVersionThrows(self):
pack = self.createPack()
path = pack.path() + ".histpack"
with open(path) as f:
raw = f.read()
raw = struct.pack("!B", 255) + raw[1:]
os.chmod(path, os.stat(path).st_mode | stat.S_IWRITE)
with open(path, "w+") as f:
f.write(raw)
try:
pack = historypack(pack.path())
self.assertTrue(False, "bad version number should have thrown")
except RuntimeError:
pass
def testLargePack(self):
"""Test creating and reading from a large pack with over X entries.
This causes it to use a 2^16 fanout table instead."""
total = SMALLFANOUTCUTOFF + 1
revisions = []
for i in xrange(total):
filename = "foo-%s" % i
node = self.getFakeHash()
p1 = self.getFakeHash()
p2 = self.getFakeHash()
linknode = self.getFakeHash()
revisions.append((filename, node, p1, p2, linknode, None))
pack = self.createPack(revisions)
self.assertEquals(pack.params.fanoutprefix, LARGEFANOUTPREFIX)
for filename, node, p1, p2, linknode, copyfrom in revisions:
actual = pack.getancestors(filename, node)[node]
self.assertEquals(p1, actual[0])
self.assertEquals(p2, actual[1])
self.assertEquals(linknode, actual[2])
self.assertEquals(copyfrom, actual[3])
def testReadingMutablePack(self):
"""Tests that the data written into a mutablehistorypack can be read out
before it has been finalized."""
packdir = self.makeTempDir()
packer = mutablehistorypack(uimod.ui(), packdir, version=1)
revisions = []
filename = "foo"
lastnode = nullid
for i in range(5):
node = self.getFakeHash()
revisions.append((filename, node, lastnode, nullid, nullid, ""))
lastnode = node
filename = "bar"
lastnode = nullid
for i in range(5):
node = self.getFakeHash()
revisions.append((filename, node, lastnode, nullid, nullid, ""))
lastnode = node
for filename, node, p1, p2, linknode, copyfrom in revisions:
packer.add(filename, node, p1, p2, linknode, copyfrom)
# Test getancestors()
for filename, node, p1, p2, linknode, copyfrom in revisions:
entry = packer.getancestors(filename, node)
self.assertEquals(entry, {node: (p1, p2, linknode, copyfrom)})
# Test getmissing()
missingcheck = [(revisions[0][0], revisions[0][1]), ("foo", self.getFakeHash())]
missing = packer.getmissing(missingcheck)
self.assertEquals(missing, missingcheck[1:])
def testWritingLinkRevs(self):
"""Tests that we can add linkrevs and have them written as linknodes.
"""
class fakerepo(object):
def __init__(self):
self.changelog = fakechangelog()
class fakechangelog(object):
def __init__(self):
self.commits = []
def __len__(self):
return len(self.commits)
def rev(self, node):
try:
return self.commits.index(node)
except Exception:
raise error.LookupError(hex(node), "x", "x")
def node(self, rev):
if rev >= len(self.commits):
raise error.LookupError(rev, "x", "x")
return self.commits[rev]
repo = fakerepo()
packdir = self.makeTempDir()
packer = mutablehistorypack(uimod.ui(), packdir, version=1, repo=repo)
revisions = []
commits = []
filename = "foo"
lastnode = nullid
for i in range(5):
node = self.getFakeHash()
linknode = self.getFakeHash()
commits.append(linknode)
revisions.append((filename, node, lastnode, nullid, linknode, ""))
lastnode = node
for filename, node, p1, p2, linknode, copyfrom in revisions:
packer.add(
filename, node, p1, p2, None, copyfrom, linkrev=commits.index(linknode)
)
# Test adding linknode and linkrev
try:
packer.add(
"",
self.getFakeHash(),
self.getFakeHash(),
nullid,
self.getFakeHash(),
"",
5,
)
self.assertFalse(True, "Adding linknode and linkrev should've " "thrown")
except error.ProgrammingError:
pass
# Test getancestors before finalizing
try:
filename, node = revisions[0][:2]
packer.getancestors(filename, node)
self.assertFalse(True, "Reading data before finalizing should've " "thrown")
except error.ProgrammingError:
pass
# "Commit" the commits to the changelog
repo.changelog.commits = commits
# Verify reading from the mutable store
for filename, node, p1, p2, linknode, copyfrom in revisions:
entry = packer.getnodeinfo(filename, node)
self.assertEquals(entry, (p1, p2, linknode, copyfrom))
path = packer.close()
pack = historypack(path)
# Verify reading from the on disk pack
for filename, node, p1, p2, linknode, copyfrom in revisions:
entry = pack.getnodeinfo(filename, node)
copyfrom = None if not copyfrom else copyfrom
self.assertEquals(entry, (p1, p2, linknode, copyfrom))
# TODO:
# histpack store:
# - repack two packs into one
if __name__ == "__main__":
silenttestrunner.main(__name__)