1#!/usr/bin/env python 2# 3# entries_tests.py: test the old entries API using entries-dump 4# 5# Subversion is a tool for revision control. 6# See http://subversion.apache.org for more information. 7# 8# ==================================================================== 9# Licensed to the Apache Software Foundation (ASF) under one 10# or more contributor license agreements. See the NOTICE file 11# distributed with this work for additional information 12# regarding copyright ownership. The ASF licenses this file 13# to you under the Apache License, Version 2.0 (the 14# "License"); you may not use this file except in compliance 15# with the License. You may obtain a copy of the License at 16# 17# http://www.apache.org/licenses/LICENSE-2.0 18# 19# Unless required by applicable law or agreed to in writing, 20# software distributed under the License is distributed on an 21# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 22# KIND, either express or implied. See the License for the 23# specific language governing permissions and limitations 24# under the License. 25###################################################################### 26 27# 28# This test series is to validate the old entries API using the entries-dump 29# tool to see what the API reports. In particular, this test is designed to 30# try and exercise all "extraordinary" code paths in the read_entries() 31# function in libsvn_wc/entries.c. Much of that function is exercised by 32# the regular test suite and its secondary "status" via entries-dump. This 33# test tries to pick up the straggly little edge cases. 34# 35 36import os, logging 37 38logger = logging.getLogger() 39 40import svntest 41 42Item = svntest.wc.StateItem 43 44 45SCHEDULE_NORMAL = 0 46SCHEDULE_ADD = 1 47SCHEDULE_DELETE = 2 48SCHEDULE_REPLACE = 3 49 50 51def validate(entry, **kw): 52 for key, value in kw.items(): 53 if getattr(entry, key) != value: 54 logger.warn("Entry '%s' has an incorrect value for .%s", entry.name, key) 55 logger.warn(" Expected: %s", value) 56 logger.warn(" Actual: %s", getattr(entry, key)) 57 raise svntest.Failure 58 59 60def check_names(entries, *names): 61 if entries is None: 62 logger.warn('entries-dump probably exited with a failure.') 63 raise svntest.Failure 64 have = set(entries.keys()) 65 want = set(names) 66 missing = want - have 67 if missing: 68 logger.warn("Entry name(s) not found: %s", 69 ', '.join("'%s'" % name for name in missing)) 70 raise svntest.Failure 71 72 73def basic_entries(sbox): 74 "basic entries behavior" 75 76 sbox.build() 77 wc_dir = sbox.wc_dir 78 79 alpha_path = os.path.join(wc_dir, 'A', 'B', 'E', 'alpha') 80 beta_path = os.path.join(wc_dir, 'A', 'B', 'E', 'beta') 81 added_path = os.path.join(wc_dir, 'A', 'B', 'E', 'added') 82 G_path = os.path.join(wc_dir, 'A', 'D', 'G') 83 G2_path = os.path.join(wc_dir, 'A', 'D', 'G2') 84 iota_path = os.path.join(wc_dir, 'iota') 85 iota2_path = os.path.join(wc_dir, 'A', 'B', 'E', 'iota2') 86 87 # Remove 'alpha'. When it is committed, it will be marked DELETED. 88 svntest.actions.run_and_verify_svn(None, [], 'rm', alpha_path) 89 90 # Tweak 'beta' in order to bump its revision to ensure the replacement 91 # gets the new revision (2), not the value from the parent (1). 92 svntest.actions.run_and_verify_svn(None, [], 93 'ps', 'random-prop', 'propvalue', 94 beta_path) 95 96 expected_output = svntest.wc.State(wc_dir, { 97 'A/B/E/alpha' : Item(verb='Deleting'), 98 'A/B/E/beta' : Item(verb='Sending'), 99 }) 100 expected_status = svntest.actions.get_virginal_state(wc_dir, 1) 101 expected_status.remove('A/B/E/alpha') 102 expected_status.tweak('A/B/E/beta', wc_rev=2) 103 svntest.actions.run_and_verify_commit(wc_dir, 104 expected_output, expected_status, 105 [], 106 alpha_path, beta_path) 107 108 # bump 'G' and iota another revision (3) for later testing 109 svntest.actions.run_and_verify_svn(None, [], 110 'ps', 'random-prop', 'propvalue', 111 G_path, iota_path) 112 113 expected_output = svntest.wc.State(wc_dir, { 114 'A/D/G' : Item(verb='Sending'), 115 'iota' : Item(verb='Sending'), 116 }) 117 expected_status.tweak('A/D/G', 'iota', wc_rev=3) 118 svntest.actions.run_and_verify_commit(wc_dir, 119 expected_output, expected_status, 120 [], 121 G_path, iota_path) 122 123 # Add a file over the DELETED 'alpha'. It should be schedule-add. 124 with open(alpha_path, 'w') as f: 125 f.write('New alpha contents\n') 126 127 # Delete 'beta', then add a file over it. Should be schedule-replace. 128 svntest.actions.run_and_verify_svn(None, [], 'rm', beta_path) 129 with open(beta_path, 'w') as f: 130 f.write('New beta contents\n') 131 132 # Plain old add. Should have revision == 0. 133 with open(added_path, 'w') as f: 134 f.write('Added file contents\n') 135 136 svntest.actions.run_and_verify_svn(None, [], 'add', 137 alpha_path, beta_path, added_path) 138 139 svntest.actions.run_and_verify_svn(None, [], 'cp', 140 iota_path, iota2_path) 141 142 entries = svntest.main.run_entriesdump(os.path.join(wc_dir, 'A', 'B', 'E')) 143 check_names(entries, 'alpha', 'beta', 'added', 'iota2') 144 145 # plain add should be rev=0. over a DELETED, should be SCHEDULE_ADD 146 validate(entries['alpha'], schedule=SCHEDULE_ADD, revision=0, copied=False) 147 148 # should pick up the BASE node's revision 149 validate(entries['beta'], schedule=SCHEDULE_REPLACE, revision=2, 150 copied=False) 151 152 # plain add should be rev=0 153 validate(entries['added'], schedule=SCHEDULE_ADD, revision=0, copied=False) 154 155 # copyfrom_rev is (3), but we inherit the rev from the parent (1) 156 validate(entries['iota2'], schedule=SCHEDULE_ADD, revision=1, copied=True, 157 copyfrom_rev=3) 158 159 svntest.actions.run_and_verify_svn(None, [], 'cp', G_path, G2_path) 160 161 entries = svntest.main.run_entriesdump(G2_path) 162 check_names(entries, 'pi', 'rho', 'tau') 163 164 # added, but revision should match the copyfrom_rev (directories don't 165 # inherit a revision like iota2 did above) 166 validate(entries[''], schedule=SCHEDULE_ADD, copied=True, revision=3) 167 168 # children should be SCHEDULE_NORMAL. still rev=1 cuz of mixed-rev source. 169 validate(entries['pi'], schedule=SCHEDULE_NORMAL, copied=True, revision=1) 170 171 172def obstructed_entries(sbox): 173 "validate entries when obstructions exist" 174 175 sbox.build() 176 wc_dir = sbox.wc_dir 177 178 D_path = os.path.join(wc_dir, 'A', 'D') 179 H_path = os.path.join(wc_dir, 'A', 'D', 'H') 180 181 # blast a directory. its revision should become SVN_INVALID_REVNUM. 182 entries = svntest.main.run_entriesdump(D_path) 183 check_names(entries, 'H') 184 validate(entries['H'], revision=1) 185 186 svntest.main.safe_rmtree(H_path) 187 188 entries = svntest.main.run_entriesdump(D_path) 189 check_names(entries, 'H') 190 191 # Data is not missing in single-db 192 validate(entries['H'], revision=1) 193 194 ### need to get svn_wc__db_read_info() to generate obstructed_add 195 196 197def deletion_details(sbox): 198 "various details about deleted nodes" 199 200 sbox.build() 201 wc_dir = sbox.wc_dir 202 203 iota_path = os.path.join(wc_dir, 'iota') 204 D_path = os.path.join(wc_dir, 'A', 'D') 205 D2_path = os.path.join(wc_dir, 'A', 'D2') 206 D2_G_path = os.path.join(wc_dir, 'A', 'D2', 'G') 207 E_path = os.path.join(wc_dir, 'A', 'B', 'E') 208 H_path = os.path.join(wc_dir, 'A', 'D', 'H') 209 210 entries = svntest.main.run_entriesdump(wc_dir) 211 check_names(entries, 'iota') 212 iota = entries['iota'] 213 214 # blast iota, then verify the now-deleted entry still contains much of 215 # the same information. 216 svntest.actions.run_and_verify_svn(None, [], 'rm', iota_path) 217 entries = svntest.main.run_entriesdump(wc_dir) 218 check_names(entries, 'iota') 219 validate(entries['iota'], revision=iota.revision, 220 cmt_rev=iota.cmt_rev, cmt_author=iota.cmt_author) 221 222 # even deleted nodes have a URL 223 validate(entries['iota'], url='%s/iota' % sbox.repo_url) 224 225 svntest.actions.run_and_verify_svn(None, [], 'cp', D_path, D2_path) 226 svntest.actions.run_and_verify_svn(None, [], 'rm', D2_G_path) 227 228 entries = svntest.main.run_entriesdump(D2_path) 229 check_names(entries, 'gamma', 'G') 230 231 # copied nodes have URLs 232 validate(entries['gamma'], url='%s/A/D2/gamma' % sbox.repo_url, 233 copied=True, schedule=SCHEDULE_NORMAL) 234 235 entries = svntest.main.run_entriesdump(D2_G_path) 236 check_names(entries, 'pi') 237 238 # oh, and this sucker has a URL, too 239 validate(entries['pi'], url='%s/A/D2/G/pi' % sbox.repo_url, 240 copied=True, schedule=SCHEDULE_DELETE) 241 242 ### hmm. somehow, subtrees can be *added* over a *deleted* subtree. 243 ### maybe this can happen via 'svn merge' ? ... the operations below 244 ### will fail because E_path is scheduled for deletion, disallowing 245 ### any new node to sit on top of it. (tho it *should* allow it...) 246 247 ### for now... this test case is done. just return 248 return 249 250 svntest.actions.run_and_verify_svn(None, [], 'rm', E_path) 251 svntest.actions.run_and_verify_svn(None, [], 'cp', H_path, E_path) 252 253 entries = svntest.main.run_entriesdump(E_path) 254 check_names(entries, 'chi', 'omega', 'psi', 'alpha', 'beta') 255 256 validate(entries['alpha'], schedule=SCHEDULE_DELETE) 257 validate(entries['chi'], schedule=SCHEDULE_NORMAL, copied=True) 258 259 260 261######################################################################## 262# Run the tests 263 264# list all tests here, starting with None: 265test_list = [ None, 266 basic_entries, 267 obstructed_entries, 268 deletion_details, 269 ] 270 271 272if __name__ == '__main__': 273 svntest.main.run_tests(test_list) 274 # NOTREACHED 275