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()