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"
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"
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"
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"
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"
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"
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 output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
281 '--recursive', wc_dir
)
282 check_reversion(files
, output
)
284 #----------------------------------------------------------------------
286 def revert_add_directories(sbox
):
287 "revert: add some directories"
289 add_directories(sbox
)
292 # Revert our changes recursively from wc_dir.
293 X_path
= os
.path
.join(wc_dir
, 'X')
294 Y_path
= os
.path
.join(wc_dir
, 'A', 'C', 'Y')
295 Z_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H', 'Z')
296 files
= [X_path
, Y_path
, Z_path
]
298 output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
300 '--recursive', wc_dir
)
301 check_reversion(files
, output
)
303 #----------------------------------------------------------------------
305 def revert_nested_adds(sbox
):
306 "revert: add some nested files and directories"
311 # Revert our changes recursively from wc_dir.
312 X_path
= os
.path
.join(wc_dir
, 'X')
313 Y_path
= os
.path
.join(wc_dir
, 'A', 'C', 'Y')
314 Z_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H', 'Z')
315 files
= [X_path
, Y_path
, Z_path
]
317 output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
319 '--recursive', wc_dir
)
320 check_reversion(files
, output
)
322 #----------------------------------------------------------------------
324 def revert_add_executable(sbox
):
325 "revert: add some executable files"
330 all_path
= os
.path
.join(wc_dir
, 'all_exe')
331 none_path
= os
.path
.join(wc_dir
, 'none_exe')
332 user_path
= os
.path
.join(wc_dir
, 'user_exe')
333 group_path
= os
.path
.join(wc_dir
, 'group_exe')
334 other_path
= os
.path
.join(wc_dir
, 'other_exe')
335 files
= [all_path
, none_path
, user_path
, group_path
, other_path
]
337 output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
339 '--recursive', wc_dir
)
340 check_reversion(files
, output
)
342 #----------------------------------------------------------------------
344 def revert_delete_files(sbox
):
345 "revert: delete some files"
350 # Revert our changes recursively from wc_dir.
351 iota_path
= os
.path
.join(wc_dir
, 'iota')
352 mu_path
= os
.path
.join(wc_dir
, 'A', 'mu')
353 rho_path
= os
.path
.join(wc_dir
, 'A', 'D', 'G', 'rho')
354 omega_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H', 'omega')
355 files
= [iota_path
, mu_path
, omega_path
, rho_path
]
357 output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
359 '--recursive', wc_dir
)
360 check_reversion(files
, output
)
362 #----------------------------------------------------------------------
364 def revert_delete_dirs(sbox
):
365 "revert: delete some directories"
370 # Revert our changes recursively from wc_dir.
371 E_path
= os
.path
.join(wc_dir
, 'A', 'B', 'E')
372 F_path
= os
.path
.join(wc_dir
, 'A', 'B', 'F')
373 H_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H')
374 alpha_path
= os
.path
.join(E_path
, 'alpha')
375 beta_path
= os
.path
.join(E_path
, 'beta')
376 chi_path
= os
.path
.join(H_path
, 'chi')
377 omega_path
= os
.path
.join(H_path
, 'omega')
378 psi_path
= os
.path
.join(H_path
, 'psi')
379 files
= [E_path
, F_path
, H_path
,
380 alpha_path
, beta_path
, chi_path
, omega_path
, psi_path
]
382 output
, err
= svntest
.actions
.run_and_verify_svn(None, None, [],
384 '--recursive', wc_dir
)
385 check_reversion(files
, output
)
388 #######################################################################
392 #----------------------------------------------------------------------
393 # Regression test for issue #863:
395 # Suppose here is a scheduled-add file or directory which is
396 # also missing. If I want to make the working copy forget all
397 # knowledge of the item ("unschedule" the addition), then either 'svn
398 # revert' or 'svn rm' will make that happen by removing the entry from
399 # .svn/entries file. While 'svn revert' does with no error,
400 # 'svn rm' does it with error.
402 def unschedule_missing_added(sbox
):
403 "unschedule addition on missing items"
408 # Create some files and dirs, then schedule them for addition
409 file1_path
= os
.path
.join(wc_dir
, 'file1')
410 file2_path
= os
.path
.join(wc_dir
, 'file2')
411 dir1_path
= os
.path
.join(wc_dir
, 'dir1')
412 dir2_path
= os
.path
.join(wc_dir
, 'dir2')
414 svntest
.main
.file_append(file1_path
, "This is the file 'file1'.")
415 svntest
.main
.file_append(file2_path
, "This is the file 'file2'.")
416 svntest
.main
.run_svn(None, 'add', file1_path
, file2_path
)
417 svntest
.main
.run_svn(None, 'mkdir', dir1_path
, dir2_path
)
419 # Make sure the 4 adds show up as such in status
420 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 1)
421 expected_status
.add({
422 'file1' : Item(status
='A ', wc_rev
=0),
423 'file2' : Item(status
='A ', wc_rev
=0),
424 'dir1' : Item(status
='A ', wc_rev
=0),
425 'dir2' : Item(status
='A ', wc_rev
=0),
428 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
430 # Poof, all 4 added things are now missing in action.
431 os
.remove(file1_path
)
432 os
.remove(file2_path
)
433 svntest
.main
.safe_rmtree(dir1_path
)
434 svntest
.main
.safe_rmtree(dir2_path
)
436 # Unschedule the additions, using 'svn rm' and 'svn revert'.
437 svntest
.main
.run_svn(svntest
.verify
.AnyOutput
, 'rm', file1_path
)
438 svntest
.main
.run_svn(svntest
.verify
.AnyOutput
, 'rm', dir1_path
)
439 svntest
.main
.run_svn(None, 'revert', file2_path
, dir2_path
)
441 # 'svn st' should now show absolutely zero local mods.
442 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 1)
443 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
445 #----------------------------------------------------------------------
446 # Regression test for issue #962:
448 # Make sure 'rm foo; svn rm foo' works on files and directories.
449 # Also make sure that the deletion is committable.
451 def delete_missing(sbox
):
452 "schedule and commit deletion on missing items"
457 mu_path
= os
.path
.join(wc_dir
, 'A', 'mu')
458 H_path
= os
.path
.join(wc_dir
, 'A', 'D', 'H')
460 # Manually remove a file and a directory.
462 svntest
.main
.safe_rmtree(H_path
)
464 # Now schedule them for deletion anyway, and make sure no error is output.
465 svntest
.actions
.run_and_verify_svn(None, None, [], 'rm', mu_path
, H_path
)
467 # Commit the deletions.
468 expected_output
= svntest
.wc
.State(wc_dir
, {
469 'A/mu' : Item(verb
='Deleting'),
470 'A/D/H' : Item(verb
='Deleting'),
473 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 2)
474 expected_status
.remove('A/mu', 'A/D/H',
475 'A/D/H/psi', 'A/D/H/omega', 'A/D/H/chi')
476 expected_status
.tweak(wc_rev
=1)
478 svntest
.actions
.run_and_verify_commit(wc_dir
,
481 None, None, None, None, None,
484 #----------------------------------------------------------------------
485 # Regression test for issue #854:
486 # Revert . inside an svn added empty directory should generate an error.
488 def revert_inside_newly_added_dir(sbox
):
489 "revert inside a newly added dir"
496 # Schedule a new directory for addition
498 svntest
.main
.run_svn(None, 'add', 'foo')
500 # Now change into the newly added directory, revert and make sure
501 # an error is output.
503 svntest
.actions
.run_and_verify_svn(None, None, svntest
.verify
.AnyOutput
,
506 #----------------------------------------------------------------------
507 # Regression test for issue #1609:
508 # 'svn status' should show a schedule-add directory as 'A' not '?'
510 def status_add_deleted_directory(sbox
):
511 "status after add of deleted directory"
516 # The original recipe:
518 # svnadmin create repo
519 # svn mkdir file://`pwd`/repo/foo -m r1
520 # svn co file://`pwd`/repo wc
526 A_path
= os
.path
.join(wc_dir
, 'A')
527 svntest
.actions
.run_and_verify_svn(None, None, [], 'rm', A_path
)
528 svntest
.main
.safe_rmtree(A_path
)
529 svntest
.actions
.run_and_verify_svn(None, None, [],
530 'ci', '-m', 'log msg', wc_dir
)
531 svntest
.actions
.run_and_verify_svn(None, None, [], 'mkdir', A_path
)
533 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 2)
534 expected_status
= svntest
.wc
.State(wc_dir
,
535 { '' : Item(status
=' ', wc_rev
=1),
536 'A' : Item(status
='A ', wc_rev
=0),
537 'iota' : Item(status
=' ', wc_rev
=1),
539 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
541 # Update will *not* remove the entry for A despite it being marked
543 svntest
.actions
.run_and_verify_svn(None, ['At revision 2.\n'], [],
545 expected_status
.tweak('', 'iota', wc_rev
=2)
546 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
549 #----------------------------------------------------------------------
550 # Regression test for issue #939:
551 # Recursive 'svn add' should still traverse already-versioned dirs.
552 def add_recursive_already_versioned(sbox
):
553 "'svn add' should traverse already-versioned dirs"
557 if svntest
.actions
.make_repo_and_wc(sbox
):
560 # Create some files, then schedule them for addition
561 delta_path
= os
.path
.join(wc_dir
, 'delta')
562 zeta_path
= os
.path
.join(wc_dir
, 'A', 'B', 'zeta')
563 epsilon_path
= os
.path
.join(wc_dir
, 'A', 'D', 'G', 'epsilon')
565 svntest
.main
.file_append(delta_path
, "This is the file 'delta'.")
566 svntest
.main
.file_append(zeta_path
, "This is the file 'zeta'.")
567 svntest
.main
.file_append(epsilon_path
, "This is the file 'epsilon'.")
569 # Make sure the adds show up as such in status
570 expected_status
= svntest
.actions
.get_virginal_state(wc_dir
, 1)
571 expected_status
.add({
572 'delta' : Item(status
='A ', wc_rev
=0),
573 'A/B/zeta' : Item(status
='A ', wc_rev
=0),
574 'A/D/G/epsilon' : Item(status
='A ', wc_rev
=0),
577 # Perform the add with the --force flag, and check the status.
578 ### TODO: This part won't work -- you have to be inside the working copy
579 ### or else Subversion will think you're trying to add the working copy
580 ### to its parent directory, and will (possibly, if the parent directory
581 ### isn't versioned) fail.
582 #svntest.main.run_svn(None, 'add', '--force', wc_dir)
583 #svntest.actions.run_and_verify_status(wc_dir, expected_status)
585 # Now revert, and do the adds again from inside the working copy.
586 svntest
.main
.run_svn(None, 'revert', '--recursive', wc_dir
)
587 saved_wd
= os
.getcwd()
589 svntest
.main
.run_svn(None, 'add', '--force', '.')
591 svntest
.actions
.run_and_verify_status(wc_dir
, expected_status
)
594 #----------------------------------------------------------------------
595 # Regression test for the case where "svn mkdir" outside a working copy
596 # would create a directory, but then not clean up after itself when it
597 # couldn't add it to source control.
598 def fail_add_directory(sbox
):
599 "'svn mkdir' should clean up after itself on error"
600 # This test doesn't use a working copy
601 svntest
.main
.safe_rmtree(sbox
.wc_dir
)
602 os
.makedirs(sbox
.wc_dir
)
604 os
.chdir(sbox
.wc_dir
)
605 svntest
.actions
.run_and_verify_svn('Failed mkdir',
606 None, svntest
.verify
.AnyOutput
,
608 if os
.path
.exists('A'):
609 raise svntest
.Failure('svn mkdir created an unversioned directory')
612 #----------------------------------------------------------------------
613 # Regression test for #2440
614 # Ideally this test should test for the exit status of the
615 # 'svn rm non-existent' invocation.
616 # As the corresponding change to get the exit code of svn binary invoked needs
617 # a change in many testcase, for now this testcase checks the stderr.
618 def delete_non_existent(sbox
):
619 "'svn rm non-existent' should exit with an error"
625 svntest
.actions
.run_and_verify_svn(None, None, svntest
.verify
.AnyOutput
,
626 'rm', '--force', 'non-existent')
628 ########################################################################
632 # list all tests here, starting with None:
635 revert_add_directories
,
637 SkipUnless(revert_add_executable
, svntest
.main
.is_posix_os
),
640 unschedule_missing_added
,
642 revert_inside_newly_added_dir
,
643 status_add_deleted_directory
,
644 add_recursive_already_versioned
,
649 if __name__
== '__main__':
650 svntest
.main
.run_tests(test_list
)