Mercurial > hg
view tests/test-rust-ancestor.py @ 41188:006c9ce486fa
rust-cpython: bindings for MissingAncestors
The exposition is rather straightforward, except for the
remove_ancestors_from() method, which forces us to an inefficient
conversion between Python sets and Rust HashSets.
Two alternatives are proposed in comments:
- changing the inner API to "emit" the revision numbers to discard
this would be a substantial change, and it would be better only in the
cases where there are more to retain than to discard
- mutating the Python set directly: this would force us to define an abstract
`RevisionSet` trait, and implement it both for plain `HashSet` and for
a struct enclosing a Python set with the GIL marker `Python<'p>`, also a non
trivial effort.
The main (and seemingly only) caller of this method being
`mercurial.setdiscovery`, which is currently undergoing serious refactoring,
it's not clear whether these improvements would be worth the effort right now,
so we're leaving it as-is.
Also, in `get_bases()` (will also be used by `setdiscovery`), we'd prefer
to build a Python set directly, but we resort to returning a tuple, waiting
to hear back from our PR onto rust-cpython about that
Differential Revision: https://phab.mercurial-scm.org/D5550
author | Georges Racinet <georges.racinet@octobus.net> |
---|---|
date | Fri, 30 Nov 2018 20:05:34 +0100 |
parents | b31a41f24864 |
children | 5257e6299d4c |
line wrap: on
line source
from __future__ import absolute_import import sys import unittest try: from mercurial import rustext rustext.__name__ # trigger immediate actual import except ImportError: rustext = None else: # this would fail already without appropriate ancestor.__package__ from mercurial.rustext.ancestor import ( AncestorsIterator, LazyAncestors, MissingAncestors, ) try: from mercurial.cext import parsers as cparsers except ImportError: cparsers = None # picked from test-parse-index2, copied rather than imported # so that it stays stable even if test-parse-index2 changes or disappears. data_non_inlined = ( b'\x00\x00\x00\x01\x00\x00\x00\x00\x00\x01D\x19' b'\x00\x07e\x12\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\xff\xff' b'\xff\xff\xff\xff\xd1\xf4\xbb\xb0\xbe\xfc\x13\xbd\x8c\xd3\x9d' b'\x0f\xcd\xd9;\x8c\x07\x8cJ/\x00\x00\x00\x00\x00\x00\x00\x00\x00' b'\x00\x00\x00\x00\x00\x00\x01D\x19\x00\x00\x00\x00\x00\xdf\x00' b'\x00\x01q\x00\x00\x00\x01\x00\x00\x00\x01\x00\x00\x00\x00\xff' b'\xff\xff\xff\xc1\x12\xb9\x04\x96\xa4Z1t\x91\xdfsJ\x90\xf0\x9bh' b'\x07l&\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' b'\x00\x01D\xf8\x00\x00\x00\x00\x01\x1b\x00\x00\x01\xb8\x00\x00' b'\x00\x01\x00\x00\x00\x02\x00\x00\x00\x01\xff\xff\xff\xff\x02\n' b'\x0e\xc6&\xa1\x92\xae6\x0b\x02i\xfe-\xe5\xbao\x05\xd1\xe7\x00' b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01F' b'\x13\x00\x00\x00\x00\x01\xec\x00\x00\x03\x06\x00\x00\x00\x01' b'\x00\x00\x00\x03\x00\x00\x00\x02\xff\xff\xff\xff\x12\xcb\xeby1' b'\xb6\r\x98B\xcb\x07\xbd`\x8f\x92\xd9\xc4\x84\xbdK\x00\x00\x00' b'\x00\x00\x00\x00\x00\x00\x00\x00\x00' ) @unittest.skipIf(rustext is None or cparsers is None, "rustext or the C Extension parsers module " "ancestor relies on is not available") class rustancestorstest(unittest.TestCase): """Test the correctness of binding to Rust code. This test is merely for the binding to Rust itself: extraction of Python variable, giving back the results etc. It is not meant to test the algorithmic correctness of the operations on ancestors it provides. Hence the very simple embedded index data is good enough. Algorithmic correctness is asserted by the Rust unit tests. """ def parseindex(self): return cparsers.parse_index2(data_non_inlined, False)[0] def testiteratorrevlist(self): idx = self.parseindex() # checking test assumption about the index binary data: self.assertEqual({i: (r[5], r[6]) for i, r in enumerate(idx)}, {0: (-1, -1), 1: (0, -1), 2: (1, -1), 3: (2, -1)}) ait = AncestorsIterator(idx, [3], 0, True) self.assertEqual([r for r in ait], [3, 2, 1, 0]) ait = AncestorsIterator(idx, [3], 0, False) self.assertEqual([r for r in ait], [2, 1, 0]) def testlazyancestors(self): idx = self.parseindex() start_count = sys.getrefcount(idx) # should be 2 (see Python doc) self.assertEqual({i: (r[5], r[6]) for i, r in enumerate(idx)}, {0: (-1, -1), 1: (0, -1), 2: (1, -1), 3: (2, -1)}) lazy = LazyAncestors(idx, [3], 0, True) # we have two more references to the index: # - in its inner iterator for __contains__ and __bool__ # - in the LazyAncestors instance itself (to spawn new iterators) self.assertEqual(sys.getrefcount(idx), start_count + 2) self.assertTrue(2 in lazy) self.assertTrue(bool(lazy)) self.assertEqual(list(lazy), [3, 2, 1, 0]) # a second time to validate that we spawn new iterators self.assertEqual(list(lazy), [3, 2, 1, 0]) # now let's watch the refcounts closer ait = iter(lazy) self.assertEqual(sys.getrefcount(idx), start_count + 3) del ait self.assertEqual(sys.getrefcount(idx), start_count + 2) del lazy self.assertEqual(sys.getrefcount(idx), start_count) # let's check bool for an empty one self.assertFalse(LazyAncestors(idx, [0], 0, False)) def testmissingancestors(self): idx = self.parseindex() missanc = MissingAncestors(idx, [1]) self.assertTrue(missanc.hasbases()) self.assertEqual(missanc.missingancestors([3]), [2, 3]) missanc.addbases({2}) self.assertEqual(set(missanc.bases()), {1, 2}) self.assertEqual(missanc.missingancestors([3]), [3]) def testmissingancestorsremove(self): idx = self.parseindex() missanc = MissingAncestors(idx, [1]) revs = {0, 1, 2, 3} missanc.removeancestorsfrom(revs) self.assertEqual(revs, {2, 3}) def testrefcount(self): idx = self.parseindex() start_count = sys.getrefcount(idx) # refcount increases upon iterator init... ait = AncestorsIterator(idx, [3], 0, True) self.assertEqual(sys.getrefcount(idx), start_count + 1) self.assertEqual(next(ait), 3) # and decreases once the iterator is removed del ait self.assertEqual(sys.getrefcount(idx), start_count) # and removing ref to the index after iterator init is no issue ait = AncestorsIterator(idx, [3], 0, True) del idx self.assertEqual(list(ait), [3, 2, 1, 0]) def testgrapherror(self): data = (data_non_inlined[:64 + 27] + b'\xf2' + data_non_inlined[64 + 28:]) idx = cparsers.parse_index2(data, False)[0] with self.assertRaises(rustext.GraphError) as arc: AncestorsIterator(idx, [1], -1, False) exc = arc.exception self.assertIsInstance(exc, ValueError) # rust-cpython issues appropriate str instances for Python 2 and 3 self.assertEqual(exc.args, ('ParentOutOfRange', 1)) if __name__ == '__main__': import silenttestrunner silenttestrunner.main(__name__)