tests/test-rust-revlog.py
author Pierre-Yves David <pierre-yves.david@octobus.net>
Tue, 09 Apr 2024 02:54:12 +0200
changeset 51577 b5d494f7d28a
parent 51254 f94c10334bcb
permissions -rw-r--r--
push: rework the computation of fallbackheads to be correct The previous computation tried to be smart but ended up being wrong. This was caught by phase movement test while reworking the phase discovery logic to be faster. The previous logic was failing to catch case where the pushed set was not based on a common heads (i.e. when the discovery seemed to have "over discovered" content, outside the pushed set) In the following graph, `e` is a common head and we `hg push -r f`. We need to detect `c` as a fallback heads and we previous failed to do so:: e | d f |/ c | b | a The performance impact of the change seems minimal. On the most impacted repository at hand (mozilla-try), the slowdown seems mostly mixed in the overall noise `hg push` but seems to be in the hundred of milliseconds order of magnitude. When using rust, we seems to be a bit faster, probably because we leverage more accelaratd internals. I added a couple of performance related common for further investigation later on.

import struct
import unittest

from mercurial.node import hex

try:
    from mercurial import rustext

    rustext.__name__  # trigger immediate actual import
except ImportError:
    rustext = None
else:
    from mercurial.rustext import revlog

    # this would fail already without appropriate ancestor.__package__
    from mercurial.rustext.ancestor import LazyAncestors

from mercurial.testing import revlog as revlogtesting

header = struct.unpack(">I", revlogtesting.data_non_inlined[:4])[0]


@unittest.skipIf(
    rustext is None,
    "rustext module revlog relies on is not available",
)
class RustRevlogIndexTest(revlogtesting.RevlogBasedTestBase):
    def test_heads(self):
        idx = self.parseindex()
        rustidx = revlog.Index(revlogtesting.data_non_inlined, header)
        self.assertEqual(rustidx.headrevs(), idx.headrevs())

    def test_len(self):
        idx = self.parseindex()
        rustidx = revlog.Index(revlogtesting.data_non_inlined, header)
        self.assertEqual(len(rustidx), len(idx))

    def test_ancestors(self):
        rustidx = revlog.Index(revlogtesting.data_non_inlined, header)
        lazy = LazyAncestors(rustidx, [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.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])

        # let's check bool for an empty one
        self.assertFalse(LazyAncestors(rustidx, [0], 0, False))


@unittest.skipIf(
    rustext is None,
    "rustext module revlog relies on is not available",
)
class RustRevlogNodeTreeClassTest(revlogtesting.RustRevlogBasedTestBase):
    def test_standalone_nodetree(self):
        idx = self.parserustindex()
        nt = revlog.NodeTree(idx)
        for i in range(4):
            nt.insert(i)

        bin_nodes = [entry[7] for entry in idx]
        hex_nodes = [hex(n) for n in bin_nodes]

        for i, node in enumerate(hex_nodes):
            self.assertEqual(nt.prefix_rev_lookup(node), i)
            self.assertEqual(nt.prefix_rev_lookup(node[:5]), i)

        # all 4 revisions in idx (standard data set) have different
        # first nybbles in their Node IDs,
        # hence `nt.shortest()` should return 1 for them, except when
        # the leading nybble is 0 (ambiguity with NULL_NODE)
        for i, (bin_node, hex_node) in enumerate(zip(bin_nodes, hex_nodes)):
            shortest = nt.shortest(bin_node)
            expected = 2 if hex_node[0] == ord('0') else 1
            self.assertEqual(shortest, expected)
            self.assertEqual(nt.prefix_rev_lookup(hex_node[:shortest]), i)

        # test invalidation (generation poisoning) detection
        del idx[3]
        self.assertTrue(nt.is_invalidated())


if __name__ == '__main__':
    import silenttestrunner

    silenttestrunner.main(__name__)