3 # schedule_tests.py: testing working copy scheduling
4 # (adds, deletes, reversion)
6 # Subversion is a tool for revision control.
7 # See http://subversion.tigris.org for more information.
9 # ====================================================================
10 # Copyright (c) 2000-2004 CollabNet. All rights reserved.
12 # This software is licensed as described in the file COPYING, which
13 # you should have received as part of this distribution. The terms
14 # are also available at http://subversion.tigris.org/license-1.html.
15 # If newer versions of this license are posted there, you may use a
16 # newer version instead, at your option.
18 ######################################################################
27 Skip
= svntest
.testcase
.Skip
28 SkipUnless
= svntest
.testcase
.SkipUnless
29 XFail
= svntest
.testcase
.XFail
30 Item
= svntest
.wc
.StateItem
33 ######################################################################
36 # Each test must return on success or raise on failure.
39 #######################################################################
40 # Stage I - Schedules and modifications, verified with `svn status'
42 # These tests make schedule changes and local mods, and verify that status
43 # output is as expected. In a second stage, reversion of these changes is
44 # tested. Potentially, a third stage could test committing these same
47 # NOTE: these tests are run within the Stage II tests, not on their own.
51 "schedule: add some files"
53 sbox
.build(read_only
= True)
56 # Create some files, then schedule them for addition
57 delta_path
= os
.path
.join(wc_dir
, 'delta')
58 zeta_path
= os
.path
.join(wc_dir
, 'A', 'B', 'zeta')
59 epsilon_path
= os
.path
.join(wc_dir
, 'A', 'D', 'G', 'epsilon')
61 svntest
.main
.file_append(delta_path
, "This is the file 'delta'.")
62 svntest
.main
.file_append(zeta_path
, "This is the file 'zeta'.")
63 svntest
.main
.file_append(epsilon_path
, "This is the file 'epsilon'.")
65 svntest
.main
.run_svn(None, 'add', delta_path
, zeta_path
, epsilon_path
)
67 # Make sure the adds show up as such in status
68 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 1)
70 'delta' : Item(status
='A ', wc_rev
=0),
71 'A/B/zeta' : Item(status
='A ', wc_rev
=0),
72 'A/D/G/epsilon' : Item(status
='A ', wc_rev
=0),
75 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
77 #----------------------------------------------------------------------
79 def add_directories(sbox
):
80 "schedule: add some directories"
82 sbox
.build(read_only
= True)
85 # Create some directories, then schedule them for addition
86 X_path
= os
.path
.join(wc_dir
, 'X')
87 Y_path
= os
.path
.join(wc_dir
, 'A', 'C', 'Y')
88 Z_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H', 'Z')
94 svntest
.main
.run_svn(None, 'add', X_path
, Y_path
, Z_path
)
96 # Make sure the adds show up as such in status
97 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 1)
99 'X' : Item(status
='A ', wc_rev
=0),
100 'A/C/Y' : Item(status
='A ', wc_rev
=0),
101 'A/D/H/Z' : Item(status
='A ', wc_rev
=0),
104 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
106 #----------------------------------------------------------------------
108 def nested_adds(sbox
):
109 "schedule: add some nested files and directories"
111 sbox
.build(read_only
= True)
114 # Create some directories then schedule them for addition
115 X_path
= os
.path
.join(wc_dir
, 'X')
116 Y_path
= os
.path
.join(wc_dir
, 'A', 'C', 'Y')
117 Z_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H', 'Z')
123 # Now, create some files and directories to put into our newly added
125 P_path
= os
.path
.join(X_path
, 'P')
126 Q_path
= os
.path
.join(Y_path
, 'Q')
127 R_path
= os
.path
.join(Z_path
, 'R')
133 delta_path
= os
.path
.join(X_path
, 'delta')
134 epsilon_path
= os
.path
.join(Y_path
, 'epsilon')
135 upsilon_path
= os
.path
.join(Y_path
, 'upsilon')
136 zeta_path
= os
.path
.join(Z_path
, 'zeta')
138 svntest
.main
.file_append(delta_path
, "This is the file 'delta'.")
139 svntest
.main
.file_append(epsilon_path
, "This is the file 'epsilon'.")
140 svntest
.main
.file_append(upsilon_path
, "This is the file 'upsilon'.")
141 svntest
.main
.file_append(zeta_path
, "This is the file 'zeta'.")
143 # Finally, let's try adding our new files and directories
144 svntest
.main
.run_svn(None, 'add', X_path
, Y_path
, Z_path
)
146 # Make sure the adds show up as such in status
147 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 1)
148 expected_status
.add({
149 'X' : Item(status
='A ', wc_rev
=0),
150 'A/C/Y' : Item(status
='A ', wc_rev
=0),
151 'A/D/H/Z' : Item(status
='A ', wc_rev
=0),
152 'X/P' : Item(status
='A ', wc_rev
=0),
153 'A/C/Y/Q' : Item(status
='A ', wc_rev
=0),
154 'A/D/H/Z/R' : Item(status
='A ', wc_rev
=0),
155 'X/delta' : Item(status
='A ', wc_rev
=0),
156 'A/C/Y/epsilon' : Item(status
='A ', wc_rev
=0),
157 'A/C/Y/upsilon' : Item(status
='A ', wc_rev
=0),
158 'A/D/H/Z/zeta' : Item(status
='A ', wc_rev
=0),
161 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
163 #----------------------------------------------------------------------
165 def add_executable(sbox
):
166 "schedule: add some executable files"
168 sbox
.build(read_only
= True)
170 def runTest(wc_dir
, fileName
, perm
, executable
):
171 fileName
= os
.path
.join(wc_dir
, fileName
)
173 expected_out
= ["*\n"]
176 f
= open(fileName
,"w")
178 os
.chmod(fileName
,perm
)
179 svntest
.main
.run_svn(None, 'add', fileName
)
180 svntest
.actions
.run_and_verify_svn(None, expected_out
, [],
181 'propget', "svn:executable", fileName
)
184 ("all_exe", 0777, 1),
185 ("none_exe", 0666, 0),
186 ("user_exe", 0766, 1),
187 ("group_exe", 0676, 0),
188 ("other_exe", 0667, 0),
190 for test_case
in test_cases
:
191 runTest(sbox
.wc_dir
, *test_case
)
193 #----------------------------------------------------------------------
195 def delete_files(sbox
):
196 "schedule: delete some files"
198 sbox
.build(read_only
= True)
201 # Schedule some files for deletion
202 iota_path
= os
.path
.join(wc_dir
, 'iota')
203 mu_path
= os
.path
.join(wc_dir
, 'A', 'mu')
204 rho_path
= os
.path
.join(wc_dir
, 'A', 'D', 'G', 'rho')
205 omega_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H', 'omega')
207 svntest
.main
.run_svn(None, 'del', iota_path
, mu_path
, rho_path
, omega_path
)
209 # Make sure the deletes show up as such in status
210 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 1)
211 expected_status
.tweak('iota', 'A/mu', 'A/D/G/rho', 'A/D/H/omega',
214 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
216 #----------------------------------------------------------------------
218 def delete_dirs(sbox
):
219 "schedule: delete some directories"
221 sbox
.build(read_only
= True)
224 # Schedule some directories for deletion (this is recursive!)
225 E_path
= os
.path
.join(wc_dir
, 'A', 'B', 'E')
226 F_path
= os
.path
.join(wc_dir
, 'A', 'B', 'F')
227 H_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H')
228 alpha_path
= os
.path
.join(E_path
, 'alpha')
229 beta_path
= os
.path
.join(E_path
, 'beta')
230 chi_path
= os
.path
.join(H_path
, 'chi')
231 omega_path
= os
.path
.join(H_path
, 'omega')
232 psi_path
= os
.path
.join(H_path
, 'psi')
234 # Now, delete (recursively) the directories.
235 svntest
.main
.run_svn(None, 'del', E_path
, F_path
, H_path
)
237 # Make sure the deletes show up as such in status
238 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 1)
239 expected_status
.tweak('A/B/E', 'A/B/E/alpha', 'A/B/E/beta',
241 'A/D/H', 'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi',
244 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
247 #######################################################################
248 # Stage II - Reversion of changes made in Stage I
250 # Each test in Stage II calls the corresponding Stage I test
251 # and then also tests reversion of those changes.
254 def check_reversion(files
, output
):
257 expected_output
= expected_output
+ ["Reverted '" + file + "'\n"]
259 expected_output
.sort()
260 if output
!= expected_output
:
261 print "Expected output:", expected_output
262 print "Actual output: ", output
263 raise svntest
.Failure
265 #----------------------------------------------------------------------
267 def revert_add_files(sbox
):
268 "revert: add some files"
273 # Revert our changes recursively from wc_dir.
274 delta_path
= os
.path
.join(wc_dir
, 'delta')
275 zeta_path
= os
.path
.join(wc_dir
, 'A', 'B', 'zeta')
276 epsilon_path
= os
.path
.join(wc_dir
, 'A', 'D', 'G', 'epsilon')
277 files
= [delta_path
, zeta_path
, epsilon_path
]
279 exit_code
, output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
283 check_reversion(files
, output
)
285 #----------------------------------------------------------------------
287 def revert_add_directories(sbox
):
288 "revert: add some directories"
290 add_directories(sbox
)
293 # Revert our changes recursively from wc_dir.
294 X_path
= os
.path
.join(wc_dir
, 'X')
295 Y_path
= os
.path
.join(wc_dir
, 'A', 'C', 'Y')
296 Z_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H', 'Z')
297 files
= [X_path
, Y_path
, Z_path
]
299 exit_code
, output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
303 check_reversion(files
, output
)
305 #----------------------------------------------------------------------
307 def revert_nested_adds(sbox
):
308 "revert: add some nested files and directories"
313 # Revert our changes recursively from wc_dir.
314 X_path
= os
.path
.join(wc_dir
, 'X')
315 Y_path
= os
.path
.join(wc_dir
, 'A', 'C', 'Y')
316 Z_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H', 'Z')
317 files
= [X_path
, Y_path
, Z_path
]
319 exit_code
, output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
323 check_reversion(files
, output
)
325 #----------------------------------------------------------------------
327 def revert_add_executable(sbox
):
328 "revert: add some executable files"
333 all_path
= os
.path
.join(wc_dir
, 'all_exe')
334 none_path
= os
.path
.join(wc_dir
, 'none_exe')
335 user_path
= os
.path
.join(wc_dir
, 'user_exe')
336 group_path
= os
.path
.join(wc_dir
, 'group_exe')
337 other_path
= os
.path
.join(wc_dir
, 'other_exe')
338 files
= [all_path
, none_path
, user_path
, group_path
, other_path
]
340 exit_code
, output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
344 check_reversion(files
, output
)
346 #----------------------------------------------------------------------
348 def revert_delete_files(sbox
):
349 "revert: delete some files"
354 # Revert our changes recursively from wc_dir.
355 iota_path
= os
.path
.join(wc_dir
, 'iota')
356 mu_path
= os
.path
.join(wc_dir
, 'A', 'mu')
357 rho_path
= os
.path
.join(wc_dir
, 'A', 'D', 'G', 'rho')
358 omega_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H', 'omega')
359 files
= [iota_path
, mu_path
, omega_path
, rho_path
]
361 exit_code
, output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
365 check_reversion(files
, output
)
367 #----------------------------------------------------------------------
369 def revert_delete_dirs(sbox
):
370 "revert: delete some directories"
375 # Revert our changes recursively from wc_dir.
376 E_path
= os
.path
.join(wc_dir
, 'A', 'B', 'E')
377 F_path
= os
.path
.join(wc_dir
, 'A', 'B', 'F')
378 H_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H')
379 alpha_path
= os
.path
.join(E_path
, 'alpha')
380 beta_path
= os
.path
.join(E_path
, 'beta')
381 chi_path
= os
.path
.join(H_path
, 'chi')
382 omega_path
= os
.path
.join(H_path
, 'omega')
383 psi_path
= os
.path
.join(H_path
, 'psi')
384 files
= [E_path
, F_path
, H_path
,
385 alpha_path
, beta_path
, chi_path
, omega_path
, psi_path
]
387 exit_code
, output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
391 check_reversion(files
, output
)
394 #######################################################################
398 #----------------------------------------------------------------------
399 # Regression test for issue #863:
401 # Suppose here is a scheduled-add file or directory which is
402 # also missing. If I want to make the working copy forget all
403 # knowledge of the item ("unschedule" the addition), then either 'svn
404 # revert' or 'svn rm' will make that happen by removing the entry from
405 # .svn/entries file. While 'svn revert' does with no error,
406 # 'svn rm' does it with error.
408 def unschedule_missing_added(sbox
):
409 "unschedule addition on missing items"
411 sbox
.build(read_only
= True)
414 # Create some files and dirs, then schedule them for addition
415 file1_path
= os
.path
.join(wc_dir
, 'file1')
416 file2_path
= os
.path
.join(wc_dir
, 'file2')
417 dir1_path
= os
.path
.join(wc_dir
, 'dir1')
418 dir2_path
= os
.path
.join(wc_dir
, 'dir2')
420 svntest
.main
.file_append(file1_path
, "This is the file 'file1'.")
421 svntest
.main
.file_append(file2_path
, "This is the file 'file2'.")
422 svntest
.main
.run_svn(None, 'add', file1_path
, file2_path
)
423 svntest
.main
.run_svn(None, 'mkdir', dir1_path
, dir2_path
)
425 # Make sure the 4 adds show up as such in status
426 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 1)
427 expected_status
.add({
428 'file1' : Item(status
='A ', wc_rev
=0),
429 'file2' : Item(status
='A ', wc_rev
=0),
430 'dir1' : Item(status
='A ', wc_rev
=0),
431 'dir2' : Item(status
='A ', wc_rev
=0),
434 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
436 # Poof, all 4 added things are now missing in action.
437 os
.remove(file1_path
)
438 os
.remove(file2_path
)
439 svntest
.main
.safe_rmtree(dir1_path
)
440 svntest
.main
.safe_rmtree(dir2_path
)
442 # Unschedule the additions, using 'svn rm' and 'svn revert'.
443 svntest
.main
.run_svn(svntest
.verify
.AnyOutput
, 'rm', file1_path
)
444 svntest
.main
.run_svn(svntest
.verify
.AnyOutput
, 'rm', dir1_path
)
445 svntest
.main
.run_svn(None, 'revert', file2_path
, dir2_path
)
447 # 'svn st' should now show absolutely zero local mods.
448 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 1)
449 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
451 #----------------------------------------------------------------------
452 # Regression test for issue #962:
454 # Make sure 'rm foo; svn rm foo' works on files and directories.
455 # Also make sure that the deletion is committable.
457 def delete_missing(sbox
):
458 "schedule and commit deletion on missing items"
463 mu_path
= os
.path
.join(wc_dir
, 'A', 'mu')
464 H_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H')
466 # Manually remove a file and a directory.
468 svntest
.main
.safe_rmtree(H_path
)
470 # Now schedule them for deletion anyway, and make sure no error is output.
471 svntest
.actions
.run_and_verify_svn(None, None, [], 'rm', mu_path
, H_path
)
473 # Commit the deletions.
474 expected_output
= svntest
.wc
.State(wc_dir
, {
475 'A/mu' : Item(verb
='Deleting'),
476 'A/D/H' : Item(verb
='Deleting'),
479 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 2)
480 expected_status
.remove('A/mu', 'A/D/H',
481 'A/D/H/psi', 'A/D/H/omega', 'A/D/H/chi')
482 expected_status
.tweak(wc_rev
=1)
484 svntest
.actions
.run_and_verify_commit(wc_dir
,
489 #----------------------------------------------------------------------
490 # Regression test for issue #854:
491 # Revert . inside an svn added empty directory should generate an error.
493 def revert_inside_newly_added_dir(sbox
):
494 "revert inside a newly added dir"
496 sbox
.build(read_only
= True)
501 # Schedule a new directory for addition
503 svntest
.main
.run_svn(None, 'add', 'foo')
505 # Now change into the newly added directory, revert and make sure
506 # an error is output.
508 svntest
.actions
.run_and_verify_svn(None, None, svntest
.verify
.AnyOutput
,
511 #----------------------------------------------------------------------
512 # Regression test for issue #1609:
513 # 'svn status' should show a schedule-add directory as 'A' not '?'
515 def status_add_deleted_directory(sbox
):
516 "status after add of deleted directory"
521 # The original recipe:
523 # svnadmin create repo
524 # svn mkdir file://`pwd`/repo/foo -m r1
525 # svn co file://`pwd`/repo wc
531 A_path
= os
.path
.join(wc_dir
, 'A')
532 svntest
.actions
.run_and_verify_svn(None, None, [], 'rm', A_path
)
533 svntest
.main
.safe_rmtree(A_path
)
534 svntest
.actions
.run_and_verify_svn(None, None, [],
535 'ci', '-m', 'log msg', wc_dir
)
536 svntest
.actions
.run_and_verify_svn(None, None, [], 'mkdir', A_path
)
538 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 2)
539 expected_status
= svntest
.wc
.State(wc_dir
,
540 { '' : Item(status
=' ', wc_rev
=1),
541 'A' : Item(status
='A ', wc_rev
=0),
542 'iota' : Item(status
=' ', wc_rev
=1),
544 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
546 # Update will *not* remove the entry for A despite it being marked
548 svntest
.actions
.run_and_verify_svn(None, ['At revision 2.\n'], [],
550 expected_status
.tweak('', 'iota', wc_rev
=2)
551 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
554 #----------------------------------------------------------------------
555 # Regression test for issue #939:
556 # Recursive 'svn add' should still traverse already-versioned dirs.
557 def add_recursive_already_versioned(sbox
):
558 "'svn add' should traverse already-versioned dirs"
562 if svntest
.actions
.make_repo_and_wc(sbox
):
565 # Create some files, then schedule them for addition
566 delta_path
= os
.path
.join(wc_dir
, 'delta')
567 zeta_path
= os
.path
.join(wc_dir
, 'A', 'B', 'zeta')
568 epsilon_path
= os
.path
.join(wc_dir
, 'A', 'D', 'G', 'epsilon')
570 svntest
.main
.file_append(delta_path
, "This is the file 'delta'.")
571 svntest
.main
.file_append(zeta_path
, "This is the file 'zeta'.")
572 svntest
.main
.file_append(epsilon_path
, "This is the file 'epsilon'.")
574 # Make sure the adds show up as such in status
575 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 1)
576 expected_status
.add({
577 'delta' : Item(status
='A ', wc_rev
=0),
578 'A/B/zeta' : Item(status
='A ', wc_rev
=0),
579 'A/D/G/epsilon' : Item(status
='A ', wc_rev
=0),
582 # Perform the add with the --force flag, and check the status.
583 ### TODO: This part won't work -- you have to be inside the working copy
584 ### or else Subversion will think you're trying to add the working copy
585 ### to its parent directory, and will (possibly, if the parent directory
586 ### isn't versioned) fail.
587 #svntest.main.run_svn(None, 'add', '--force', wc_dir)
588 #svntest.actions.run_and_verify_status(wc_dir, expected_status)
590 # Now revert, and do the adds again from inside the working copy.
591 svntest
.main
.run_svn(None, 'revert', '--recursive', wc_dir
)
592 saved_wd
= os
.getcwd()
594 svntest
.main
.run_svn(None, 'add', '--force', '.')
596 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
599 #----------------------------------------------------------------------
600 # Regression test for the case where "svn mkdir" outside a working copy
601 # would create a directory, but then not clean up after itself when it
602 # couldn't add it to source control.
603 def fail_add_directory(sbox
):
604 "'svn mkdir' should clean up after itself on error"
605 # This test doesn't use a working copy
606 svntest
.main
.safe_rmtree(sbox
.wc_dir
)
607 os
.makedirs(sbox
.wc_dir
)
609 os
.chdir(sbox
.wc_dir
)
610 svntest
.actions
.run_and_verify_svn('Failed mkdir',
611 None, svntest
.verify
.AnyOutput
,
613 if os
.path
.exists('A'):
614 raise svntest
.Failure('svn mkdir created an unversioned directory')
617 #----------------------------------------------------------------------
618 # Regression test for #2440
619 # Ideally this test should test for the exit status of the
620 # 'svn rm non-existent' invocation.
621 # As the corresponding change to get the exit code of svn binary invoked needs
622 # a change in many testcase, for now this testcase checks the stderr.
623 def delete_non_existent(sbox
):
624 "'svn rm non-existent' should exit with an error"
626 sbox
.build(read_only
= True)
630 svntest
.actions
.run_and_verify_svn(None, None, svntest
.verify
.AnyOutput
,
631 'rm', '--force', 'non-existent')
633 ########################################################################
637 # list all tests here, starting with None:
640 revert_add_directories
,
642 SkipUnless(revert_add_executable
, svntest
.main
.is_posix_os
),
645 unschedule_missing_added
,
647 revert_inside_newly_added_dir
,
648 status_add_deleted_directory
,
649 add_recursive_already_versioned
,
654 if __name__
== '__main__':
655 svntest
.main
.run_tests(test_list
)