| #!/usr/bin/env python |
| # |
| # entries_tests.py: test the old entries API using entries-dump |
| # |
| # Subversion is a tool for revision control. |
| # See http://subversion.apache.org for more information. |
| # |
| # ==================================================================== |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| ###################################################################### |
| |
| # |
| # This test series is to validate the old entries API using the entries-dump |
| # tool to see what the API reports. In particular, this test is designed to |
| # try and exercise all "extraordinary" code paths in the read_entries() |
| # function in libsvn_wc/entries.c. Much of that function is exercised by |
| # the regular test suite and its secondary "status" via entries-dump. This |
| # test tries to pick up the straggly little edge cases. |
| # |
| |
| import os, logging |
| |
| logger = logging.getLogger() |
| |
| import svntest |
| |
| Item = svntest.wc.StateItem |
| |
| |
| SCHEDULE_NORMAL = 0 |
| SCHEDULE_ADD = 1 |
| SCHEDULE_DELETE = 2 |
| SCHEDULE_REPLACE = 3 |
| |
| |
| def validate(entry, **kw): |
| for key, value in kw.items(): |
| if getattr(entry, key) != value: |
| logger.warn("Entry '%s' has an incorrect value for .%s", entry.name, key) |
| logger.warn(" Expected: %s", value) |
| logger.warn(" Actual: %s", getattr(entry, key)) |
| raise svntest.Failure |
| |
| |
| def check_names(entries, *names): |
| if entries is None: |
| logger.warn('entries-dump probably exited with a failure.') |
| raise svntest.Failure |
| have = set(entries.keys()) |
| want = set(names) |
| missing = want - have |
| if missing: |
| logger.warn("Entry name(s) not found: %s", |
| ', '.join("'%s'" % name for name in missing)) |
| raise svntest.Failure |
| |
| |
| def basic_entries(sbox): |
| "basic entries behavior" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| alpha_path = os.path.join(wc_dir, 'A', 'B', 'E', 'alpha') |
| beta_path = os.path.join(wc_dir, 'A', 'B', 'E', 'beta') |
| added_path = os.path.join(wc_dir, 'A', 'B', 'E', 'added') |
| G_path = os.path.join(wc_dir, 'A', 'D', 'G') |
| G2_path = os.path.join(wc_dir, 'A', 'D', 'G2') |
| iota_path = os.path.join(wc_dir, 'iota') |
| iota2_path = os.path.join(wc_dir, 'A', 'B', 'E', 'iota2') |
| |
| # Remove 'alpha'. When it is committed, it will be marked DELETED. |
| svntest.actions.run_and_verify_svn(None, [], 'rm', alpha_path) |
| |
| # Tweak 'beta' in order to bump its revision to ensure the replacement |
| # gets the new revision (2), not the value from the parent (1). |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ps', 'random-prop', 'propvalue', |
| beta_path) |
| |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/B/E/alpha' : Item(verb='Deleting'), |
| 'A/B/E/beta' : Item(verb='Sending'), |
| }) |
| expected_status = svntest.actions.get_virginal_state(wc_dir, 1) |
| expected_status.remove('A/B/E/alpha') |
| expected_status.tweak('A/B/E/beta', wc_rev=2) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status, |
| [], |
| alpha_path, beta_path) |
| |
| # bump 'G' and iota another revision (3) for later testing |
| svntest.actions.run_and_verify_svn(None, [], |
| 'ps', 'random-prop', 'propvalue', |
| G_path, iota_path) |
| |
| expected_output = svntest.wc.State(wc_dir, { |
| 'A/D/G' : Item(verb='Sending'), |
| 'iota' : Item(verb='Sending'), |
| }) |
| expected_status.tweak('A/D/G', 'iota', wc_rev=3) |
| svntest.actions.run_and_verify_commit(wc_dir, |
| expected_output, expected_status, |
| [], |
| G_path, iota_path) |
| |
| # Add a file over the DELETED 'alpha'. It should be schedule-add. |
| with open(alpha_path, 'w') as f: |
| f.write('New alpha contents\n') |
| |
| # Delete 'beta', then add a file over it. Should be schedule-replace. |
| svntest.actions.run_and_verify_svn(None, [], 'rm', beta_path) |
| with open(beta_path, 'w') as f: |
| f.write('New beta contents\n') |
| |
| # Plain old add. Should have revision == 0. |
| with open(added_path, 'w') as f: |
| f.write('Added file contents\n') |
| |
| svntest.actions.run_and_verify_svn(None, [], 'add', |
| alpha_path, beta_path, added_path) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'cp', |
| iota_path, iota2_path) |
| |
| entries = svntest.main.run_entriesdump(os.path.join(wc_dir, 'A', 'B', 'E')) |
| check_names(entries, 'alpha', 'beta', 'added', 'iota2') |
| |
| # plain add should be rev=0. over a DELETED, should be SCHEDULE_ADD |
| validate(entries['alpha'], schedule=SCHEDULE_ADD, revision=0, copied=False) |
| |
| # should pick up the BASE node's revision |
| validate(entries['beta'], schedule=SCHEDULE_REPLACE, revision=2, |
| copied=False) |
| |
| # plain add should be rev=0 |
| validate(entries['added'], schedule=SCHEDULE_ADD, revision=0, copied=False) |
| |
| # copyfrom_rev is (3), but we inherit the rev from the parent (1) |
| validate(entries['iota2'], schedule=SCHEDULE_ADD, revision=1, copied=True, |
| copyfrom_rev=3) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'cp', G_path, G2_path) |
| |
| entries = svntest.main.run_entriesdump(G2_path) |
| check_names(entries, 'pi', 'rho', 'tau') |
| |
| # added, but revision should match the copyfrom_rev (directories don't |
| # inherit a revision like iota2 did above) |
| validate(entries[''], schedule=SCHEDULE_ADD, copied=True, revision=3) |
| |
| # children should be SCHEDULE_NORMAL. still rev=1 cuz of mixed-rev source. |
| validate(entries['pi'], schedule=SCHEDULE_NORMAL, copied=True, revision=1) |
| |
| |
| def obstructed_entries(sbox): |
| "validate entries when obstructions exist" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| D_path = os.path.join(wc_dir, 'A', 'D') |
| H_path = os.path.join(wc_dir, 'A', 'D', 'H') |
| |
| # blast a directory. its revision should become SVN_INVALID_REVNUM. |
| entries = svntest.main.run_entriesdump(D_path) |
| check_names(entries, 'H') |
| validate(entries['H'], revision=1) |
| |
| svntest.main.safe_rmtree(H_path) |
| |
| entries = svntest.main.run_entriesdump(D_path) |
| check_names(entries, 'H') |
| |
| # Data is not missing in single-db |
| validate(entries['H'], revision=1) |
| |
| ### need to get svn_wc__db_read_info() to generate obstructed_add |
| |
| |
| def deletion_details(sbox): |
| "various details about deleted nodes" |
| |
| sbox.build() |
| wc_dir = sbox.wc_dir |
| |
| iota_path = os.path.join(wc_dir, 'iota') |
| D_path = os.path.join(wc_dir, 'A', 'D') |
| D2_path = os.path.join(wc_dir, 'A', 'D2') |
| D2_G_path = os.path.join(wc_dir, 'A', 'D2', 'G') |
| E_path = os.path.join(wc_dir, 'A', 'B', 'E') |
| H_path = os.path.join(wc_dir, 'A', 'D', 'H') |
| |
| entries = svntest.main.run_entriesdump(wc_dir) |
| check_names(entries, 'iota') |
| iota = entries['iota'] |
| |
| # blast iota, then verify the now-deleted entry still contains much of |
| # the same information. |
| svntest.actions.run_and_verify_svn(None, [], 'rm', iota_path) |
| entries = svntest.main.run_entriesdump(wc_dir) |
| check_names(entries, 'iota') |
| validate(entries['iota'], revision=iota.revision, |
| cmt_rev=iota.cmt_rev, cmt_author=iota.cmt_author) |
| |
| # even deleted nodes have a URL |
| validate(entries['iota'], url='%s/iota' % sbox.repo_url) |
| |
| svntest.actions.run_and_verify_svn(None, [], 'cp', D_path, D2_path) |
| svntest.actions.run_and_verify_svn(None, [], 'rm', D2_G_path) |
| |
| entries = svntest.main.run_entriesdump(D2_path) |
| check_names(entries, 'gamma', 'G') |
| |
| # copied nodes have URLs |
| validate(entries['gamma'], url='%s/A/D2/gamma' % sbox.repo_url, |
| copied=True, schedule=SCHEDULE_NORMAL) |
| |
| entries = svntest.main.run_entriesdump(D2_G_path) |
| check_names(entries, 'pi') |
| |
| # oh, and this sucker has a URL, too |
| validate(entries['pi'], url='%s/A/D2/G/pi' % sbox.repo_url, |
| copied=True, schedule=SCHEDULE_DELETE) |
| |
| ### hmm. somehow, subtrees can be *added* over a *deleted* subtree. |
| ### maybe this can happen via 'svn merge' ? ... the operations below |
| ### will fail because E_path is scheduled for deletion, disallowing |
| ### any new node to sit on top of it. (tho it *should* allow it...) |
| |
| ### for now... this test case is done. just return |
| return |
| |
| svntest.actions.run_and_verify_svn(None, [], 'rm', E_path) |
| svntest.actions.run_and_verify_svn(None, [], 'cp', H_path, E_path) |
| |
| entries = svntest.main.run_entriesdump(E_path) |
| check_names(entries, 'chi', 'omega', 'psi', 'alpha', 'beta') |
| |
| validate(entries['alpha'], schedule=SCHEDULE_DELETE) |
| validate(entries['chi'], schedule=SCHEDULE_NORMAL, copied=True) |
| |
| |
| |
| ######################################################################## |
| # Run the tests |
| |
| # list all tests here, starting with None: |
| test_list = [ None, |
| basic_entries, |
| obstructed_entries, |
| deletion_details, |
| ] |
| |
| |
| if __name__ == '__main__': |
| svntest.main.run_tests(test_list) |
| # NOTREACHED |