github.com/treeverse/lakefs@v1.24.1-0.20240520134607-95648127bfb0/clients/python-wrapper/tests/integration/test_branch.py (about)

     1  import pytest
     2  
     3  try:
     4      from pydantic.v1 import ValidationError
     5  except ImportError:
     6      from pydantic import ValidationError
     7  
     8  import lakefs
     9  from lakefs.exceptions import NotFoundException, TransactionException
    10  from tests.utests.common import expect_exception_context
    11  
    12  
    13  def test_revert(setup_repo):
    14      _, repo = setup_repo
    15      test_branch = repo.branch("main")
    16      initial_content = "test_content"
    17      test_branch.object("test_object").upload(initial_content)
    18      test_branch.commit("test_commit", {"test_key": "test_value"})
    19  
    20      override_content = "override_test_content"
    21      obj = test_branch.object("test_object").upload(override_content)
    22      test_branch.commit("override_data")
    23  
    24      with obj.reader(mode='r') as fd:
    25          assert fd.read() == override_content
    26  
    27      c = test_branch.revert(test_branch.head)
    28      assert c.message.startswith("Revert")
    29  
    30      with obj.reader(mode='r') as fd:
    31          assert fd.read() == initial_content
    32  
    33  
    34  def test_cherry_pick(setup_repo):
    35      _, repo = setup_repo
    36      main_branch = repo.branch("main")
    37      test_branch = repo.branch("testest").create("main")
    38  
    39      initial_content = "test_content"
    40      test_branch.object("test_object").upload(initial_content)
    41      testcommit = test_branch.commit("test_commit", {"test_key": "test_value"}).get_commit()
    42  
    43      cherry_picked = main_branch.cherry_pick(test_branch.head)
    44      assert test_branch.object("test_object").exists()
    45      # SHAs are not equal, so we exclude them from eq checks.
    46      assert cherry_picked.message == testcommit.message
    47      # cherry-picks have origin and source ref name attached as metadata (at minimum),
    48      # so we only check that the additional user-supplied metadata is present.
    49      assert set(testcommit.metadata.items()) <= set(cherry_picked.metadata.items())
    50      # check that the cherry-pick origin is exactly testest@HEAD.
    51      assert cherry_picked.metadata["cherry-pick-origin"] == testcommit.id
    52  
    53  
    54  def test_reset_changes(setup_repo):
    55      _, repo = setup_repo
    56      test_branch = repo.branch("main")
    57      paths = ["a", "b", "bar/a", "bar/b", "bar/c", "c", "foo/a", "foo/b", "foo/c", ]
    58      upload_data(test_branch, paths)
    59  
    60      validate_uncommitted_changes(test_branch, paths)
    61  
    62      validate_uncommitted_changes(test_branch, ["bar/a", "bar/b", "bar/c"], prefix="bar")
    63      test_branch.reset_changes("object", "bar/a")
    64      validate_uncommitted_changes(test_branch, ["a", "b", "bar/b", "bar/c", "c", "foo/a", "foo/b", "foo/c", ])
    65  
    66      test_branch.reset_changes("object", "bar/")
    67  
    68      validate_uncommitted_changes(test_branch, ["a", "b", "bar/b", "bar/c", "c", "foo/a", "foo/b", "foo/c", ])
    69  
    70      test_branch.reset_changes("common_prefix", "foo/")
    71      validate_uncommitted_changes(test_branch, ["a", "b", "bar/b", "bar/c", "c"])
    72  
    73      test_branch.reset_changes()
    74      validate_uncommitted_changes(test_branch, [])
    75  
    76  
    77  def test_delete_object_changes(setup_repo):
    78      _, repo = setup_repo
    79      test_branch = repo.branch("main")
    80      path_and_data = ["a", "b", "bar/a", "bar/b", "bar/c", "c", "foo/a", "foo/b", "foo/c"]
    81      upload_data(test_branch, path_and_data)
    82      test_branch.commit("add some files", {"test_key": "test_value"})
    83  
    84      test_branch.delete_objects("foo/a")
    85      validate_uncommitted_changes(test_branch, ["foo/a"], "removed")
    86  
    87      paths = {"foo/b", "foo/c"}
    88      test_branch.delete_objects(paths)
    89      validate_uncommitted_changes(test_branch, ["foo/a", "foo/b", "foo/c"], "removed")
    90      repo = lakefs.Repository(test_branch.repo_id)
    91      test_branch.delete_objects([repo.ref(test_branch.head).object("a"), repo.ref(test_branch.head).object("b")])
    92      validate_uncommitted_changes(test_branch, ["a", "b", "foo/a", "foo/b", "foo/c"], "removed")
    93      with expect_exception_context(ValidationError):
    94          test_branch.reset_changes("unknown", "foo/")
    95  
    96  
    97  def upload_data(branch, path_and_data, multiplier=1):
    98      for s in path_and_data:
    99          branch.object(s).upload(s * multiplier)
   100  
   101  
   102  def validate_uncommitted_changes(branch, expected, change_type="added", prefix=""):
   103      count = 0
   104      for index, change in enumerate(branch.uncommitted(max_amount=10, prefix=prefix)):
   105          assert change.path == expected[index]
   106          assert change.path_type == "object"
   107          assert change.type == change_type
   108          assert change.size_bytes == 0 if change_type == "removed" else len(expected[index])
   109          count += 1
   110      if count != len(expected):
   111          raise AssertionError(f"Expected {len(expected)} changes, got {count}")
   112  
   113  
   114  def test_transaction(setup_repo):
   115      _, repo = setup_repo
   116      path_and_data1 = ["a", "b", "bar/a", "bar/b", "bar/c", "c"]
   117      path_and_data2 = ["foo/a", "foo/b", "foo/c"]
   118      test_branch = repo.branch("main")
   119  
   120      with test_branch.transact(commit_message="my transaction", commit_metadata={"foo": "bar"}) as tx:
   121          assert tx.get_commit().id == test_branch.head.id
   122          upload_data(tx, path_and_data1)
   123          upload_data(tx, path_and_data2)
   124          tx.reset_changes(path_type="common_prefix", path="foo")
   125          tx_id = tx.id
   126  
   127      # Verify transaction branch was deleted
   128      with expect_exception_context(NotFoundException):
   129          repo.branch(tx.id).get_commit()
   130  
   131      #  Verify transaction completed successfully
   132      log = list(test_branch.log(amount=2))
   133      assert log[0].message == f"Merge transaction {tx_id} to branch"
   134      assert log[1].message == "my transaction"
   135      assert log[1].metadata.get("foo") == "bar"
   136  
   137      for obj in path_and_data1:
   138          assert test_branch.object(obj).exists()
   139  
   140      for obj in path_and_data2:
   141          assert not test_branch.object(obj).exists()
   142  
   143      # Reset all changes - ensure no new commits
   144      with expect_exception_context(TransactionException, "no changes"):
   145          with test_branch.transact(commit_message="my transaction", commit_metadata={"foo": "bar"}) as tx:
   146              assert tx.get_commit().id == test_branch.head.id
   147              upload_data(tx, path_and_data2)
   148              tx.reset_changes()
   149  
   150      log = list(test_branch.log(amount=1))
   151      assert log[0].message == f"Merge transaction {tx_id} to branch"
   152  
   153      # Verify transaction branch is deleted when no changes are made
   154      with expect_exception_context(TransactionException, "no changes"):
   155          with test_branch.transact(commit_message="my transaction") as tx:
   156              pass
   157  
   158      with expect_exception_context(NotFoundException):
   159          tx.get_commit()
   160  
   161  
   162  @pytest.mark.parametrize("cleanup_branch", [True, False])
   163  def test_transaction_failure(setup_repo, cleanup_branch):
   164      _, repo = setup_repo
   165      new_data = ["a", "b", "bar/a", "bar/b"]
   166      common_data = ["foo/a", "foo/b", "foo/c"]
   167      test_branch = repo.branch("main")
   168      commit = test_branch.get_commit()
   169  
   170      # Exception during transaction
   171      with expect_exception_context(ValueError, "Something bad happened"):
   172          with test_branch.transact(commit_message="my transaction", delete_branch_on_error=cleanup_branch) as tx:
   173              upload_data(tx, new_data)
   174              raise ValueError("Something bad happened")
   175  
   176      # Ensure tx branch exists and not merged
   177      if not cleanup_branch:
   178          assert tx.get_commit()
   179  
   180      assert test_branch.get_commit() == commit
   181  
   182      # Merge on dirty branch
   183      with expect_exception_context(TransactionException, "dirty branch"):
   184          with test_branch.transact(commit_message="my transaction") as tx:
   185              assert tx.get_commit().id == test_branch.head.id
   186              upload_data(tx, new_data)
   187              upload_data(test_branch, common_data, 2)
   188  
   189      # Ensure tx branch exists and not merged
   190      if not cleanup_branch:
   191          assert tx.get_commit()
   192  
   193      assert test_branch.get_commit() == commit
   194  
   195      # Merge with conflicts
   196      with expect_exception_context(TransactionException, "Conflict"):
   197          with test_branch.transact(commit_message="my transaction") as tx:
   198              new_ref = test_branch.commit(message="test branch commit")
   199              upload_data(tx, common_data)
   200  
   201      with expect_exception_context(NotFoundException):
   202          assert tx.get_commit()
   203  
   204      assert test_branch.get_commit() == new_ref.get_commit()