1#!/usr/bin/env python
2#
3#  wc_tests.py:  testing working-copy operations
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# General modules
28from __future__ import with_statement
29import shutil, stat, re, os, logging
30
31logger = logging.getLogger()
32
33# Our testing module
34import svntest
35from svntest import wc
36
37# (abbreviation)
38Skip = svntest.testcase.Skip_deco
39SkipUnless = svntest.testcase.SkipUnless_deco
40XFail = svntest.testcase.XFail_deco
41Issues = svntest.testcase.Issues_deco
42Issue = svntest.testcase.Issue_deco
43Wimp = svntest.testcase.Wimp_deco
44Item = wc.StateItem
45UnorderedOutput = svntest.verify.UnorderedOutput
46
47######################################################################
48# Tests
49#
50#   Each test must return on success or raise on failure.
51
52
53@XFail()
54@Issue(4193)
55@SkipUnless(svntest.main.is_posix_os)
56def status_through_unversioned_symlink(sbox):
57  """file status through unversioned symlink"""
58
59  sbox.build(read_only = True)
60  state = svntest.actions.get_virginal_state(sbox.wc_dir, 1)
61  os.symlink('A', sbox.ospath('Z'))
62  svntest.actions.run_and_verify_status(sbox.ospath('Z/mu'), state)
63
64@XFail()
65@Issue(4193)
66@SkipUnless(svntest.main.is_posix_os)
67def status_through_versioned_symlink(sbox):
68  """file status through versioned symlink"""
69
70  sbox.build(read_only = True)
71  state = svntest.actions.get_virginal_state(sbox.wc_dir, 1)
72  os.symlink('A', sbox.ospath('Z'))
73  sbox.simple_add('Z')
74  state.add({'Z': Item(status='A ')})
75  svntest.actions.run_and_verify_status(sbox.ospath('Z/mu'), state)
76
77@XFail()
78@Issue(4193)
79@SkipUnless(svntest.main.is_posix_os)
80def status_with_symlink_in_path(sbox):
81  """file status with not-parent symlink"""
82
83  sbox.build(read_only = True)
84  state = svntest.actions.get_virginal_state(sbox.wc_dir, 1)
85  os.symlink('A', sbox.ospath('Z'))
86  svntest.actions.run_and_verify_status(sbox.ospath('Z/B/lambda'), state)
87
88@XFail()
89@Issue(4193)
90@SkipUnless(svntest.main.is_posix_os)
91def add_through_unversioned_symlink(sbox):
92  """add file through unversioned symlink"""
93
94  sbox.build(read_only = True)
95  os.symlink('A', sbox.ospath('Z'))
96  sbox.simple_append('A/kappa', 'xyz', True)
97  sbox.simple_add('Z/kappa')
98
99@XFail()
100@Issue(4193)
101@SkipUnless(svntest.main.is_posix_os)
102def add_through_versioned_symlink(sbox):
103  """add file through versioned symlink"""
104
105  sbox.build(read_only = True)
106  os.symlink('A', sbox.ospath('Z'))
107  sbox.simple_add('Z')
108  sbox.simple_append('A/kappa', 'xyz', True)
109  sbox.simple_add('Z/kappa')
110
111@XFail()
112@Issue(4193)
113@SkipUnless(svntest.main.is_posix_os)
114def add_with_symlink_in_path(sbox):
115  """add file with not-parent symlink"""
116
117  sbox.build(read_only = True)
118  os.symlink('A', sbox.ospath('Z'))
119  sbox.simple_append('A/B/kappa', 'xyz', True)
120  sbox.simple_add('Z/B/kappa')
121
122def is_posix_os_and_not_root():
123  if not svntest.main.is_posix_os():
124    return False
125  return os.getuid() != 0
126
127@Issue(4118)
128@SkipUnless(is_posix_os_and_not_root)
129def status_with_inaccessible_wc_db(sbox):
130  """inaccessible .svn/wc.db"""
131
132  sbox.build(read_only = True)
133  os.chmod(sbox.ospath(".svn/wc.db"), 0)
134  svntest.actions.run_and_verify_svn(
135    None,
136    r"[^ ]+ E155016: The working copy database at '.*' is corrupt",
137    "st", sbox.wc_dir)
138
139@Issue(4118)
140def status_with_corrupt_wc_db(sbox):
141  """corrupt .svn/wc.db"""
142
143  sbox.build(read_only = True)
144  with open(sbox.ospath(".svn/wc.db"), 'wb') as fd:
145    fd.write(b'\0' * 17)
146  svntest.actions.run_and_verify_svn(
147    None,
148    r"[^ ]+ E155016: The working copy database at '.*' is corrupt",
149    "st", sbox.wc_dir)
150
151@Issue(4118)
152def status_with_zero_length_wc_db(sbox):
153  """zero-length .svn/wc.db"""
154
155  sbox.build(read_only = True)
156  os.close(os.open(sbox.ospath(".svn/wc.db"), os.O_RDWR | os.O_TRUNC))
157  svntest.actions.run_and_verify_svn(
158    None,
159    r"[^ ]+ E200030:",                    # SVN_ERR_SQLITE_ERROR
160    "st", sbox.wc_dir)
161
162@Issue(4118)
163def status_without_wc_db(sbox):
164  """missing .svn/wc.db"""
165
166  sbox.build(read_only = True)
167  os.remove(sbox.ospath(".svn/wc.db"))
168  svntest.actions.run_and_verify_svn(
169    None,
170    r"[^ ]+ E155016: The working copy database at '.*' is missing",
171    "st", sbox.wc_dir)
172
173@Issue(4118)
174@Skip()      # FIXME: Test fails in-tree because it finds the source WC root
175def status_without_wc_db_and_entries(sbox):
176  """missing .svn/wc.db and .svn/entries"""
177
178  sbox.build(read_only = True)
179  os.remove(sbox.ospath(".svn/wc.db"))
180  os.remove(sbox.ospath(".svn/entries"))
181  svntest.actions.run_and_verify_svn2(
182    None,
183    r"[^ ]+ warning: W155007: '.*' is not a working copy",
184    0, "st", sbox.wc_dir)
185
186@Issue(4118)
187def status_with_missing_wc_db_and_maybe_valid_entries(sbox):
188  """missing .svn/wc.db, maybe valid .svn/entries"""
189
190  sbox.build(read_only = True)
191  with open(sbox.ospath(".svn/entries"), 'ab') as fd:
192    fd.write(b'something\n')
193    os.remove(sbox.ospath(".svn/wc.db"))
194  svntest.actions.run_and_verify_svn(
195    None,
196    r"[^ ]+ E155036:",                    # SVN_ERR_WC_UPGRADE_REQUIRED
197    "st", sbox.wc_dir)
198
199
200@Issue(4267)
201def cleanup_below_wc_root(sbox):
202  """cleanup from directory below WC root"""
203
204  sbox.build(read_only = True)
205  svntest.actions.lock_admin_dir(sbox.ospath(""), True)
206  svntest.actions.run_and_verify_svn(None, [],
207                                     "cleanup", sbox.ospath("A"))
208
209@SkipUnless(svntest.main.is_posix_os)
210@Issue(4383)
211def update_through_unversioned_symlink(sbox):
212  """update through unversioned symlink"""
213
214  sbox.build(read_only = True)
215  wc_dir = sbox.wc_dir
216  state = svntest.actions.get_virginal_state(wc_dir, 1)
217  symlink = sbox.get_tempname()
218  os.symlink(os.path.abspath(sbox.wc_dir), symlink)
219  expected_output = []
220  expected_disk = []
221  expected_status = []
222  # Subversion 1.8.0 crashes when updating a working copy through a symlink
223  svntest.actions.run_and_verify_update(wc_dir, expected_output,
224                                        expected_disk, expected_status,
225                                        [], True, symlink)
226
227@Issue(3549)
228def cleanup_unversioned_items(sbox):
229  """cleanup --remove-unversioned / --remove-ignored"""
230
231  sbox.build(read_only = True)
232  wc_dir = sbox.wc_dir
233
234  # create some unversioned items
235  os.mkdir(sbox.ospath('dir1'))
236  os.mkdir(sbox.ospath('dir2'))
237  contents = "This is an unversioned file\n."
238  svntest.main.file_write(sbox.ospath('dir1/dir1_child1'), contents)
239  svntest.main.file_write(sbox.ospath('dir2/dir2_child1'), contents)
240  os.mkdir(sbox.ospath('dir2/foo_child2'))
241  svntest.main.file_write(sbox.ospath('file_foo'), contents),
242  os.mkdir(sbox.ospath('dir_foo'))
243  svntest.main.file_write(sbox.ospath('dir_foo/foo_child1'), contents)
244  os.mkdir(sbox.ospath('dir_foo/foo_child2'))
245  # a file that matches a default ignore pattern
246  svntest.main.file_write(sbox.ospath('foo.o'), contents)
247
248  # ignore some of the unversioned items
249  sbox.simple_propset('svn:ignore', '*_foo', '.')
250
251  os.chdir(wc_dir)
252
253  expected_output = [
254        ' M      .\n',
255        '?       dir1\n',
256        '?       dir2\n',
257  ]
258  svntest.actions.run_and_verify_svn(UnorderedOutput(expected_output),
259                                     [], 'status')
260  expected_output += [
261        'I       dir_foo\n',
262        'I       file_foo\n',
263        'I       foo.o\n',
264  ]
265  svntest.actions.run_and_verify_svn(UnorderedOutput(expected_output),
266                                     [], 'status', '--no-ignore')
267
268  expected_output = [
269        'D         dir1\n',
270        'D         dir2\n',
271  ]
272  svntest.actions.run_and_verify_svn(UnorderedOutput(expected_output),
273                                     [], 'cleanup', '--remove-unversioned')
274  expected_output = [
275        ' M      .\n',
276        'I       dir_foo\n',
277        'I       file_foo\n',
278        'I       foo.o\n',
279  ]
280  svntest.actions.run_and_verify_svn(UnorderedOutput(expected_output),
281                                     [], 'status', '--no-ignore')
282
283  # remove ignored items, with an empty global-ignores list
284  expected_output = [
285        'D         dir_foo\n',
286        'D         file_foo\n',
287  ]
288  svntest.actions.run_and_verify_svn(UnorderedOutput(expected_output),
289                                     [], 'cleanup', '--remove-ignored',
290                                     '--config-option',
291                                     'config:miscellany:global-ignores=')
292
293  # the file matching global-ignores should still be present
294  expected_output = [
295        ' M      .\n',
296        'I       foo.o\n',
297  ]
298  svntest.actions.run_and_verify_svn(UnorderedOutput(expected_output),
299                                     [], 'status', '--no-ignore')
300
301  # un-ignore the file matching global ignores, making it unversioned,
302  # and remove it with --remove-unversioned
303  expected_output = [
304        'D         foo.o\n',
305  ]
306  svntest.actions.run_and_verify_svn(UnorderedOutput(expected_output),
307                                     [], 'cleanup', '--remove-unversioned',
308                                     '--config-option',
309                                     'config:miscellany:global-ignores=')
310  expected_output = [
311        ' M      .\n',
312  ]
313  svntest.actions.run_and_verify_svn(expected_output,
314                                     [], 'status', '--no-ignore')
315
316def cleanup_unversioned_items_in_locked_wc(sbox):
317  """cleanup unversioned items in locked WC should fail"""
318
319  sbox.build(read_only = True)
320
321  contents = "This is an unversioned file\n."
322  svntest.main.file_write(sbox.ospath('unversioned_file'), contents)
323
324  svntest.actions.lock_admin_dir(sbox.ospath(""), True)
325  for option in ['--remove-unversioned', '--remove-ignored']:
326    svntest.actions.run_and_verify_svn(None,
327                                       "svn: E155004: Working copy locked;.*",
328                                       "cleanup", option,
329                                       sbox.ospath(""))
330
331def cleanup_dir_external(sbox):
332  """cleanup --include-externals"""
333
334  sbox.build(read_only = True)
335
336  # configure a directory external
337  sbox.simple_propset("svn:externals", "^/A A_ext", ".")
338  sbox.simple_update()
339
340  svntest.actions.lock_admin_dir(sbox.ospath("A_ext"), True)
341  svntest.actions.run_and_verify_svn(["Performing cleanup on external " +
342                                     "item at '%s'.\n" % sbox.ospath("A_ext")],
343                                     [], "cleanup", '--include-externals',
344                                     sbox.ospath(""))
345
346@Issue(4390)
347def checkout_within_locked_wc(sbox):
348  """checkout within a locked working copy"""
349
350  sbox.build(read_only = True)
351
352  # lock working copy and create outstanding work queue items
353  svntest.actions.lock_admin_dir(sbox.ospath(""), True, True)
354  expected_output = [
355  "A    %s\n" % sbox.ospath("nested-wc/alpha"),
356  "A    %s\n" % sbox.ospath("nested-wc/beta"),
357  "Checked out revision 1.\n"
358  ]
359  svntest.actions.run_and_verify_svn(UnorderedOutput(expected_output),
360                                     [], "checkout", sbox.repo_url + '/A/B/E',
361                                     sbox.ospath("nested-wc"))
362
363
364########################################################################
365# Run the tests
366
367# list all tests here, starting with None:
368test_list = [ None,
369              status_through_unversioned_symlink,
370              status_through_versioned_symlink,
371              status_with_symlink_in_path,
372              add_through_unversioned_symlink,
373              add_through_versioned_symlink,
374              add_with_symlink_in_path,
375              status_with_inaccessible_wc_db,
376              status_with_corrupt_wc_db,
377              status_with_zero_length_wc_db,
378              status_without_wc_db,
379              status_without_wc_db_and_entries,
380              status_with_missing_wc_db_and_maybe_valid_entries,
381              cleanup_below_wc_root,
382              update_through_unversioned_symlink,
383              cleanup_unversioned_items,
384              cleanup_unversioned_items_in_locked_wc,
385              cleanup_dir_external,
386              checkout_within_locked_wc,
387             ]
388
389if __name__ == '__main__':
390  svntest.main.run_tests(test_list)
391  # NOTREACHED
392
393
394### End of file.
395