Add integration test for rebasing in Hg.

Summary:
This is a relatively simple test that takes two branches with no conflicts and
rebases one on top of the other. It also provides modest checks to ensure Eden
does not load a bunch of inodes unnecessarily when updating to the new head.

This also introduces `EdenServerInspector`, which provides convenience methods
for inspecting the Eden server via Thrift.

Reviewed By: simpkins

Differential Revision: D5504741

fbshipit-source-id: 6636c431658f24a850d0e5404d1a0e4f0528a781
This commit is contained in:
Michael Bolin 2017-07-27 17:18:19 -07:00 committed by Facebook Github Bot
parent b9b9ba32e9
commit 31eac649e2
3 changed files with 147 additions and 1 deletions

View File

@ -8,9 +8,10 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from .client import EdenNotRunningError, create_thrift_client
from .client import EdenClient, EdenNotRunningError, create_thrift_client
__all__ = [
'EdenClient',
'EdenNotRunningError',
'create_thrift_client',
]

View File

@ -0,0 +1,91 @@
#!/usr/bin/env python3
#
# Copyright (c) 2016-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
from .lib.hg_extension_test_base import HgExtensionTestBase
from ..lib import eden_server_inspector
class RebaseTest(HgExtensionTestBase):
def populate_backing_repo(self, repo):
repo.mkdir('numbers')
repo.write_file('numbers/README', 'this will have two directories')
self._base_commit = repo.commit('commit')
repo.mkdir('numbers/1')
repo.write_file('numbers/1/11', '')
self._c11 = repo.commit('c11')
repo.write_file('numbers/1/12', '')
self._c12 = repo.commit('c12')
repo.write_file('numbers/1/13', '')
self._c13 = repo.commit('c13')
repo.write_file('numbers/1/14', '')
self._c14 = repo.commit('c14')
repo.write_file('numbers/1/15', '')
self._c15 = repo.commit('c15')
repo.update(self._base_commit)
repo.mkdir('numbers/2')
repo.write_file('numbers/2/21', '')
self._c21 = repo.commit('c21')
repo.write_file('numbers/2/22', '')
self._c22 = repo.commit('c22')
repo.write_file('numbers/2/23', '')
self._c23 = repo.commit('c23')
repo.write_file('numbers/2/24', '')
self._c24 = repo.commit('c24')
repo.write_file('numbers/2/25', '')
self._c25 = repo.commit('c25')
repo.update(self._base_commit)
def test_rebase_commit_with_independent_folder(self):
stdout = self.hg('rebase', '-s', self._c11, '-d', self._c25)
expected_stdout = f'''\
rebasing 1:{self._c11[:12]} "c11"
rebasing 2:{self._c12[:12]} "c12"
rebasing 3:{self._c13[:12]} "c13"
rebasing 4:{self._c14[:12]} "c14"
rebasing 5:{self._c15[:12]} "c15"
'''
self.assertEqual(expected_stdout, stdout)
# Get the hash of the new head created as a result of the rebase.
new_head = self.hg(
'log', '-r', f'successors({self._c15})', '-T', '{node}'
)
# Record the pre-update inode count.
inspector = eden_server_inspector.EdenServerInspector(self.repo.path)
inspector.unload_inode_for_path('numbers')
pre_update_count = inspector.get_inode_count('numbers')
print(f'loaded inode count before `hg update`: {pre_update_count}')
# Verify that updating to the new head that was created as a result of
# the rebase leaves Hg in the correct state.
self.assertEqual(1, len(self.repo.log()), msg=(
'At the base commit, `hg log` should have only one entry.'
))
self.hg('update', new_head)
self.assertEqual(11, len(self.repo.log()), msg=(
'The new head should include all the commits.'
))
# Verify the post-update inode count.
post_update_count = inspector.get_inode_count('numbers')
print(f'loaded inode count after `hg update`: {post_update_count}')
self.assertGreaterEqual(post_update_count, pre_update_count, msg=(
'The inode count should not decrease due to `hg update`.'
))
num_new_inodes = post_update_count - pre_update_count
self.assertLessEqual(num_new_inodes, 2, msg=(
'There should be no more than 2 new inodes as a result of the '
'update. At the time this test was created, num_new_inodes is 0, '
'but if we included unloaded inodes, there would be 2: one for '
'numbers/1 and one for numbers/2.'
))

View File

@ -0,0 +1,54 @@
#!/usr/bin/env python3
#
# Copyright (c) 2016-present, Facebook, Inc.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree. An additional grant
# of patent rights can be found in the PATENTS file in the same directory.
import eden.thrift
from typing import Iterable
'''Utilities for inspecting the state of the Eden server via Thrift.
This utility is parameterized by a specific mount point so that it need not be
specified for each instance method.
'''
class EdenServerInspector(object):
def __init__(self, mount_point: str) -> None:
self._mount_point = mount_point
def create_thrift_client(self) -> eden.thrift.EdenClient:
return eden.thrift.create_thrift_client(mounted_path=self._mount_point)
def unload_inode_for_path(self, path: str='') -> None:
'''path: relative path to a directory under the mount.'''
with self.create_thrift_client() as client:
client.unloadInodeForPath(self._mount_point, path)
def get_inode_count(self, path: str='') -> int:
'''path: relative path to a directory under the mount.
Use '' for the root. Note that this will include the inode count for
the root .hg and .eden entries.
'''
with self.create_thrift_client() as client:
debug_info = client.debugInodeStatus(self._mount_point, path)
count = 0
for tree_inode_debug_info in debug_info:
count += sum(1 for entry in tree_inode_debug_info.entries
if entry.loaded)
return count
def get_paths_for_inodes(self, path: str='') -> Iterable[str]:
'''path: relative path to a directory under the mount.'''
with self.create_thrift_client() as client:
debug_info = client.debugInodeStatus(self._mount_point, path)
for tree_inode_debug_info in debug_info:
parent_dir = tree_inode_debug_info.path.decode('utf-8')
for entry in tree_inode_debug_info.entries:
if entry.loaded:
yield f'{parent_dir}/{entry.name.decode("utf-8")}'