sapling/eden/integration/hg/rebase_test.py
Adam Simpkins 9beff99012 add type annotations to most of the integration test functions
Summary:
This updates the integration tests to add type annotations to most functions
that were missing annotations.

In particular this is needed to make pyre happy, as it complains if subclasses
override methods from their parent class and do not specify type annotations
if the parent class did have annotations.

This diff also contains some minor changes to hg_extension_test_base.py to
explicitly declare some abstract methods that it uses.  This was also
necessary to make pyre happy about this ocde.

Reviewed By: strager

Differential Revision: D13051097

fbshipit-source-id: 77567ed2f4d3050f93acefb52e688932d276d587
2018-11-16 19:47:48 -08:00

310 lines
11 KiB
Python

#!/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 os
import threading
from eden.integration.lib import eden_server_inspector, hgrepo
from .lib.hg_extension_test_base import EdenHgTestCase, hg_test
@hg_test
class RebaseTest(EdenHgTestCase):
_base_commit: str
_c11: str
_c12: str
_c13: str
_c14: str
_c15: str
_c21: str
_c22: str
_c23: str
_c24: str
_c25: str
def populate_backing_repo(self, repo: hgrepo.HgRepository) -> None:
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", "11\n")
self._c11 = repo.commit("c11")
repo.write_file("numbers/1/12", "12\n")
self._c12 = repo.commit("c12")
repo.write_file("numbers/1/13", "13\n")
self._c13 = repo.commit("c13")
repo.write_file("numbers/1/14", "14\n")
self._c14 = repo.commit("c14")
repo.write_file("numbers/1/15", "15\n")
self._c15 = repo.commit("c15")
repo.update(self._base_commit)
repo.mkdir("numbers/2")
repo.write_file("numbers/2/21", "21\n")
self._c21 = repo.commit("c21")
repo.write_file("numbers/2/22", "22\n")
self._c22 = repo.commit("c22")
repo.write_file("numbers/2/23", "23\n")
self._c23 = repo.commit("c23")
repo.write_file("numbers/2/24", "24\n")
self._c24 = repo.commit("c24")
repo.write_file("numbers/2/25", "25\n")
self._c25 = repo.commit("c25")
repo.update(self._base_commit)
def test_rebase_commit_with_independent_folder(self) -> None:
stdout = self.hg("--debug", "rebase", "-s", self._c11, "-d", self._c25)
self.assertIn(f'rebasing 1:{self._c11[:12]} "c11"\n', stdout)
self.assertIn(f'rebasing 2:{self._c12[:12]} "c12"\n', stdout)
self.assertIn(f'rebasing 3:{self._c13[:12]} "c13"\n', stdout)
self.assertIn(f'rebasing 4:{self._c14[:12]} "c14"\n', stdout)
self.assertIn(f'rebasing 5:{self._c15[:12]} "c15"\n', stdout)
# Note that these are empirical values, not desired values.
# We need to figure out why this hits the slow path and fix it!
self.assert_update_logic(stdout, num_fast_path=2, num_slow_path=5)
# Get the hash of the new head created as a result of the rebase.
new_head = self.repo.log(revset=f"successors({self._c15})-{self._c15}")[0]
# Record the pre-update inode count.
inspector = eden_server_inspector.EdenServerInspector(self.eden, 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."),
)
stdout = self.hg("--debug", "update", new_head)
self.assert_update_logic(stdout, num_fast_path=1)
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."
),
)
def test_rebasing_a_commit_that_removes_a_file(self) -> None:
# Rebase a commit that removes the numbers/README file.
self.hg("rm", "numbers/README")
removal_commit = self.repo.commit("removing README")
self.hg("rebase", "-s", removal_commit, "-d", self._c15)
# Verify we end up in the expected state.
self.assert_status_empty()
self.assertFalse(os.path.exists(self.get_path("numbers/README")))
self.assertEqual(7, len(self.repo.log()))
def test_rebase_stack_with_conflicts(self) -> None:
"""Create a stack of commits that has conflicts with the stack onto
which we rebase and verify that if we merge the expected conflicts along
the way, then we end up in the expected state."""
self.mkdir("numbers/1")
self.write_file("numbers/1/11", "new 11\n")
self.repo.add_file("numbers/1/11")
self.write_file("numbers/1/12", "new 12\n")
self.repo.add_file("numbers/1/12")
commit = self.repo.commit("Introduce 1/11 and 1/12.")
self.write_file("numbers/1/12", "change 12 again\n")
self.write_file("numbers/1/13", "new 13\n")
self.repo.add_file("numbers/1/13")
self.write_file("numbers/1/14", "new 14\n")
self.repo.add_file("numbers/1/14")
self.repo.commit("Introduce 1/13 and 1/14.")
with self.assertRaises(hgrepo.HgError) as context:
self.hg("rebase", "-s", commit, "-d", self._c15)
self.assertIn(
b"conflicts while merging numbers/1/11! "
b"(edit, then use 'hg resolve --mark')",
context.exception.stderr,
)
self.assert_unresolved(unresolved=["numbers/1/11", "numbers/1/12"])
self.assert_status({"numbers/1/11": "M", "numbers/1/12": "M"}, op="rebase")
self.assert_file_regex(
"numbers/1/11",
"""\
<<<<<<< dest: .*
11
=======
new 11
>>>>>>> source: .*
""",
)
self.assert_file_regex(
"numbers/1/12",
"""\
<<<<<<< dest: .*
12
=======
new 12
>>>>>>> source: .*
""",
)
self.write_file("numbers/1/11", "11 merged.\n")
self.write_file("numbers/1/12", "12 merged.\n")
self.hg("resolve", "--mark", "numbers/1/11", "numbers/1/12")
with self.assertRaises(hgrepo.HgError) as context:
self.hg("rebase", "--continue")
self.assertIn(
b"conflicts while merging numbers/1/12! "
b"(edit, then use 'hg resolve --mark')",
context.exception.stderr,
)
self.assert_unresolved(
unresolved=["numbers/1/12", "numbers/1/13", "numbers/1/14"]
)
self.assert_status(
{"numbers/1/12": "M", "numbers/1/13": "M", "numbers/1/14": "M"}, op="rebase"
)
self.assert_file_regex(
"numbers/1/12",
"""\
<<<<<<< dest: .*
12 merged.
=======
change 12 again
>>>>>>> source: .*
""",
)
self.assert_file_regex(
"numbers/1/13",
"""\
<<<<<<< dest: .*
13
=======
new 13
>>>>>>> source: .*
""",
)
self.assert_file_regex(
"numbers/1/14",
"""\
<<<<<<< dest: .*
14
=======
new 14
>>>>>>> source: .*
""",
)
self.write_file("numbers/1/12", "merged.\n")
self.write_file("numbers/1/13", "merged.\n")
self.write_file("numbers/1/14", "merged.\n")
self.hg("resolve", "--mark", "numbers/1/12", "numbers/1/13", "numbers/1/14")
self.hg("rebase", "--continue")
commits = self.repo.log()
self.assertEqual(8, len(commits))
self.assertEqual(
[self._base_commit, self._c11, self._c12, self._c13, self._c14, self._c15],
commits[0:6],
)
def assert_update_logic(
self, stdout: str, num_fast_path: int = 0, num_slow_path: int = 0
) -> None:
"""Helper function to examine the stdout of an `hg --debug update` call
and verify the number of times our Hg extension exercised the "fast
path" for Eden when doing an update versus the number of times it
exercised the "slow path."
"""
self.assertEqual(
num_fast_path,
stdout.count("using eden update code path\n"),
msg=(
"Number of times `hg update` should exercise the fast path: "
+ str(num_fast_path)
),
)
self.assertEqual(
num_slow_path,
stdout.count("falling back to non-eden update code path: "),
msg=(
"Number of times `hg update` should exercise the slow path: "
+ str(num_slow_path)
),
)
def test_rebase_with_concurrent_status(self) -> None:
"""
Test using `hg rebase` to rebase a stack while simultaneously running
`hg status`
"""
stop = threading.Event()
def status_thread():
while not stop.is_set():
self.repo.run_hg("status", stdout=None, stderr=None)
# Spawn several threads to run "hg status" in parallel with the rebase
num_threads = 6
threads = []
for _ in range(num_threads):
t = threading.Thread(target=status_thread)
threads.append(t)
t.start()
# Run the rebase. Explicitly disable inmemory rebase so that eden
# will need to update the working directory state as tehe rebase progresses
self.repo.run_hg(
"--debug",
"--config",
"rebase.experimental.inmemory=False",
"rebase",
"-s",
self._c11,
"-d",
self._c25,
stdout=None,
stderr=None,
)
new_commit = self.hg("log", "-rtip", "-T{node}")
stop.set()
for t in threads:
t.join()
self.assert_status_empty()
# Verify that the new commit looks correct
self.repo.update(new_commit)
self.assert_status_empty()
self.assert_file_regex("numbers/1/15", "15\n")
self.assert_file_regex("numbers/2/25", "25\n")