1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208
|
import sys
from mercurial.node import wdirrev
from mercurial.testing import revlog as revlogtesting
try:
from mercurial import pyo3_rustext
pyo3_rustext.__name__
except ImportError:
pyo3_rustext = None
try:
from mercurial.cext import parsers as cparsers
except ImportError:
cparsers = None
class RustAncestorsTestMixin:
"""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.
At this point, we have two sets of bindings, in `hg-cpython` and
`hg-pyo3`. This class used to be for the first and now contains
the tests that are identical in both bindings. As of this writing,
there are more implementations in `hg-cpython` than `hg-pyo3`, hence
some more tests in the subclass for `hg-cpython`. When the work on PyO3
is complete, the subclasses for `hg-cpython` should have no specific
test left. Later on, when we remove the dead code in `hg-cpython`, the tests
should migrate from the mixin to the class for `hg-pyo3`, until we can
simply remove the mixin.
"""
@classmethod
def ancestors_mod(cls):
return pyo3_rustext.ancestor
@classmethod
def dagop_mod(cls):
return pyo3_rustext.dagop
@classmethod
def graph_error(cls):
return pyo3_rustext.GraphError
def testiteratorrevlist(self):
AncestorsIterator = self.ancestors_mod().AncestorsIterator
idx = self.parserustindex()
# 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])
ait = AncestorsIterator(idx, [3], 0, False)
# tainting the index with a mutation, let's see what happens
# (should be more critical with AncestorsIterator)
del idx[0:2]
try:
next(ait)
except RuntimeError as exc:
assert "leaked reference after mutation" in exc.args[0]
else:
raise AssertionError("Expected an exception")
def testlazyancestors(self):
LazyAncestors = self.ancestors_mod().LazyAncestors
idx = self.parserustindex()
start_count = sys.getrefcount(idx.inner) # 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)
# the LazyAncestors instance holds just one reference to the
# inner revlog. TODO check that this is normal
self.assertEqual(sys.getrefcount(idx.inner), start_count + 1)
self.assertTrue(2 in lazy)
self.assertTrue(bool(lazy))
self.assertFalse(None in 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.inner), start_count + 2)
del ait
self.assertEqual(sys.getrefcount(idx.inner), start_count + 1)
del lazy
self.assertEqual(sys.getrefcount(idx.inner), start_count)
# let's check bool for an empty one
self.assertFalse(LazyAncestors(idx, [0], 0, False))
def testrefcount(self):
AncestorsIterator = self.ancestors_mod().AncestorsIterator
idx = self.parserustindex()
start_count = sys.getrefcount(idx.inner)
# refcount increases upon iterator init...
ait = AncestorsIterator(idx, [3], 0, True)
self.assertEqual(sys.getrefcount(idx.inner), start_count + 1)
self.assertEqual(next(ait), 3)
# and decreases once the iterator is removed
del ait
self.assertEqual(sys.getrefcount(idx.inner), 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])
# the index is not tracked by the GC, hence there is nothing more
# we can assert to check that it is properly deleted once its refcount
# drops to 0
def testgrapherror(self):
AncestorsIterator = self.ancestors_mod().AncestorsIterator
GraphError = self.graph_error()
data = (
revlogtesting.data_non_inlined[: 64 + 27]
+ b'\xf2'
+ revlogtesting.data_non_inlined[64 + 28 :]
)
idx = self.parserustindex(data=data)
with self.assertRaises(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))
def testwdirunsupported(self):
AncestorsIterator = self.ancestors_mod().AncestorsIterator
GraphError = self.graph_error()
# trying to access ancestors of the working directory raises
idx = self.parserustindex()
with self.assertRaises(GraphError) as arc:
list(AncestorsIterator(idx, [wdirrev], -1, False))
exc = arc.exception
self.assertIsInstance(exc, ValueError)
# rust-cpython issues appropriate str instances for Python 2 and 3
self.assertEqual(exc.args, ('InvalidRevision', wdirrev))
def testheadrevs(self):
dagop = self.dagop_mod()
idx = self.parserustindex()
self.assertEqual(dagop.headrevs(idx, [1, 2, 3]), {3})
def testmissingancestors(self):
MissingAncestors = self.ancestors_mod().MissingAncestors
idx = self.parserustindex()
missanc = MissingAncestors(idx, [1])
self.assertTrue(missanc.hasbases())
self.assertEqual(missanc.missingancestors([3]), [2, 3])
missanc.addbases({2})
self.assertEqual(missanc.bases(), {1, 2})
self.assertEqual(missanc.missingancestors([3]), [3])
self.assertEqual(missanc.basesheads(), {2})
def testmissingancestorsremove(self):
MissingAncestors = self.ancestors_mod().MissingAncestors
idx = self.parserustindex()
missanc = MissingAncestors(idx, [1])
revs = {0, 1, 2, 3}
missanc.removeancestorsfrom(revs)
self.assertEqual(revs, {2, 3})
def test_rank(self):
dagop = self.dagop_mod()
idx = self.parserustindex()
try:
dagop.rank(idx, 1, 2)
except pyo3_rustext.GraphError as exc:
self.assertEqual(exc.args, ("InconsistentGraphData",))
if __name__ == '__main__':
import silenttestrunner
silenttestrunner.main(__name__)
|