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