#!/usr/bin/env python # # schedule_tests.py: testing working copy scheduling # (adds, deletes, reversion) # # Subversion is a tool for revision control. # See http://subversion.apache.org for more information. # # ==================================================================== # Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. ###################################################################### # General modules import os # Our testing module import svntest # (abbreviation) Skip = svntest.testcase.Skip_deco SkipUnless = svntest.testcase.SkipUnless_deco XFail = svntest.testcase.XFail_deco Issues = svntest.testcase.Issues_deco Issue = svntest.testcase.Issue_deco Wimp = svntest.testcase.Wimp_deco Item = svntest.wc.StateItem exp_noop_up_out = svntest.actions.expected_noop_update_output ###################################################################### # Tests # # Each test must return on success or raise on failure. # ####################################################################### # Stage I - Schedules and modifications, verified with `svn status' # # These tests make schedule changes and local mods, and verify that status # output is as expected. In a second stage, reversion of these changes is # tested. Potentially, a third stage could test committing these same # changes. # # NOTE: these tests are run within the Stage II tests, not on their own. # def add_files(sbox): "schedule: add some files" sbox.build(read_only = True) wc_dir = sbox.wc_dir # Create some files, then schedule them for addition delta_path = sbox.ospath('delta') zeta_path = sbox.ospath('A/B/zeta') epsilon_path = sbox.ospath('A/D/G/epsilon') svntest.main.file_append(delta_path, "This is the file 'delta'.") svntest.main.file_append(zeta_path, "This is the file 'zeta'.") svntest.main.file_append(epsilon_path, "This is the file 'epsilon'.") sbox.simple_add('delta', 'A/B/zeta', 'A/D/G/epsilon') # Make sure the adds show up as such in status expected_status = svntest.actions.get_virginal_state(wc_dir, 1) expected_status.add({ 'delta' : Item(status='A ', wc_rev=0), 'A/B/zeta' : Item(status='A ', wc_rev=0), 'A/D/G/epsilon' : Item(status='A ', wc_rev=0), }) svntest.actions.run_and_verify_status(wc_dir, expected_status) #---------------------------------------------------------------------- def add_directories(sbox): "schedule: add some directories" sbox.build(read_only = True) wc_dir = sbox.wc_dir # Create some directories, then schedule them for addition X_path = sbox.ospath('X') Y_path = sbox.ospath('A/C/Y') Z_path = sbox.ospath('A/D/H/Z') os.mkdir(X_path) os.mkdir(Y_path) os.mkdir(Z_path) sbox.simple_add('X', 'A/C/Y', 'A/D/H/Z') # Make sure the adds show up as such in status expected_status = svntest.actions.get_virginal_state(wc_dir, 1) expected_status.add({ 'X' : Item(status='A ', wc_rev=0), 'A/C/Y' : Item(status='A ', wc_rev=0), 'A/D/H/Z' : Item(status='A ', wc_rev=0), }) svntest.actions.run_and_verify_status(wc_dir, expected_status) #---------------------------------------------------------------------- def nested_adds(sbox): "schedule: add some nested files and directories" sbox.build(read_only = True) wc_dir = sbox.wc_dir # Create some directories then schedule them for addition X_path = sbox.ospath('X') Y_path = sbox.ospath('A/C/Y') Z_path = sbox.ospath('A/D/H/Z') os.mkdir(X_path) os.mkdir(Y_path) os.mkdir(Z_path) # Now, create some files and directories to put into our newly added # directories P_path = sbox.ospath('X/P') Q_path = sbox.ospath('A/C/Y/Q') R_path = sbox.ospath('A/D/H/Z/R') os.mkdir(P_path) os.mkdir(Q_path) os.mkdir(R_path) delta_path = sbox.ospath('X/delta') epsilon_path = sbox.ospath('A/C/Y/epsilon') upsilon_path = sbox.ospath('A/C/Y/upsilon') zeta_path = sbox.ospath('A/D/H/Z/zeta') svntest.main.file_append(delta_path, "This is the file 'delta'.") svntest.main.file_append(epsilon_path, "This is the file 'epsilon'.") svntest.main.file_append(upsilon_path, "This is the file 'upsilon'.") svntest.main.file_append(zeta_path, "This is the file 'zeta'.") # Finally, let's try adding our new files and directories sbox.simple_add('X', 'A/C/Y', 'A/D/H/Z') # Make sure the adds show up as such in status expected_status = svntest.actions.get_virginal_state(wc_dir, 1) expected_status.add({ 'X' : Item(status='A ', wc_rev=0), 'A/C/Y' : Item(status='A ', wc_rev=0), 'A/D/H/Z' : Item(status='A ', wc_rev=0), 'X/P' : Item(status='A ', wc_rev=0), 'A/C/Y/Q' : Item(status='A ', wc_rev=0), 'A/D/H/Z/R' : Item(status='A ', wc_rev=0), 'X/delta' : Item(status='A ', wc_rev=0), 'A/C/Y/epsilon' : Item(status='A ', wc_rev=0), 'A/C/Y/upsilon' : Item(status='A ', wc_rev=0), 'A/D/H/Z/zeta' : Item(status='A ', wc_rev=0), }) svntest.actions.run_and_verify_status(wc_dir, expected_status) #---------------------------------------------------------------------- def add_executable(sbox): "schedule: add some executable files" sbox.build(read_only = True) def runTest(wc_dir, fileName, perm, executable): file_ospath = sbox.ospath(fileName) if executable: expected_out = ["*\n"] else: expected_out = [] # create an empty file open(file_ospath, "w") os.chmod(file_ospath, perm) sbox.simple_add(fileName) svntest.actions.run_and_verify_svn(None, expected_out, [], 'propget', "svn:executable", file_ospath) test_cases = [ ("all_exe", 0777, 1), ("none_exe", 0666, 0), ("user_exe", 0766, 1), ("group_exe", 0676, 0), ("other_exe", 0667, 0), ] for test_case in test_cases: runTest(sbox.wc_dir, *test_case) #---------------------------------------------------------------------- def delete_files(sbox): "schedule: delete some files" sbox.build(read_only = True) wc_dir = sbox.wc_dir # Schedule some files for deletion sbox.simple_rm('iota', 'A/mu', 'A/D/G/rho', 'A/D/H/omega') # Make sure the deletes show up as such in status expected_status = svntest.actions.get_virginal_state(wc_dir, 1) expected_status.tweak('iota', 'A/mu', 'A/D/G/rho', 'A/D/H/omega', status='D ') svntest.actions.run_and_verify_status(wc_dir, expected_status) #---------------------------------------------------------------------- def delete_dirs(sbox): "schedule: delete some directories" sbox.build(read_only = True) wc_dir = sbox.wc_dir # Schedule some directories for deletion (this is recursive!) sbox.simple_rm('A/B/E', 'A/B/F', 'A/D/H') # Make sure the deletes show up as such in status expected_status = svntest.actions.get_virginal_state(wc_dir, 1) expected_status.tweak('A/B/E', 'A/B/E/alpha', 'A/B/E/beta', 'A/B/F', 'A/D/H', 'A/D/H/chi', 'A/D/H/omega', 'A/D/H/psi', status='D ') svntest.actions.run_and_verify_status(wc_dir, expected_status) ####################################################################### # Stage II - Reversion of changes made in Stage I # # Each test in Stage II calls the corresponding Stage I test # and then also tests reversion of those changes. # def check_reversion(files, output): expected_output = [] for file in files: expected_output = expected_output + ["Reverted '" + file + "'\n"] output.sort() expected_output.sort() if output != expected_output: print("Expected output: %s" % expected_output) print("Actual output: %s" % output) raise svntest.Failure #---------------------------------------------------------------------- def revert_add_files(sbox): "revert: add some files" add_files(sbox) wc_dir = sbox.wc_dir # Revert our changes recursively from wc_dir. delta_path = sbox.ospath('delta') zeta_path = sbox.ospath('A/B/zeta') epsilon_path = sbox.ospath('A/D/G/epsilon') files = [delta_path, zeta_path, epsilon_path] exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [], 'revert', '--recursive', wc_dir) check_reversion(files, output) #---------------------------------------------------------------------- def revert_add_directories(sbox): "revert: add some directories" add_directories(sbox) wc_dir = sbox.wc_dir # Revert our changes recursively from wc_dir. X_path = sbox.ospath('X') Y_path = sbox.ospath('A/C/Y') Z_path = sbox.ospath('A/D/H/Z') files = [X_path, Y_path, Z_path] exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [], 'revert', '--recursive', wc_dir) check_reversion(files, output) #---------------------------------------------------------------------- def revert_nested_adds(sbox): "revert: add some nested files and directories" nested_adds(sbox) wc_dir = sbox.wc_dir # Revert our changes recursively from wc_dir. X_path = sbox.ospath('X') Y_path = sbox.ospath('A/C/Y') Z_path = sbox.ospath('A/D/H/Z') files = ([X_path, Y_path, Z_path] + [os.path.join(X_path, child) for child in ['P', 'delta']] + [os.path.join(Y_path, child) for child in ['Q', 'epsilon', 'upsilon']] + [os.path.join(Z_path, child) for child in ['R', 'zeta']]) exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [], 'revert', '--recursive', wc_dir) check_reversion(files, output) #---------------------------------------------------------------------- @SkipUnless(svntest.main.is_posix_os) def revert_add_executable(sbox): "revert: add some executable files" add_executable(sbox) wc_dir = sbox.wc_dir all_path = sbox.ospath('all_exe') none_path = sbox.ospath('none_exe') user_path = sbox.ospath('user_exe') group_path = sbox.ospath('group_exe') other_path = sbox.ospath('other_exe') files = [all_path, none_path, user_path, group_path, other_path] exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [], 'revert', '--recursive', wc_dir) check_reversion(files, output) #---------------------------------------------------------------------- def revert_delete_files(sbox): "revert: delete some files" delete_files(sbox) wc_dir = sbox.wc_dir # Revert our changes recursively from wc_dir. iota_path = sbox.ospath('iota') mu_path = sbox.ospath('A/mu') rho_path = sbox.ospath('A/D/G/rho') omega_path = sbox.ospath('A/D/H/omega') files = [iota_path, mu_path, omega_path, rho_path] exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [], 'revert', '--recursive', wc_dir) check_reversion(files, output) #---------------------------------------------------------------------- def revert_delete_dirs(sbox): "revert: delete some directories" delete_dirs(sbox) wc_dir = sbox.wc_dir # Revert our changes recursively from wc_dir. E_path = sbox.ospath('A/B/E') F_path = sbox.ospath('A/B/F') H_path = sbox.ospath('A/D/H') alpha_path = sbox.ospath('A/B/E/alpha') beta_path = sbox.ospath('A/B/E/beta') chi_path = sbox.ospath('A/D/H/chi') omega_path = sbox.ospath('A/D/H/omega') psi_path = sbox.ospath('A/D/H/psi') files = [E_path, F_path, H_path, alpha_path, beta_path, chi_path, omega_path, psi_path] exit_code, output, err = svntest.actions.run_and_verify_svn(None, None, [], 'revert', '--recursive', wc_dir) check_reversion(files, output) ####################################################################### # Regression tests # #---------------------------------------------------------------------- # Regression test for issue #863: # # Suppose here is a scheduled-add file or directory which is # also missing. If I want to make the working copy forget all # knowledge of the item ("unschedule" the addition), then either 'svn # revert' or 'svn rm' will make that happen by removing the entry from # .svn/entries file. While 'svn revert' does with no error, # 'svn rm' does it with error. @Issue(863) def unschedule_missing_added(sbox): "unschedule addition on missing items" sbox.build(read_only = True) wc_dir = sbox.wc_dir # Create some files and dirs, then schedule them for addition file1_path = sbox.ospath('file1') file2_path = sbox.ospath('file2') dir1_path = sbox.ospath('dir1') dir2_path = sbox.ospath('dir2') svntest.main.file_append(file1_path, "This is the file 'file1'.") svntest.main.file_append(file2_path, "This is the file 'file2'.") sbox.simple_add('file1', 'file2') sbox.simple_mkdir('dir1', 'dir2') # Make sure the 4 adds show up as such in status expected_status = svntest.actions.get_virginal_state(wc_dir, 1) expected_status.add({ 'file1' : Item(status='A ', wc_rev=0), 'file2' : Item(status='A ', wc_rev=0), 'dir1' : Item(status='A ', wc_rev=0), 'dir2' : Item(status='A ', wc_rev=0), }) svntest.actions.run_and_verify_status(wc_dir, expected_status) # Poof, all 4 added things are now missing in action. os.remove(file1_path) os.remove(file2_path) svntest.main.safe_rmtree(dir1_path) svntest.main.safe_rmtree(dir2_path) # Unschedule the additions, using 'svn rm' and 'svn revert'. # FILE1_PATH will throw an error. DIR1_PATH will not since the stub is # still available in the parent directory. svntest.main.run_svn(svntest.verify.AnyOutput, 'rm', file1_path) ### actually, the stub does not provide enough information to revert ### the addition, so this command will fail. marking as XFail sbox.simple_rm('dir1') sbox.simple_revert('file2', 'dir2') # 'svn st' should now show absolutely zero local mods. expected_status = svntest.actions.get_virginal_state(wc_dir, 1) svntest.actions.run_and_verify_status(wc_dir, expected_status) #---------------------------------------------------------------------- # Regression test for issue #962: # # Make sure 'rm foo; svn rm foo' works on files and directories. # Also make sure that the deletion is committable. @Issue(962) def delete_missing(sbox): "schedule and commit deletion on missing items" sbox.build() wc_dir = sbox.wc_dir mu_path = sbox.ospath('A/mu') H_path = sbox.ospath('A/D/H') # Manually remove a file and a directory. os.remove(mu_path) svntest.main.safe_rmtree(H_path) # Now schedule them for deletion anyway, and make sure no error is output. sbox.simple_rm('A/mu', 'A/D/H') # Commit the deletions. expected_output = svntest.wc.State(wc_dir, { 'A/mu' : Item(verb='Deleting'), 'A/D/H' : Item(verb='Deleting'), }) expected_status = svntest.actions.get_virginal_state(wc_dir, 2) expected_status.remove('A/mu', 'A/D/H', 'A/D/H/psi', 'A/D/H/omega', 'A/D/H/chi') expected_status.tweak(wc_rev=1) svntest.actions.run_and_verify_commit(wc_dir, expected_output, expected_status, None, wc_dir) #---------------------------------------------------------------------- # Regression test for issue #854: # Revert . inside an svn added empty directory should generate an error. # Not anymore! wc-ng uses absolute paths for everything, which means we # can handle this case without too much trouble. @Issue(854) def revert_inside_newly_added_dir(sbox): "revert inside a newly added dir" sbox.build(read_only = True) # Schedule a new directory for addition sbox.simple_mkdir('foo') # Now change into the newly added directory, revert and make sure # no error is output. os.chdir(sbox.ospath('foo')) svntest.main.run_svn(None, 'revert', '.') #---------------------------------------------------------------------- # Regression test for issue #1609: # 'svn status' should show a schedule-add directory as 'A' not '?' @Issue(1609) def status_add_deleted_directory(sbox): "status after add of deleted directory" sbox.build() wc_dir = sbox.wc_dir # The original recipe: # # svnadmin create repo # svn mkdir file://`pwd`/repo/foo -m r1 # svn co file://`pwd`/repo wc # svn rm wc/foo # rm -rf wc/foo # svn ci wc -m r2 # svn mkdir wc/foo A_path = sbox.ospath('A') sbox.simple_rm('A') svntest.main.safe_rmtree(A_path) sbox.simple_commit() sbox.simple_mkdir('A') expected_status = svntest.actions.get_virginal_state(wc_dir, 2) expected_status = svntest.wc.State(wc_dir, { '' : Item(status=' ', wc_rev=1), 'A' : Item(status='A ', wc_rev=0), 'iota' : Item(status=' ', wc_rev=1), }) svntest.actions.run_and_verify_status(wc_dir, expected_status) # Update will *not* remove the entry for A despite it being marked # deleted. svntest.actions.run_and_verify_svn(None, exp_noop_up_out(2), [], 'up', wc_dir) expected_status.tweak('', 'iota', wc_rev=2) svntest.actions.run_and_verify_status(wc_dir, expected_status) #---------------------------------------------------------------------- # Regression test for issue #939: # Recursive 'svn add' should still traverse already-versioned dirs. @Issue(939) def add_recursive_already_versioned(sbox): "'svn add' should traverse already-versioned dirs" wc_dir = sbox.wc_dir svntest.actions.make_repo_and_wc(sbox) # Create some files, then schedule them for addition delta_path = sbox.ospath('delta') zeta_path = sbox.ospath('A/B/zeta') epsilon_path = sbox.ospath('A/D/G/epsilon') svntest.main.file_append(delta_path, "This is the file 'delta'.") svntest.main.file_append(zeta_path, "This is the file 'zeta'.") svntest.main.file_append(epsilon_path, "This is the file 'epsilon'.") # Make sure the adds show up as such in status expected_status = svntest.actions.get_virginal_state(wc_dir, 1) expected_status.add({ 'delta' : Item(status='A ', wc_rev=0), 'A/B/zeta' : Item(status='A ', wc_rev=0), 'A/D/G/epsilon' : Item(status='A ', wc_rev=0), }) # Perform the add with the --force flag, and check the status. ### TODO: This part won't work -- you have to be inside the working copy ### or else Subversion will think you're trying to add the working copy ### to its parent directory, and will (possibly, if the parent directory ### isn't versioned) fail. #svntest.main.run_svn(None, 'add', '--force', wc_dir) #svntest.actions.run_and_verify_status(wc_dir, expected_status) # Now revert, and do the adds again from inside the working copy. svntest.main.run_svn(None, 'revert', '--recursive', wc_dir) saved_wd = os.getcwd() os.chdir(wc_dir) svntest.main.run_svn(None, 'add', '--force', '.') os.chdir(saved_wd) svntest.actions.run_and_verify_status(wc_dir, expected_status) #---------------------------------------------------------------------- # Regression test for the case where "svn mkdir" outside a working copy # would create a directory, but then not clean up after itself when it # couldn't add it to source control. def fail_add_directory(sbox): "'svn mkdir' should clean up after itself on error" # This test doesn't use a working copy svntest.main.safe_rmtree(sbox.wc_dir) os.makedirs(sbox.wc_dir) os.chdir(sbox.wc_dir) svntest.actions.run_and_verify_svn('Failed mkdir', None, svntest.verify.AnyOutput, 'mkdir', 'A') if os.path.exists('A'): raise svntest.Failure('svn mkdir created an unversioned directory') #---------------------------------------------------------------------- # Regression test for #2440 # Ideally this test should test for the exit status of the # 'svn rm non-existent' invocation. # As the corresponding change to get the exit code of svn binary invoked needs # a change in many testcase, for now this testcase checks the stderr. def delete_non_existent(sbox): "'svn rm non-existent' should exit with an error" sbox.build(read_only = True) wc_dir = sbox.wc_dir os.chdir(wc_dir) svntest.actions.run_and_verify_svn(None, None, svntest.verify.AnyOutput, 'rm', '--force', 'non-existent') #---------------------------------------------------------------------- # Problem encountered by cmpilato when he inadvertantly upset an # 'svn rm --keep-local' and had to retry it. def delete_redelete_fudgery(sbox): "retry of manually upset --keep-local deletion" sbox.build() wc_dir = sbox.wc_dir B_path = os.path.join(wc_dir, 'A', 'B') # Delete 'A/B' using --keep-local, then remove at the OS level. svntest.actions.run_and_verify_svn(None, None, [], 'rm', '--keep-local', B_path) svntest.main.safe_rmtree(B_path) # Update the tree. # ### When WC-NG is running in single-DB mode (one .svn directory and ### database for the whole working copy), I suspect that this update ### will change. Today it re-adds the directory which we just ### scheduled for deletion because the only record of that ### scheduling is stored -- you guessed it -- the directory's .svn/ ### area... which we just deleted from disk. ### ### In single-DB-WC-NG-land, though, deleting the directory from ### disk should have no bearing whatsoever on the scheduling ### information stored now in the working copy root's one DB. That ### could change the whole flow of this test, possible leading us to ### remove it as altogether irrelevant. --cmpilato svntest.actions.run_and_verify_svn(None, None, [], 'up', wc_dir) # Now try to run svntest.actions.run_and_verify_svn(None, None, [], 'rm', '--keep-local', B_path) def propset_on_deleted_should_fail(sbox): "calling svn propset on a deleted node should fail" sbox.build() wc_dir = sbox.wc_dir iota = os.path.join(wc_dir, 'iota') svntest.actions.run_and_verify_svn(None, None, [], 'rm', iota) svntest.actions.run_and_verify_svn(None, None, "svn: E155023: Can't set propert.*", 'ps', 'prop', 'val', iota) @Issue(3468) def replace_dir_delete_child(sbox): "replace a dir, then delete a child" # The purpose of this test is to make sure that when a child of a # replaced directory is deleted, the result can be committed. sbox.build() # Replace A/D/H with a copy of A/B sbox.simple_rm('A/D/H') sbox.simple_copy('A/B', 'A/D/H') # Remove two children sbox.simple_rm('A/D/H/lambda') sbox.simple_rm('A/D/H/E') # Don't look at what "svn status" says before commit. It's not clear # what it should be and that's not the point of this test. # Commit. expected_output = svntest.wc.State(sbox.wc_dir, { 'A/D/H' : Item(verb='Replacing'), 'A/D/H/lambda' : Item(verb='Deleting'), 'A/D/H/E' : Item(verb='Deleting'), }) expected_status = svntest.actions.get_virginal_state(sbox.wc_dir, 1) expected_status.add({ 'A/D/H/F' : Item(status=' ', wc_rev=0), }) expected_status.tweak('A/D/H', 'A/D/H/F', wc_rev=2) expected_status.remove('A/D/H/psi', 'A/D/H/omega', 'A/D/H/chi') svntest.actions.run_and_verify_commit(sbox.wc_dir, expected_output, expected_status, None, sbox.wc_dir) ######################################################################## # Run the tests # list all tests here, starting with None: test_list = [ None, revert_add_files, revert_add_directories, revert_nested_adds, revert_add_executable, revert_delete_files, revert_delete_dirs, unschedule_missing_added, delete_missing, revert_inside_newly_added_dir, status_add_deleted_directory, add_recursive_already_versioned, fail_add_directory, delete_non_existent, delete_redelete_fudgery, propset_on_deleted_should_fail, replace_dir_delete_child, ] if __name__ == '__main__': svntest.main.run_tests(test_list) # NOTREACHED ### End of file.