Modified: subversion/branches/authzperf/subversion/tests/cmdline/svnsync_tests.py URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/tests/cmdline/svnsync_tests.py?rev=1649205&r1=1649204&r2=1649205&view=diff ============================================================================== --- subversion/branches/authzperf/subversion/tests/cmdline/svnsync_tests.py (original) +++ subversion/branches/authzperf/subversion/tests/cmdline/svnsync_tests.py Sat Jan 3 14:00:41 2015 @@ -209,8 +209,12 @@ def setup_and_sync(sbox, dump_file_conte return dest_sbox -def verify_mirror(dest_sbox, src_sbox): - """Compare the contents of the DEST_SBOX repository with EXP_DUMP_FILE_CONTENTS.""" +def verify_mirror(dest_sbox, exp_dump_file_contents): + """Compare the contents of the mirror repository in DEST_SBOX with + EXP_DUMP_FILE_CONTENTS, by comparing the parsed dump stream content. + + First remove svnsync rev-props from the DEST_SBOX repository. + """ # Remove some SVNSync-specific housekeeping properties from the # mirror repository in preparation for the comparison dump. @@ -222,10 +226,9 @@ def verify_mirror(dest_sbox, src_sbox): # Create a dump file from the mirror repository. dest_dump = svntest.actions.run_and_verify_dump(dest_sbox.repo_dir) - src_dump = svntest.actions.run_and_verify_dump(src_sbox.repo_dir) svntest.verify.compare_dump_files( - "Dump files", "DUMP", src_dump, dest_dump) + "Dump files", "DUMP", exp_dump_file_contents, dest_dump) def run_test(sbox, dump_file_name, subdir=None, exp_dump_file_name=None, bypass_prop_validation=False, source_prop_encoding=None, @@ -251,16 +254,12 @@ or another dump file.""" # dump file (used to create the master repository) or another specified dump # file. if exp_dump_file_name: - build_repos(sbox) - svntest.actions.run_and_verify_load(sbox.repo_dir, - open(os.path.join(svnsync_tests_dir, - exp_dump_file_name), - 'rb').readlines()) - src_sbox = sbox + exp_dump_file_contents = open(os.path.join(svnsync_tests_dir, + exp_dump_file_name), 'rb').readlines() else: - src_sbox = sbox + exp_dump_file_contents = master_dumpfile_contents - verify_mirror(dest_sbox, sbox) + verify_mirror(dest_sbox, exp_dump_file_contents) @@ -564,9 +563,7 @@ def delete_revprops(sbox): run_copy_revprops(dest_sbox.repo_url, sbox.repo_url) # Does the result look as we expected? - build_repos(sbox) - svntest.actions.run_and_verify_load(sbox.repo_dir, expected_contents) - verify_mirror(dest_sbox, sbox) + verify_mirror(dest_sbox, expected_contents) @Issue(3870) @SkipUnless(svntest.main.is_posix_os) @@ -576,6 +573,15 @@ def fd_leak_sync_from_serf_to_local(sbox resource.setrlimit(resource.RLIMIT_NOFILE, (128, 128)) run_test(sbox, "largemods.dump", is_src_ra_local=None, is_dest_ra_local=True) +#---------------------------------------------------------------------- + +@Issue(4476) +def mergeinfo_contains_r0(sbox): + "mergeinfo contains r0" + run_test(sbox, "mergeinfo-contains-r0.dump", + exp_dump_file_name="mergeinfo-contains-r0.expected.dump", + bypass_prop_validation=True) + ######################################################################## # Run the tests @@ -612,6 +618,7 @@ test_list = [ None, descend_into_replace, delete_revprops, fd_leak_sync_from_serf_to_local, # calls setrlimit + mergeinfo_contains_r0, ] if __name__ == '__main__':
Modified: subversion/branches/authzperf/subversion/tests/cmdline/svntest/main.py URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/tests/cmdline/svntest/main.py?rev=1649205&r1=1649204&r2=1649205&view=diff ============================================================================== --- subversion/branches/authzperf/subversion/tests/cmdline/svntest/main.py (original) +++ subversion/branches/authzperf/subversion/tests/cmdline/svntest/main.py Sat Jan 3 14:00:41 2015 @@ -908,68 +908,10 @@ def file_substitute(path, contents, new_ fcontent = open(path, 'r').read().replace(contents, new_contents) open(path, 'w').write(fcontent) -def _unpack_precooked_repos(path, template): - testdir = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) - repozip = os.path.join(os.path.dirname(testdir), "templates", template) - zipfile.ZipFile(repozip, 'r').extractall(path) - -# For creating new, pre-cooked greek repositories -def unpack_greek_repos(path): - template = "greek-fsfs-v%d.zip" % options.fsfs_version - _unpack_precooked_repos(path, template) - -# For creating blank new repositories -def create_repos(path, minor_version = None): - """Create a brand-new SVN repository at PATH. If PATH does not yet - exist, create it.""" - - if not os.path.exists(path): - os.makedirs(path) # this creates all the intermediate dirs, if necessary - - if options.fsfs_version is None: - if options.fs_type == "bdb": - opts = ("--bdb-txn-nosync",) - else: - opts = () - if minor_version is None or minor_version > options.server_minor_version: - minor_version = options.server_minor_version - opts += ("--compatible-version=1.%d" % (minor_version),) - if options.fs_type is not None: - opts += ("--fs-type=" + options.fs_type,) - exit_code, stdout, stderr = run_command(svnadmin_binary, 1, False, - "create", path, *opts) - else: - # Copy a pre-cooked FSFS repository - assert options.fs_type == "fsfs" - template = "empty-fsfs-v%d.zip" % options.fsfs_version - _unpack_precooked_repos(path, template) - exit_code, stdout, stderr = run_command(svnadmin_binary, 1, False, - "setuuid", path) - - # Skip tests if we can't create the repository. - if stderr: - stderr_lines = 0 - not_using_fsfs_backend = (options.fs_type != "fsfs") - backend_deprecation_warning = False - for line in stderr: - stderr_lines += 1 - if line.find('Unknown FS type') != -1: - raise Skip - if not_using_fsfs_backend: - if 0 < line.find('repository back-end is deprecated, consider using'): - backend_deprecation_warning = True - - # Creating BDB repositories will cause svnadmin to print a warning - # which should be ignored. - if (stderr_lines == 1 - and not_using_fsfs_backend - and backend_deprecation_warning): - pass - else: - # If the FS type is known and we noticed more than just the - # BDB-specific warning, assume the repos couldn't be created - # (e.g. due to a missing 'svnadmin' binary). - raise SVNRepositoryCreateFailure("".join(stderr).rstrip()) +# For setting up authz and hooks in existing repos +def _post_create_repos(path, minor_version = None): + """Set default access right configurations for svnserve and mod_dav + as well as hooks etc. in the SVN repository at PATH.""" # Require authentication to write to the repos, for ra_svn testing. file_write(get_svnserve_conf_file_path(path), @@ -1039,6 +981,73 @@ def create_repos(path, minor_version = N # make the repos world-writeable, for mod_dav_svn's sake. chmod_tree(path, 0666, 0666) +def _unpack_precooked_repos(path, template): + testdir = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) + repozip = os.path.join(os.path.dirname(testdir), "templates", template) + zipfile.ZipFile(repozip, 'r').extractall(path) + +# For creating new, pre-cooked greek repositories +def unpack_greek_repos(path): + template = "greek-fsfs-v%d.zip" % options.fsfs_version + _unpack_precooked_repos(path, template) + _post_create_repos(path, options.server_minor_version) + +# For creating blank new repositories +def create_repos(path, minor_version = None): + """Create a brand-new SVN repository at PATH. If PATH does not yet + exist, create it.""" + + if not os.path.exists(path): + os.makedirs(path) # this creates all the intermediate dirs, if necessary + + if options.fsfs_version is None: + if options.fs_type == "bdb": + opts = ("--bdb-txn-nosync",) + else: + opts = () + if minor_version is None or minor_version > options.server_minor_version: + minor_version = options.server_minor_version + opts += ("--compatible-version=1.%d" % (minor_version),) + if options.fs_type is not None: + opts += ("--fs-type=" + options.fs_type,) + exit_code, stdout, stderr = run_command(svnadmin_binary, 1, False, + "create", path, *opts) + else: + # Copy a pre-cooked FSFS repository + assert options.fs_type == "fsfs" + template = "empty-fsfs-v%d.zip" % options.fsfs_version + _unpack_precooked_repos(path, template) + exit_code, stdout, stderr = run_command(svnadmin_binary, 1, False, + "setuuid", path) + + # Skip tests if we can't create the repository. + if stderr: + stderr_lines = 0 + not_using_fsfs_backend = (options.fs_type != "fsfs") + backend_deprecation_warning = False + for line in stderr: + stderr_lines += 1 + if line.find('Unknown FS type') != -1: + raise Skip + if not_using_fsfs_backend: + if 0 < line.find('repository back-end is deprecated, consider using'): + backend_deprecation_warning = True + + # Creating BDB repositories will cause svnadmin to print a warning + # which should be ignored. + if (stderr_lines == 1 + and not_using_fsfs_backend + and backend_deprecation_warning): + pass + else: + # If the FS type is known and we noticed more than just the + # BDB-specific warning, assume the repos couldn't be created + # (e.g. due to a missing 'svnadmin' binary). + raise SVNRepositoryCreateFailure("".join(stderr).rstrip()) + + # Configure the new repository. + _post_create_repos(path, minor_version) + # For copying a repository def copy_repos(src_path, dst_path, head_revision, ignore_uuid = 1, minor_version = None): @@ -1465,6 +1474,17 @@ def is_plaintext_password_storage_disabl return False return True + +# https://issues.apache.org/bugzilla/show_bug.cgi?id=56480 +__mod_dav_url_quoting_broken_versions = frozenset([ + '2.2.26', + '2.4.9', +]) +def is_mod_dav_url_quoting_broken(): + if is_ra_type_dav(): + return (options.httpd_version in __mod_dav_url_quoting_broken_versions) + return None + ###################################################################### @@ -1526,6 +1546,8 @@ class TestSpawningThread(threading.Threa args.append('--http-proxy-username=' + options.http_proxy_username) if options.http_proxy_password: args.append('--http-proxy-password=' + options.http_proxy_password) + if options.httpd_version: + args.append('--httpd-version=' + options.httpd_version) if options.exclusive_wc_locks: args.append('--exclusive-wc-locks') if options.memcached_server: @@ -1887,6 +1909,8 @@ def _create_parser(): help='Username for the HTTP Proxy.') parser.add_option('--http-proxy-password', action='store', help='Password for the HTTP Proxy.') + parser.add_option('--httpd-version', action='store', + help='Assume HTTPD is this version.') parser.add_option('--tools-bin', action='store', dest='tools_bin', help='Use the svn tools installed in this path') parser.add_option('--exclusive-wc-locks', action='store_true', Modified: subversion/branches/authzperf/subversion/tests/cmdline/switch_tests.py URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/tests/cmdline/switch_tests.py?rev=1649205&r1=1649204&r2=1649205&view=diff ============================================================================== --- subversion/branches/authzperf/subversion/tests/cmdline/switch_tests.py (original) +++ subversion/branches/authzperf/subversion/tests/cmdline/switch_tests.py Sat Jan 3 14:00:41 2015 @@ -2860,6 +2860,49 @@ def switch_keywords(sbox): None, expected_disk, expected_status, None, None, None, None, None) +@Issue(4524) +def switch_moves(sbox): + "switch moves on wc checkpoint" + + sbox.build() + + sbox.simple_move('A/B', 'B') + sbox.simple_rm('A') + + branch_url = sbox.repo_url + '/branch' + + svntest.actions.run_and_verify_svn(None, None, [], + 'cp', sbox.wc_dir, branch_url, + '-m', '') + + expected_disk = svntest.wc.State('', { + 'B/E/alpha' : Item(contents="This is the file 'alpha'.\n"), + 'B/E/beta' : Item(contents="This is the file 'beta'.\n"), + 'B/lambda' : Item(contents="This is the file 'lambda'.\n"), + 'B/F' : Item(), + 'iota' : Item(contents="This is the file 'iota'.\n"), + }) + + expected_status = svntest.wc.State(sbox.wc_dir, { + '' : Item(status=' ', wc_rev='2'), + 'B' : Item(status='R ', copied='+', treeconflict='C', wc_rev='-'), + 'B/lambda' : Item(status=' ', copied='+', wc_rev='-'), + 'B/F' : Item(status=' ', copied='+', wc_rev='-'), + 'B/E' : Item(status=' ', copied='+', wc_rev='-'), + 'B/E/beta' : Item(status=' ', copied='+', wc_rev='-'), + 'B/E/alpha' : Item(status=' ', copied='+', wc_rev='-'), + 'A' : Item(status='! ', treeconflict='C'), + 'iota' : Item(status=' ', wc_rev='2'), + }) + + # In Subversion 1.8 this scenario causes an Sqlite row not found error. + # It would be nice if we could handle the tree conflict more intelligent, as + # the working copy matches the incomming change. + svntest.actions.run_and_verify_switch(sbox.wc_dir, sbox.ospath(''), branch_url, + None, expected_disk, expected_status, + None, None, None, None, None) + + ######################################################################## # Run the tests @@ -2903,6 +2946,7 @@ test_list = [ None, switch_to_spaces, switch_across_replacement, switch_keywords, + switch_moves, ] if __name__ == '__main__': Modified: subversion/branches/authzperf/subversion/tests/libsvn_fs/fs-test.c URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/tests/libsvn_fs/fs-test.c?rev=1649205&r1=1649204&r2=1649205&view=diff ============================================================================== --- subversion/branches/authzperf/subversion/tests/libsvn_fs/fs-test.c (original) +++ subversion/branches/authzperf/subversion/tests/libsvn_fs/fs-test.c Sat Jan 3 14:00:41 2015 @@ -41,6 +41,7 @@ #include "svn_private_config.h" #include "private/svn_fs_util.h" #include "private/svn_fs_private.h" +#include "private/svn_fspath.h" #include "../svn_test_fs.h" @@ -207,24 +208,35 @@ reopen_trivial_transaction(const svn_tes { svn_fs_t *fs; svn_fs_txn_t *txn; + svn_fs_root_t *root; const char *txn_name; apr_pool_t *subpool = svn_pool_create(pool); SVN_ERR(svn_test__create_fs(&fs, "test-repo-reopen-trivial-txn", opts, pool)); - /* Begin a new transaction that is based on revision 0. */ + /* Create a first transaction - we don't want that one to reopen. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); + + /* Begin a second transaction that is based on revision 0. */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); /* Don't use the subpool, txn_name must persist beyond the current txn */ SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool)); + /* Create a third transaction - we don't want that one to reopen. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); + /* Close the transaction. */ svn_pool_clear(subpool); /* Reopen the transaction by name */ SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, subpool)); + /* Does it have the same name? */ + SVN_ERR(svn_fs_txn_root(&root, txn, subpool)); + SVN_TEST_STRING_ASSERT(svn_fs_txn_root_name(root, subpool), txn_name); + /* Close the transaction ... again. */ svn_pool_destroy(subpool); @@ -1126,6 +1138,8 @@ basic_commit(const svn_test_opts_t *opts /* Create the greek tree. */ SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); + SVN_TEST_ASSERT(svn_fs_is_txn_root(txn_root)); + SVN_TEST_ASSERT(!svn_fs_is_revision_root(txn_root)); /* Commit it. */ SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, pool)); @@ -1139,6 +1153,8 @@ basic_commit(const svn_test_opts_t *opts /* Get root of the revision */ SVN_ERR(svn_fs_revision_root(&revision_root, fs, after_rev, pool)); + SVN_TEST_ASSERT(!svn_fs_is_txn_root(revision_root)); + SVN_TEST_ASSERT(svn_fs_is_revision_root(revision_root)); /* Check the tree. */ SVN_ERR(svn_test__check_greek_tree(revision_root, pool)); @@ -1580,6 +1596,10 @@ merging_commit(const svn_test_opts_t *op SVN_ERR(svn_fs_make_file(txn_root, "theta", pool)); SVN_ERR(svn_test__set_file_contents (txn_root, "theta", "This is another file 'theta'.\n", pool)); + + /* TXN must actually be based upon revisions[4] (instead of HEAD). */ + SVN_TEST_ASSERT(svn_fs_txn_base_revision(txn) == revisions[4]); + SVN_ERR(test_commit_txn(&failed_rev, txn, "/theta", pool)); SVN_ERR(svn_fs_abort_txn(txn, pool)); @@ -1603,6 +1623,9 @@ merging_commit(const svn_test_opts_t *op SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); SVN_ERR(svn_fs_delete(txn_root, "A/D/H", pool)); + /* TXN must actually be based upon revisions[1] (instead of HEAD). */ + SVN_TEST_ASSERT(svn_fs_txn_base_revision(txn) == revisions[1]); + /* We used to create the revision like this before fixing issue #2751 -- Directory prop mods reverted in overlapping commits scenario. @@ -4171,6 +4194,12 @@ check_related(const svn_test_opts_t *opt { "E", 7 }, { "E", 8 }, { "F", 9 }, { "F", 10 } }; + /* Latest revision that touched the respective path. */ + struct path_rev_t latest_changes[6] = { + { "A", 4 }, { "B", 6 }, { "C", 6 }, + { "D", 7 }, { "E", 8 }, { "F", 10 } + }; + int related_matrix[16][16] = { /* A1 ... F10 across the top here*/ { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0 }, /* A1 */ @@ -4200,14 +4229,16 @@ check_related(const svn_test_opts_t *opt struct path_rev_t pr2 = path_revs[j]; const svn_fs_id_t *id1, *id2; int related = 0; + svn_fs_node_relation_t relation; + svn_fs_root_t *rev_root1, *rev_root2; /* Get the ID for the first path/revision combination. */ - SVN_ERR(svn_fs_revision_root(&rev_root, fs, pr1.rev, subpool)); - SVN_ERR(svn_fs_node_id(&id1, rev_root, pr1.path, subpool)); + SVN_ERR(svn_fs_revision_root(&rev_root1, fs, pr1.rev, subpool)); + SVN_ERR(svn_fs_node_id(&id1, rev_root1, pr1.path, subpool)); /* Get the ID for the second path/revision combination. */ - SVN_ERR(svn_fs_revision_root(&rev_root, fs, pr2.rev, subpool)); - SVN_ERR(svn_fs_node_id(&id2, rev_root, pr2.path, subpool)); + SVN_ERR(svn_fs_revision_root(&rev_root2, fs, pr2.rev, subpool)); + SVN_ERR(svn_fs_node_id(&id2, rev_root2, pr2.path, subpool)); /* <exciting> Now, run the relationship check! </exciting> */ related = svn_fs_check_related(id1, id2) ? 1 : 0; @@ -4230,9 +4261,74 @@ check_related(const svn_test_opts_t *opt pr1.path, (int)pr1.rev, pr2.path, (int)pr2.rev); } + /* Asking directly, i.e. without involving the noderev IDs as + * an intermediate, should yield the same results. */ + SVN_ERR(svn_fs_node_relation(&relation, rev_root1, pr1.path, + rev_root2, pr2.path, subpool)); + if (i == j) + { + /* Identical note. */ + if (!related || relation != svn_fs_node_same) + { + return svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "expected '%s:%d' to be the same as '%s:%d';" + " it was not", + pr1.path, (int)pr1.rev, pr2.path, (int)pr2.rev); + } + } + else if (related && relation != svn_fs_node_common_ancestor) + { + return svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "expected '%s:%d' to have a common ancestor with '%s:%d';" + " it had not", + pr1.path, (int)pr1.rev, pr2.path, (int)pr2.rev); + } + else if (!related && relation != svn_fs_node_unrelated) + { + return svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "expected '%s:%d' to not be related to '%s:%d'; it was", + pr1.path, (int)pr1.rev, pr2.path, (int)pr2.rev); + } + svn_pool_clear(subpool); } /* for ... */ } /* for ... */ + + /* Verify that the noderevs stay the same after their last change. */ + for (i = 0; i < 6; ++i) + { + const char *path = latest_changes[i].path; + svn_revnum_t latest = latest_changes[i].rev; + svn_fs_root_t *latest_root; + svn_revnum_t rev; + svn_fs_node_relation_t relation; + + /* FS root of the latest change. */ + svn_pool_clear(subpool); + SVN_ERR(svn_fs_revision_root(&latest_root, fs, latest, subpool)); + + /* All future revisions. */ + for (rev = latest + 1; rev <= 10; ++rev) + { + /* Query their noderev relationship to the latest change. */ + SVN_ERR(svn_fs_revision_root(&rev_root, fs, rev, subpool)); + SVN_ERR(svn_fs_node_relation(&relation, latest_root, path, + rev_root, path, subpool)); + + /* They shall use the same noderevs */ + if (relation != svn_fs_node_same) + { + return svn_error_createf + (SVN_ERR_TEST_FAILED, NULL, + "expected '%s:%d' to be the same as '%s:%d';" + " it was not", + path, (int)latest, path, (int)rev); + } + } /* for ... */ + } /* for ... */ } /* Destroy the subpool. */ @@ -4320,22 +4416,49 @@ branch_test(const svn_test_opts_t *opts, } +/* Verify that file FILENAME under ROOT has the same contents checksum + * as CONTENTS when comparing the checksums of the given TYPE. + * Use POOL for temporary allocations. */ +static svn_error_t * +verify_file_checksum(svn_stringbuf_t *contents, + svn_fs_root_t *root, + const char *filename, + svn_checksum_kind_t type, + apr_pool_t *pool) +{ + svn_checksum_t *expected_checksum, *actual_checksum; + + /* Write a file, compare the repository's idea of its checksum + against our idea of its checksum. They should be the same. */ + SVN_ERR(svn_checksum(&expected_checksum, type, contents->data, + contents->len, pool)); + SVN_ERR(svn_fs_file_checksum(&actual_checksum, type, root, filename, TRUE, + pool)); + if (!svn_checksum_match(expected_checksum, actual_checksum)) + return svn_error_createf + (SVN_ERR_FS_GENERAL, NULL, + "verify-checksum: checksum mismatch:\n" + " expected: %s\n" + " actual: %s\n", + svn_checksum_to_cstring(expected_checksum, pool), + svn_checksum_to_cstring(actual_checksum, pool)); + + return SVN_NO_ERROR; +} + static svn_error_t * verify_checksum(const svn_test_opts_t *opts, apr_pool_t *pool) { svn_fs_t *fs; svn_fs_txn_t *txn; - svn_fs_root_t *txn_root; + svn_fs_root_t *txn_root, *rev_root; svn_stringbuf_t *str; - svn_checksum_t *expected_checksum, *actual_checksum; + svn_revnum_t rev; /* Write a file, compare the repository's idea of its checksum against our idea of its checksum. They should be the same. */ - str = svn_stringbuf_create("My text editor charges me rent.", pool); - SVN_ERR(svn_checksum(&expected_checksum, svn_checksum_md5, str->data, - str->len, pool)); SVN_ERR(svn_test__create_fs(&fs, "test-repo-verify-checksum", opts, pool)); @@ -4343,17 +4466,20 @@ verify_checksum(const svn_test_opts_t *o SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); SVN_ERR(svn_fs_make_file(txn_root, "fact", pool)); SVN_ERR(svn_test__set_file_contents(txn_root, "fact", str->data, pool)); - SVN_ERR(svn_fs_file_checksum(&actual_checksum, svn_checksum_md5, txn_root, - "fact", TRUE, pool)); - if (!svn_checksum_match(expected_checksum, actual_checksum)) - return svn_error_createf - (SVN_ERR_FS_GENERAL, NULL, - "verify-checksum: checksum mismatch:\n" - " expected: %s\n" - " actual: %s\n", - svn_checksum_to_cstring(expected_checksum, pool), - svn_checksum_to_cstring(actual_checksum, pool)); + /* Do it for the txn. */ + SVN_ERR(verify_file_checksum(str, txn_root, "fact", svn_checksum_md5, + pool)); + SVN_ERR(verify_file_checksum(str, txn_root, "fact", svn_checksum_sha1, + pool)); + + /* Do it again - this time for the revision. */ + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + SVN_ERR(svn_fs_revision_root(&rev_root, fs, rev, pool)); + SVN_ERR(verify_file_checksum(str, rev_root, "fact", svn_checksum_md5, + pool)); + SVN_ERR(verify_file_checksum(str, rev_root, "fact", svn_checksum_sha1, + pool)); return SVN_NO_ERROR; } @@ -5403,6 +5529,1172 @@ reopen_modify(const svn_test_opts_t *opt #endif } +static svn_error_t * +upgrade_while_committing(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_revnum_t head_rev = 0; + svn_fs_root_t *root; + svn_fs_txn_t *txn1, *txn2; + const char *fs_path; + apr_hash_t *fs_config = apr_hash_make(pool); + + /* Bail (with success) on known-untestable scenarios */ + if (strcmp(opts->fs_type, "fsfs") != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "this will test FSFS repositories only"); + + if (opts->server_minor_version && (opts->server_minor_version < 6)) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "pre-1.6 SVN doesn't support FSFS packing"); + + /* Create test repository with greek tree. */ + fs_path = "test-upgrade-while-committing"; + + svn_hash_sets(fs_config, SVN_FS_CONFIG_COMPATIBLE_VERSION, "1.7"); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_SHARD_SIZE, "2"); + SVN_ERR(svn_test__create_fs2(&fs, fs_path, opts, fs_config, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn1, fs, head_rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn1, pool)); + SVN_ERR(svn_test__create_greek_tree(root, pool)); + SVN_ERR(test_commit_txn(&head_rev, txn1, NULL, pool)); + + /* Create txn with changes. */ + SVN_ERR(svn_fs_begin_txn(&txn1, fs, head_rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn1, pool)); + SVN_ERR(svn_fs_make_dir(root, "/foo", pool)); + + /* Upgrade filesystem, but keep existing svn_fs_t object. */ + SVN_ERR(svn_fs_upgrade(fs_path, pool)); + + /* Creating a new txn for the old svn_fs_t should not fail. */ + SVN_ERR(svn_fs_begin_txn(&txn2, fs, head_rev, pool)); + + /* Committing the already existing txn should not fail. */ + SVN_ERR(test_commit_txn(&head_rev, txn1, NULL, pool)); + + /* Verify filesystem content. */ + SVN_ERR(svn_fs_verify(fs_path, NULL, 0, SVN_INVALID_REVNUM, NULL, NULL, + NULL, NULL, pool)); + + return SVN_NO_ERROR; +} + +/* Utility method for test_paths_changed. Verify that REV in FS changes + * exactly one path and that that change is a property change. Expect + * the MERGEINFO_MOD flag of the change to have the given value. + */ +static svn_error_t * +verify_root_prop_change(svn_fs_t *fs, + svn_revnum_t rev, + svn_tristate_t mergeinfo_mod, + apr_pool_t *pool) +{ + svn_fs_path_change2_t *change; + svn_fs_root_t *root; + apr_hash_t *changes; + + SVN_ERR(svn_fs_revision_root(&root, fs, rev, pool)); + SVN_ERR(svn_fs_paths_changed2(&changes, root, pool)); + SVN_TEST_ASSERT(apr_hash_count(changes) == 1); + change = svn_hash_gets(changes, "/"); + + SVN_TEST_ASSERT(change->node_rev_id); + SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_modify); + SVN_TEST_ASSERT( change->node_kind == svn_node_dir + || change->node_kind == svn_node_unknown); + SVN_TEST_ASSERT(change->text_mod == FALSE); + SVN_TEST_ASSERT(change->prop_mod == TRUE); + + if (change->copyfrom_known) + { + SVN_TEST_ASSERT(change->copyfrom_rev == SVN_INVALID_REVNUM); + SVN_TEST_ASSERT(change->copyfrom_path == NULL); + } + + SVN_TEST_ASSERT(change->mergeinfo_mod == mergeinfo_mod); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_paths_changed(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_revnum_t head_rev = 0; + svn_fs_root_t *root; + svn_fs_txn_t *txn; + const char *fs_path; + apr_hash_t *changes; + svn_boolean_t has_mergeinfo_mod = FALSE; + apr_hash_index_t *hi; + int i; + + /* The "mergeinfo_mod flag will say "unknown" until recently. */ + if ( strcmp(opts->fs_type, "bdb") != 0 + && (!opts->server_minor_version || (opts->server_minor_version >= 9))) + has_mergeinfo_mod = TRUE; + + /* Create test repository with greek tree. */ + fs_path = "test-paths-changed"; + + SVN_ERR(svn_test__create_fs2(&fs, fs_path, opts, NULL, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, head_rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_test__create_greek_tree(root, pool)); + SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool)); + + /* Create txns with various prop changes. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, head_rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_fs_change_node_prop(root, "/", "propname", + svn_string_create("propval", pool), pool)); + SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, head_rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_fs_change_node_prop(root, "/", "svn:mergeinfo", + svn_string_create("/: 1\n", pool), pool)); + SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool)); + + /* Verify changed path lists. */ + + /* Greek tree creation rev. */ + SVN_ERR(svn_fs_revision_root(&root, fs, head_rev - 2, pool)); + SVN_ERR(svn_fs_paths_changed2(&changes, root, pool)); + + /* Reports all paths? */ + for (i = 0; svn_test__greek_tree_nodes[i].path; ++i) + { + const char *path + = svn_fspath__canonicalize(svn_test__greek_tree_nodes[i].path, pool); + + SVN_TEST_ASSERT(svn_hash_gets(changes, path)); + } + + SVN_TEST_ASSERT(apr_hash_count(changes) == i); + + /* Verify per-path info. */ + for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi)) + { + svn_fs_path_change2_t *change = apr_hash_this_val(hi); + + SVN_TEST_ASSERT(change->node_rev_id); + SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_add); + SVN_TEST_ASSERT( change->node_kind == svn_node_file + || change->node_kind == svn_node_dir + || change->node_kind == svn_node_unknown); + + if (change->node_kind != svn_node_unknown) + SVN_TEST_ASSERT(change->text_mod == ( change->node_kind + == svn_node_file)); + + SVN_TEST_ASSERT(change->prop_mod == FALSE); + + if (change->copyfrom_known) + { + SVN_TEST_ASSERT(change->copyfrom_rev == SVN_INVALID_REVNUM); + SVN_TEST_ASSERT(change->copyfrom_path == NULL); + } + + if (has_mergeinfo_mod) + SVN_TEST_ASSERT(change->mergeinfo_mod == svn_tristate_false); + else + SVN_TEST_ASSERT(change->mergeinfo_mod == svn_tristate_unknown); + } + + /* Propset rev. */ + SVN_ERR(verify_root_prop_change(fs, head_rev - 1, + has_mergeinfo_mod ? svn_tristate_false + : svn_tristate_unknown, + pool)); + + /* Mergeinfo set rev. */ + SVN_ERR(verify_root_prop_change(fs, head_rev, + has_mergeinfo_mod ? svn_tristate_true + : svn_tristate_unknown, + pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_delete_replaced_paths_changed(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_revnum_t head_rev = 0; + svn_fs_root_t *root; + svn_fs_txn_t *txn; + const char *fs_path; + apr_hash_t *changes; + svn_fs_path_change2_t *change; + const svn_fs_id_t *file_id; + + /* Create test repository with greek tree. */ + fs_path = "test-delete-replace-paths-changed"; + + SVN_ERR(svn_test__create_fs2(&fs, fs_path, opts, NULL, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, head_rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_test__create_greek_tree(root, pool)); + SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool)); + + /* Create that replaces a file with a folder and then deletes that + * replacement. Start with the deletion. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, head_rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_fs_delete(root, "/iota", pool)); + + /* The change list should now report a deleted file. */ + SVN_ERR(svn_fs_paths_changed2(&changes, root, pool)); + change = svn_hash_gets(changes, "/iota"); + file_id = change->node_rev_id; + SVN_TEST_ASSERT( change->node_kind == svn_node_file + || change->node_kind == svn_node_unknown); + SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_delete); + + /* Add a replacement. */ + SVN_ERR(svn_fs_make_dir(root, "/iota", pool)); + + /* The change list now reports a replacement by a directory. */ + SVN_ERR(svn_fs_paths_changed2(&changes, root, pool)); + change = svn_hash_gets(changes, "/iota"); + SVN_TEST_ASSERT( change->node_kind == svn_node_dir + || change->node_kind == svn_node_unknown); + SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_replace); + SVN_TEST_ASSERT(svn_fs_compare_ids(change->node_rev_id, file_id) != 0); + + /* Delete the replacement again. */ + SVN_ERR(svn_fs_delete(root, "/iota", pool)); + + /* The change list should now be reported as a deleted file again. */ + SVN_ERR(svn_fs_paths_changed2(&changes, root, pool)); + change = svn_hash_gets(changes, "/iota"); + SVN_TEST_ASSERT( change->node_kind == svn_node_file + || change->node_kind == svn_node_unknown); + SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_delete); + SVN_TEST_ASSERT(svn_fs_compare_ids(change->node_rev_id, file_id) == 0); + + /* Finally, commit the change. */ + SVN_ERR(test_commit_txn(&head_rev, txn, NULL, pool)); + + /* The committed revision should still report the same change. */ + SVN_ERR(svn_fs_revision_root(&root, fs, head_rev, pool)); + SVN_ERR(svn_fs_paths_changed2(&changes, root, pool)); + change = svn_hash_gets(changes, "/iota"); + SVN_TEST_ASSERT( change->node_kind == svn_node_file + || change->node_kind == svn_node_unknown); + SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_delete); + + return SVN_NO_ERROR; +} + +/* Get rid of transaction NAME in FS. This function deals with backend- + * specific behavior as permitted by the API. */ +static svn_error_t * +cleanup_txn(svn_fs_t *fs, + const char *name, + apr_pool_t *scratch_pool) +{ + /* Get rid of the txns one at a time. */ + svn_error_t *err = svn_fs_purge_txn(fs, name, scratch_pool); + + /* Some backends (BDB) don't support purging transactions that have never + * seen an abort or commit attempt. Simply abort those txns. */ + if (err && err->apr_err == SVN_ERR_FS_TRANSACTION_NOT_DEAD) + { + svn_fs_txn_t *txn; + svn_error_clear(err); + err = SVN_NO_ERROR; + + SVN_ERR(svn_fs_open_txn(&txn, fs, name, scratch_pool)); + SVN_ERR(svn_fs_abort_txn(txn, scratch_pool)); + + /* Should be gone now ... */ + SVN_TEST_ASSERT_ERROR(svn_fs_open_txn(&txn, fs, name, scratch_pool), + SVN_ERR_FS_NO_SUCH_TRANSACTION); + } + + return svn_error_trace(err); +} + +/* Make sure we get txn lists correctly. */ +static svn_error_t * +purge_txn_test(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_fs_txn_t *txn; + const char *name1, *name2; + apr_array_header_t *txn_list; + apr_pool_t *subpool = svn_pool_create(pool); + + SVN_ERR(svn_test__create_fs(&fs, "test-repo-purge-txn", + opts, pool)); + + /* Begin a new transaction, get its name (in the top pool), close it. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); + SVN_ERR(svn_fs_txn_name(&name1, txn, pool)); + + /* Begin *another* transaction, get its name (in the top pool), close it. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); + SVN_ERR(svn_fs_txn_name(&name2, txn, pool)); + svn_pool_clear(subpool); + + /* Get rid of the txns one at a time. */ + SVN_ERR(cleanup_txn(fs, name1, pool)); + + /* There should be exactly one left. */ + SVN_ERR(svn_fs_list_transactions(&txn_list, fs, pool)); + + /* Check the list. It should have *exactly* one entry. */ + SVN_TEST_ASSERT( txn_list->nelts == 1 + && !strcmp(name2, APR_ARRAY_IDX(txn_list, 0, const char *))); + + /* Get rid of the other txn as well. */ + SVN_ERR(cleanup_txn(fs, name2, pool)); + + /* There should be exactly one left. */ + SVN_ERR(svn_fs_list_transactions(&txn_list, fs, pool)); + + /* Check the list. It should have no entries. */ + SVN_TEST_ASSERT(txn_list->nelts == 0); + + return SVN_NO_ERROR; +} + +static svn_error_t * +compare_contents(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root, *root1, *root2; + const char *original = "original contents"; + svn_revnum_t rev; + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + + /* Two similar but different texts that yield the same MD5 digest. */ + const char *evil_text1 + = "\xd1\x31\xdd\x02\xc5\xe6\xee\xc4\x69\x3d\x9a\x06\x98\xaf\xf9\x5c" + "\x2f\xca\xb5\x87\x12\x46\x7e\xab\x40\x04\x58\x3e\xb8\xfb\x7f\x89" + "\x55\xad\x34\x06\x09\xf4\xb3\x02\x83\xe4\x88\x83\x25\x71\x41\x5a" + "\x08\x51\x25\xe8\xf7\xcd\xc9\x9f\xd9\x1d\xbd\xf2\x80\x37\x3c\x5b" + "\xd8\x82\x3e\x31\x56\x34\x8f\x5b\xae\x6d\xac\xd4\x36\xc9\x19\xc6" + "\xdd\x53\xe2\xb4\x87\xda\x03\xfd\x02\x39\x63\x06\xd2\x48\xcd\xa0" + "\xe9\x9f\x33\x42\x0f\x57\x7e\xe8\xce\x54\xb6\x70\x80\xa8\x0d\x1e" + "\xc6\x98\x21\xbc\xb6\xa8\x83\x93\x96\xf9\x65\x2b\x6f\xf7\x2a\x70"; + const char *evil_text2 + = "\xd1\x31\xdd\x02\xc5\xe6\xee\xc4\x69\x3d\x9a\x06\x98\xaf\xf9\x5c" + "\x2f\xca\xb5\x07\x12\x46\x7e\xab\x40\x04\x58\x3e\xb8\xfb\x7f\x89" + "\x55\xad\x34\x06\x09\xf4\xb3\x02\x83\xe4\x88\x83\x25\xf1\x41\x5a" + "\x08\x51\x25\xe8\xf7\xcd\xc9\x9f\xd9\x1d\xbd\x72\x80\x37\x3c\x5b" + "\xd8\x82\x3e\x31\x56\x34\x8f\x5b\xae\x6d\xac\xd4\x36\xc9\x19\xc6" + "\xdd\x53\xe2\x34\x87\xda\x03\xfd\x02\x39\x63\x06\xd2\x48\xcd\xa0" + "\xe9\x9f\x33\x42\x0f\x57\x7e\xe8\xce\x54\xb6\x70\x80\x28\x0d\x1e" + "\xc6\x98\x21\xbc\xb6\xa8\x83\x93\x96\xf9\x65\xab\x6f\xf7\x2a\x70"; + svn_checksum_t *checksum1, *checksum2; + + /* (path, rev) pairs to compare plus the expected API return values */ + struct + { + svn_revnum_t rev1; + const char *path1; + svn_revnum_t rev2; + const char *path2; + + svn_boolean_t different; /* result of svn_fs_*_different */ + svn_tristate_t changed; /* result of svn_fs_*_changed */ + } to_compare[] = + { + /* same representation */ + { 1, "foo", 2, "foo", FALSE, svn_tristate_false }, + { 1, "foo", 2, "bar", FALSE, svn_tristate_false }, + { 2, "foo", 2, "bar", FALSE, svn_tristate_false }, + + /* different content but MD5 check is not reliable */ + { 3, "foo", 3, "bar", TRUE, svn_tristate_true }, + + /* different contents */ + { 1, "foo", 3, "bar", TRUE, svn_tristate_true }, + { 1, "foo", 3, "foo", TRUE, svn_tristate_true }, + { 3, "foo", 4, "bar", TRUE, svn_tristate_true }, + { 3, "foo", 4, "bar", TRUE, svn_tristate_true }, + { 2, "bar", 3, "bar", TRUE, svn_tristate_true }, + { 3, "bar", 4, "bar", TRUE, svn_tristate_true }, + + /* variations on the same theme: same content, possibly different rep */ + { 4, "foo", 4, "bar", FALSE, svn_tristate_unknown }, + { 1, "foo", 4, "bar", FALSE, svn_tristate_unknown }, + { 2, "foo", 4, "bar", FALSE, svn_tristate_unknown }, + { 1, "foo", 4, "foo", FALSE, svn_tristate_unknown }, + { 2, "foo", 4, "foo", FALSE, svn_tristate_unknown }, + { 2, "bar", 4, "bar", FALSE, svn_tristate_unknown }, + + /* EOL */ + { 0 }, + }; + + /* Same same, but different. + * Just checking that we actually have an MD5 collision. */ + SVN_ERR(svn_checksum(&checksum1, svn_checksum_md5, evil_text1, + strlen(evil_text1), pool)); + SVN_ERR(svn_checksum(&checksum2, svn_checksum_md5, evil_text2, + strlen(evil_text2), pool)); + SVN_TEST_ASSERT(svn_checksum_match(checksum1, checksum1)); + SVN_TEST_ASSERT(strcmp(evil_text1, evil_text2)); + + /* Now, build up our test repo. */ + SVN_ERR(svn_test__create_fs(&fs, "test-repo-compare-contents", + opts, pool)); + + /* Rev 1: create a file. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, iterpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool)); + SVN_ERR(svn_fs_make_file(txn_root, "foo", iterpool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "foo", original, iterpool)); + SVN_ERR(svn_fs_change_node_prop(txn_root, "foo", "prop", + svn_string_create(original, iterpool), + iterpool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, iterpool)); + SVN_TEST_ASSERT(rev == 1); + svn_pool_clear(iterpool); + + /* Rev 2: copy that file. */ + SVN_ERR(svn_fs_revision_root(&root1, fs, rev, iterpool)); + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, iterpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool)); + SVN_ERR(svn_fs_copy(root1, "foo", txn_root, "bar", iterpool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, iterpool)); + SVN_TEST_ASSERT(rev == 2); + svn_pool_clear(iterpool); + + /* Rev 3: modify both files. + * The new contents differs for both files but has the same length and MD5. + */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, iterpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "foo", evil_text1, iterpool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "bar", evil_text2, iterpool)); + SVN_ERR(svn_fs_change_node_prop(txn_root, "foo", "prop", + svn_string_create(evil_text1, iterpool), + iterpool)); + SVN_ERR(svn_fs_change_node_prop(txn_root, "bar", "prop", + svn_string_create(evil_text2, iterpool), + iterpool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, iterpool)); + SVN_TEST_ASSERT(rev == 3); + svn_pool_clear(iterpool); + + /* Rev 4: revert both file contents. + */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, iterpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "foo", original, iterpool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "bar", original, iterpool)); + SVN_ERR(svn_fs_change_node_prop(txn_root, "foo", "prop", + svn_string_create(original, iterpool), + iterpool)); + SVN_ERR(svn_fs_change_node_prop(txn_root, "bar", "prop", + svn_string_create(original, iterpool), + iterpool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, iterpool)); + SVN_TEST_ASSERT(rev == 4); + svn_pool_clear(iterpool); + + /* Perform all comparisons listed in TO_COMPARE. */ + for (i = 0; to_compare[i].rev1 > 0; ++i) + { + svn_boolean_t text_different; + svn_boolean_t text_changed; + svn_boolean_t props_different; + svn_boolean_t props_changed; + + svn_pool_clear(iterpool); + SVN_ERR(svn_fs_revision_root(&root1, fs, to_compare[i].rev1, iterpool)); + SVN_ERR(svn_fs_revision_root(&root2, fs, to_compare[i].rev2, iterpool)); + + /* Compare node texts. */ + SVN_ERR(svn_fs_contents_different(&text_different, + root1, to_compare[i].path1, + root2, to_compare[i].path2, + iterpool)); + SVN_ERR(svn_fs_contents_changed(&text_changed, + root1, to_compare[i].path1, + root2, to_compare[i].path2, + iterpool)); + + /* Compare properties. */ + SVN_ERR(svn_fs_props_different(&props_different, + root1, to_compare[i].path1, + root2, to_compare[i].path2, + iterpool)); + SVN_ERR(svn_fs_props_changed(&props_changed, + root1, to_compare[i].path1, + root2, to_compare[i].path2, + iterpool)); + + /* Check results. */ + SVN_TEST_ASSERT(text_different == to_compare[i].different); + SVN_TEST_ASSERT(props_different == to_compare[i].different); + + switch (to_compare[i].changed) + { + case svn_tristate_true: + SVN_TEST_ASSERT(text_changed); + SVN_TEST_ASSERT(props_changed); + break; + + case svn_tristate_false: + SVN_TEST_ASSERT(!text_changed); + SVN_TEST_ASSERT(!props_changed); + break; + + default: + break; + } + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_path_change_create(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_fs_root_t *root; + const svn_fs_id_t *id; + svn_fs_path_change2_t *change; + + /* Build an empty test repo ... */ + SVN_ERR(svn_test__create_fs(&fs, "test-repo-path-change-create", + opts, pool)); + + /* ... just to give us a valid ID. */ + SVN_ERR(svn_fs_revision_root(&root, fs, 0, pool)); + SVN_ERR(svn_fs_node_id(&id, root, "", pool)); + + /* Do what we came here for. */ + change = svn_fs_path_change2_create(id, svn_fs_path_change_replace, pool); + + SVN_TEST_ASSERT(change); + SVN_TEST_ASSERT(change->node_rev_id == id); + SVN_TEST_ASSERT(change->change_kind == svn_fs_path_change_replace); + + /* All other fields should be "empty" / "unused". */ + SVN_TEST_ASSERT(change->node_kind == svn_node_none); + + SVN_TEST_ASSERT(change->text_mod == FALSE); + SVN_TEST_ASSERT(change->prop_mod == FALSE); + SVN_TEST_ASSERT(change->mergeinfo_mod == svn_tristate_unknown); + + SVN_TEST_ASSERT(change->copyfrom_known == FALSE); + SVN_TEST_ASSERT(change->copyfrom_rev == SVN_INVALID_REVNUM); + SVN_TEST_ASSERT(change->copyfrom_path == NULL); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_node_created_info(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root, *root; + svn_revnum_t rev; + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + + /* Test vectors. */ + struct + { + svn_revnum_t rev; + const char *path; + svn_revnum_t crev; + const char *cpath; + } to_check[] = + { + /* New noderev only upon modification. */ + { 1, "A/B/E/beta", 1, "/A/B/E/beta" }, + { 2, "A/B/E/beta", 1, "/A/B/E/beta" }, + { 3, "A/B/E/beta", 3, "/A/B/E/beta" }, + { 4, "A/B/E/beta", 3, "/A/B/E/beta" }, + + /* Lazily copied node. */ + { 2, "Z/B/E/beta", 1, "/A/B/E/beta" }, + { 3, "Z/B/E/beta", 1, "/A/B/E/beta" }, + { 4, "Z/B/E/beta", 4, "/Z/B/E/beta" }, + + /* Bubble-up upon sub-tree change. */ + { 2, "Z", 2, "/Z" }, + { 3, "Z", 2, "/Z" }, + { 4, "Z", 4, "/Z" }, + + { 0 } + }; + + /* Start with a new repo and the greek tree in rev 1. */ + SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-created-path", + opts, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, iterpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool)); + SVN_ERR(svn_test__create_greek_tree(txn_root, iterpool)); + SVN_ERR(test_commit_txn(&rev, txn, NULL, iterpool)); + svn_pool_clear(iterpool); + + /* r2: copy a subtree */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, iterpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool)); + SVN_ERR(svn_fs_revision_root(&root, fs, rev, iterpool)); + SVN_ERR(svn_fs_copy(root, "A", txn_root, "Z", iterpool)); + SVN_ERR(test_commit_txn(&rev, txn, NULL, iterpool)); + svn_pool_clear(iterpool); + + /* r3: touch node in copy source */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, iterpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "A/B/E/beta", "new", iterpool)); + SVN_ERR(test_commit_txn(&rev, txn, NULL, iterpool)); + svn_pool_clear(iterpool); + + /* r4: touch same relative node in copy target */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, iterpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "Z/B/E/beta", "new", iterpool)); + SVN_ERR(test_commit_txn(&rev, txn, NULL, iterpool)); + svn_pool_clear(iterpool); + + /* Now ask for some 'node created' info. */ + for (i = 0; to_check[i].rev > 0; ++i) + { + svn_revnum_t crev; + const char *cpath; + + svn_pool_clear(iterpool); + + /* Get created path and rev. */ + SVN_ERR(svn_fs_revision_root(&root, fs, to_check[i].rev, iterpool)); + SVN_ERR(svn_fs_node_created_path(&cpath, root, to_check[i].path, + iterpool)); + SVN_ERR(svn_fs_node_created_rev(&crev, root, to_check[i].path, + iterpool)); + + /* Compare the results with our expectations. */ + SVN_TEST_STRING_ASSERT(cpath, to_check[i].cpath); + + if (crev != to_check[i].crev) + return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, + "created rev mismatch for %s@%ld:\n" + " expected '%ld'\n" + " found '%ld", + to_check[i].path, + to_check[i].rev, + to_check[i].crev, + crev); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_print_modules(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + const char *expected, *module_name; + svn_stringbuf_t *modules = svn_stringbuf_create_empty(pool); + + /* Name of the providing module */ + if (strcmp(opts->fs_type, "fsx") == 0) + module_name = "fs_x"; + else if (strcmp(opts->fs_type, "fsfs") == 0) + module_name = "fs_fs"; + else if (strcmp(opts->fs_type, "bdb") == 0) + module_name = "fs_base"; + else + return svn_error_createf(SVN_ERR_TEST_SKIPPED, NULL, + "don't know the module name for %s", + opts->fs_type); + + SVN_ERR(svn_fs_print_modules(modules, pool)); + + /* The requested FS type must be listed amongst the available modules. */ + expected = apr_psprintf(pool, "* %s : ", module_name); + SVN_TEST_ASSERT(strstr(modules->data, expected)); + + return SVN_NO_ERROR; +} + +/* Baton to be used with process_file_contents. */ +typedef struct process_file_contents_baton_t +{ + const char *contents; + svn_boolean_t processed; +} process_file_contents_baton_t; + +/* Implements svn_fs_process_contents_func_t. + * We flag the BATON as "processed" and compare the CONTENTS we've got to + * what we expect through the BATON. + */ +static svn_error_t * +process_file_contents(const unsigned char *contents, + apr_size_t len, + void *baton, + apr_pool_t *scratch_pool) +{ + process_file_contents_baton_t *b = baton; + + SVN_TEST_ASSERT(strlen(b->contents) == len); + SVN_TEST_ASSERT(memcmp(b->contents, contents, len) == 0); + b->processed = TRUE; + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_zero_copy_processsing(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root, *root; + svn_revnum_t rev; + const struct svn_test__tree_entry_t *node; + apr_pool_t *iterpool = svn_pool_create(pool); + + /* Start with a new repo and the greek tree in rev 1. */ + SVN_ERR(svn_test__create_fs(&fs, "test-repo-zero-copy-processing", + opts, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, iterpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool)); + SVN_ERR(svn_test__create_greek_tree(txn_root, iterpool)); + SVN_ERR(test_commit_txn(&rev, txn, NULL, iterpool)); + svn_pool_clear(iterpool); + + SVN_ERR(svn_fs_revision_root(&root, fs, rev, pool)); + + /* Prime the full-text cache by reading all file contents. */ + for (node = svn_test__greek_tree_nodes; node->path; node++) + if (node->contents) + { + svn_stream_t *stream; + svn_pool_clear(iterpool); + + SVN_ERR(svn_fs_file_contents(&stream, root, node->path, iterpool)); + SVN_ERR(svn_stream_copy3(stream, svn_stream_buffered(iterpool), + NULL, NULL, iterpool)); + } + + /* Now, try to get the data directly from cache + * (if the backend has caches). */ + for (node = svn_test__greek_tree_nodes; node->path; node++) + if (node->contents) + { + svn_boolean_t success; + + process_file_contents_baton_t baton; + baton.contents = node->contents; + baton.processed = FALSE; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_fs_try_process_file_contents(&success, root, node->path, + process_file_contents, &baton, + iterpool)); + SVN_TEST_ASSERT(success == baton.processed); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_dir_optimal_order(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root, *root; + svn_revnum_t rev; + apr_hash_t *unordered; + apr_array_header_t *ordered; + int i; + apr_hash_index_t *hi; + + /* Create a new repo and the greek tree in rev 1. */ + SVN_ERR(svn_test__create_fs(&fs, "test-repo-dir-optimal-order", + opts, pool)); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_test__create_greek_tree(txn_root, pool)); + SVN_ERR(test_commit_txn(&rev, txn, NULL, pool)); + + SVN_ERR(svn_fs_revision_root(&root, fs, rev, pool)); + + /* Call the API function we are interested in. */ + SVN_ERR(svn_fs_dir_entries(&unordered, root, "A", pool)); + SVN_ERR(svn_fs_dir_optimal_order(&ordered, root, unordered, pool)); + + /* Verify that all entries are returned. */ + SVN_TEST_ASSERT(ordered->nelts == apr_hash_count(unordered)); + for (hi = apr_hash_first(pool, unordered); hi; hi = apr_hash_next(hi)) + { + svn_boolean_t found = FALSE; + const char *name = apr_hash_this_key(hi); + + /* Compare hash -> array because the array might contain the same + * entry more than once. Since that can't happen in the hash, doing + * it in this direction ensures ORDERED won't contain duplicates. + */ + for (i = 0; !found && i < ordered->nelts; ++i) + { + svn_fs_dirent_t *item = APR_ARRAY_IDX(ordered, i, svn_fs_dirent_t*); + if (strcmp(item->name, name) == 0) + { + found = TRUE; + SVN_TEST_ASSERT(item == apr_hash_this_val(hi)); + } + } + + SVN_TEST_ASSERT(found); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_config_files(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + apr_array_header_t *files; + int i; + const char *repo_name = "test-repo-config-files"; + + /* Create a empty and get its config files. */ + SVN_ERR(svn_test__create_fs(&fs, repo_name, opts, pool)); + SVN_ERR(svn_fs_info_config_files(&files, fs, pool, pool)); + + /* All files should exist and be below the repo. */ + for (i = 0; i < files->nelts; ++i) + { + svn_node_kind_t kind; + const char *path = APR_ARRAY_IDX(files, i, const char*); + + SVN_ERR(svn_io_check_path(path, &kind, pool)); + + SVN_TEST_ASSERT(kind == svn_node_file); + SVN_TEST_ASSERT(svn_dirent_is_ancestor(repo_name, path)); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_delta_file_stream(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root, *root1, *root2; + svn_revnum_t rev; + + const char *old_content = "some content"; + const char *new_content = "some more content"; + svn_txdelta_window_handler_t delta_handler; + void *delta_baton; + svn_txdelta_stream_t *delta_stream; + svn_stringbuf_t *source = svn_stringbuf_create_empty(pool); + svn_stringbuf_t *dest = svn_stringbuf_create_empty(pool); + + /* Create a new repo. */ + SVN_ERR(svn_test__create_fs(&fs, "test-repo-delta-file-stream", + opts, pool)); + + /* Revision 1: create a file. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_file(txn_root, "foo", pool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "foo", old_content, pool)); + SVN_ERR(test_commit_txn(&rev, txn, NULL, pool)); + + /* Revision 2: create a file. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "foo", new_content, pool)); + SVN_ERR(test_commit_txn(&rev, txn, NULL, pool)); + + SVN_ERR(svn_fs_revision_root(&root1, fs, 1, pool)); + SVN_ERR(svn_fs_revision_root(&root2, fs, 2, pool)); + + /* Test 1: Get delta against empty target. */ + SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, + NULL, NULL, root1, "foo", pool)); + + svn_stringbuf_setempty(source); + svn_stringbuf_setempty(dest); + + svn_txdelta_apply(svn_stream_from_stringbuf(source, pool), + svn_stream_from_stringbuf(dest, pool), + NULL, NULL, pool, &delta_handler, &delta_baton); + SVN_ERR(svn_txdelta_send_txstream(delta_stream, + delta_handler, + delta_baton, + pool)); + SVN_TEST_STRING_ASSERT(old_content, dest->data); + + /* Test 2: Get delta against previous version. */ + SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, + root1, "foo", root2, "foo", pool)); + + svn_stringbuf_set(source, old_content); + svn_stringbuf_setempty(dest); + + svn_txdelta_apply(svn_stream_from_stringbuf(source, pool), + svn_stream_from_stringbuf(dest, pool), + NULL, NULL, pool, &delta_handler, &delta_baton); + SVN_ERR(svn_txdelta_send_txstream(delta_stream, + delta_handler, + delta_baton, + pool)); + SVN_TEST_STRING_ASSERT(new_content, dest->data); + + /* Test 3: Get reverse delta. */ + SVN_ERR(svn_fs_get_file_delta_stream(&delta_stream, + root2, "foo", root1, "foo", pool)); + + svn_stringbuf_set(source, new_content); + svn_stringbuf_setempty(dest); + + svn_txdelta_apply(svn_stream_from_stringbuf(source, pool), + svn_stream_from_stringbuf(dest, pool), + NULL, NULL, pool, &delta_handler, &delta_baton); + SVN_ERR(svn_txdelta_send_txstream(delta_stream, + delta_handler, + delta_baton, + pool)); + SVN_TEST_STRING_ASSERT(old_content, dest->data); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_fs_merge(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root, *root0, *root1; + svn_revnum_t rev; + + /* Very basic test for svn_fs_merge because all the other interesting + * cases get tested implicitly with concurrent txn / commit tests. + * This API is just a thin layer around the internal merge function + * and we simply check that the plumbing between them works. + */ + + /* Create a new repo. */ + SVN_ERR(svn_test__create_fs(&fs, "test-repo-fs-merge", + opts, pool)); + SVN_ERR(svn_fs_revision_root(&root0, fs, 0, pool)); + + /* Revision 1: create a file. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_file(txn_root, "foo", pool)); + SVN_ERR(test_commit_txn(&rev, txn, NULL, pool)); + SVN_ERR(svn_fs_revision_root(&root1, fs, rev, pool)); + + /* Merge-able txn: create another file. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_file(txn_root, "bar", pool)); + + SVN_ERR(svn_fs_merge(NULL, root1, "/", txn_root, "/", root0, "/", pool)); + + /* Not merge-able: create the same file file. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_fs_make_file(txn_root, "foo", pool)); + + SVN_TEST_ASSERT_ERROR(svn_fs_merge(NULL, root1, "/", txn_root, "/", root0, + "/", pool), SVN_ERR_FS_CONFLICT); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_fsfs_config_opts(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + apr_hash_t *fs_config; + svn_fs_t *fs; + const svn_fs_info_placeholder_t *fs_info; + const svn_fs_fsfs_info_t *fsfs_info; + + /* Bail (with SKIP) on known-untestable scenarios */ + if (strcmp(opts->fs_type, "fsfs") != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "this will test FSFS repositories only"); + + /* Remove the test directory from previous runs. */ + SVN_ERR(svn_io_remove_dir2("test-fsfs-config-opts", TRUE, NULL, NULL, pool)); + + /* Create the test directory and add it to the test cleanup list. */ + SVN_ERR(svn_io_dir_make("test-fsfs-config-opts", APR_OS_DEFAULT, pool)); + svn_test_add_dir_cleanup("test-fsfs-config-opts"); + + /* Create an FSFS filesystem with default config.*/ + fs_config = apr_hash_make(pool); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, SVN_FS_TYPE_FSFS); + SVN_ERR(svn_fs_create(&fs, "test-fsfs-config-opts/default", fs_config, pool)); + + /* Re-open FS to test the data on disk. */ + SVN_ERR(svn_fs_open2(&fs, "test-fsfs-config-opts/default", NULL, pool, pool)); + + SVN_ERR(svn_fs_info(&fs_info, fs, pool, pool)); + SVN_TEST_STRING_ASSERT(fs_info->fs_type, SVN_FS_TYPE_FSFS); + fsfs_info = (const void *) fs_info; + + /* Check FSFS specific info. Don't check the SHARD_SIZE, because it depends + * on a compile-time constant and may be overridden. */ + SVN_TEST_ASSERT(fsfs_info->log_addressing); + SVN_TEST_ASSERT(fsfs_info->min_unpacked_rev == 0); + + /* Create an FSFS filesystem with custom settings: disabled log-addressing + * and custom shard size (123). */ + fs_config = apr_hash_make(pool); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FS_TYPE, SVN_FS_TYPE_FSFS); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_LOG_ADDRESSING, "false"); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_SHARD_SIZE, "123"); + SVN_ERR(svn_fs_create(&fs, "test-fsfs-config-opts/custom", fs_config, pool)); + + /* Re-open FS to test the data on disk. */ + SVN_ERR(svn_fs_open2(&fs, "test-fsfs-config-opts/custom", NULL, pool, pool)); + + SVN_ERR(svn_fs_info(&fs_info, fs, pool, pool)); + SVN_TEST_STRING_ASSERT(fs_info->fs_type, SVN_FS_TYPE_FSFS); + fsfs_info = (const void *) fs_info; + + /* Check FSFS specific info, including the SHARD_SIZE. */ + SVN_TEST_ASSERT(fsfs_info->log_addressing == FALSE); + SVN_TEST_ASSERT(fsfs_info->shard_size == 123); + SVN_TEST_ASSERT(fsfs_info->min_unpacked_rev == 0); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_txn_pool_lifetime(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + /* Technically, the FS API makes no assumption on the lifetime of logically + * dependent objects. In particular, a txn root object may get destroyed + * after the FS object that it has been built upon. Actual data access is + * implied to be invalid without a valid svn_fs_t. + * + * This test verifies that at least the destruction order of those two + * objects is arbitrary. + */ + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + + /* We will allocate FS in FS_POOL. Using a separate allocator makes + * sure that we actually free the memory when destroying the pool. + */ + apr_allocator_t *fs_allocator = svn_pool_create_allocator(FALSE); + apr_pool_t *fs_pool = apr_allocator_owner_get(fs_allocator); + + /* Create a new repo. */ + SVN_ERR(svn_test__create_fs(&fs, "test-repo-pool-lifetime", + opts, fs_pool)); + + /* Create a TXN_ROOT referencing FS. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + + /* Destroy FS. Depending on the actual allocator implementation, + * these memory pages becomes inaccessible. */ + svn_pool_destroy(fs_pool); + + /* Unclean implementations will try to access FS and may segfault here. */ + svn_fs_close_root(txn_root); + + return SVN_NO_ERROR; +} + +static svn_error_t * +test_modify_txn_being_written(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + /* FSFS has a limitation (and check) that only one file can be + * modified in TXN at time: see r861812 and svn_fs_apply_text() docstring. + * This is regression test for this behavior. */ + svn_fs_t *fs; + svn_fs_txn_t *txn; + const char *txn_name; + svn_fs_root_t *txn_root; + svn_stream_t *foo_contents; + svn_stream_t *bar_contents; + + /* Bail (with success) on known-untestable scenarios */ + if (strcmp(opts->fs_type, SVN_FS_TYPE_FSFS) != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "this will test FSFS repositories only"); + + /* Create a new repo. */ + SVN_ERR(svn_test__create_fs(&fs, "test-modify-txn-being-written", + opts, pool)); + + /* Create a TXN_ROOT referencing FS. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + + /* Make file /foo and open for writing.*/ + SVN_ERR(svn_fs_make_file(txn_root, "/foo", pool)); + SVN_ERR(svn_fs_apply_text(&foo_contents, txn_root, "/foo", NULL, pool)); + + /* Attempt to modify another file '/bar' -- FSFS doesn't allow this. */ + SVN_ERR(svn_fs_make_file(txn_root, "/bar", pool)); + SVN_TEST_ASSERT_ERROR( + svn_fs_apply_text(&bar_contents, txn_root, "/bar", NULL, pool), + SVN_ERR_FS_REP_BEING_WRITTEN); + + /* *Reopen TXN. */ + SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + + /* Check that file '/bar' still cannot be modified */ + SVN_TEST_ASSERT_ERROR( + svn_fs_apply_text(&bar_contents, txn_root, "/bar", NULL, pool), + SVN_ERR_FS_REP_BEING_WRITTEN); + + /* Close file '/foo'. */ + SVN_ERR(svn_stream_close(foo_contents)); + + /* Now file '/bar' can be modified. */ + SVN_ERR(svn_fs_apply_text(&bar_contents, txn_root, "/bar", NULL, pool)); + + return SVN_NO_ERROR; +} /* ------------------------------------------------------------------------ */ @@ -5501,6 +6793,38 @@ static struct svn_test_descriptor_t test SVN_TEST_OPTS_WIMP(reopen_modify, "test reopen and modify txn", "txn_dir_cache fail in FSFS"), + SVN_TEST_OPTS_PASS(upgrade_while_committing, + "upgrade while committing"), + SVN_TEST_OPTS_PASS(test_paths_changed, + "test svn_fs_paths_changed"), + SVN_TEST_OPTS_PASS(test_delete_replaced_paths_changed, + "test deletion after replace in changed paths list"), + SVN_TEST_OPTS_PASS(purge_txn_test, + "test purging transactions"), + SVN_TEST_OPTS_PASS(compare_contents, + "compare contents of different nodes"), + SVN_TEST_OPTS_PASS(test_path_change_create, + "test svn_fs_path_change2_create"), + SVN_TEST_OPTS_PASS(test_node_created_info, + "test FS 'node created' info"), + SVN_TEST_OPTS_PASS(test_print_modules, + "test FS module listing"), + SVN_TEST_OPTS_PASS(test_zero_copy_processsing, + "test zero copy file processing"), + SVN_TEST_OPTS_PASS(test_dir_optimal_order, + "test svn_fs_dir_optimal_order"), + SVN_TEST_OPTS_PASS(test_config_files, + "get configuration files"), + SVN_TEST_OPTS_PASS(test_delta_file_stream, + "get a delta stream on a file"), + SVN_TEST_OPTS_PASS(test_fs_merge, + "get merging txns with newer revisions"), + SVN_TEST_OPTS_PASS(test_fsfs_config_opts, + "test creating FSFS repository with different opts"), + SVN_TEST_OPTS_PASS(test_txn_pool_lifetime, + "test pool lifetime dependencies with txn roots"), + SVN_TEST_OPTS_PASS(test_modify_txn_being_written, + "test modify txn being written in FSFS"), SVN_TEST_NULL }; Propchange: subversion/branches/authzperf/subversion/tests/libsvn_fs_fs/ ------------------------------------------------------------------------------ --- svn:ignore (original) +++ svn:ignore Sat Jan 3 14:00:41 2015 @@ -4,7 +4,7 @@ Release test-repo-* upgrade_* fs-pack-test -fs-fs-pack-test +fs-fs-*-test test-get-set-revprop-packed-fs get_set_multiple_huge_revprops_packed_fs *.o Modified: subversion/branches/authzperf/subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c?rev=1649205&r1=1649204&r2=1649205&view=diff ============================================================================== --- subversion/branches/authzperf/subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c (original) +++ subversion/branches/authzperf/subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c Sat Jan 3 14:00:41 2015 @@ -1,4 +1,4 @@ -/* fs-pack-test.c --- tests for the filesystem +/* fs-fs-pack-test.c --- tests for the FSFS filesystem * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one @@ -33,7 +33,6 @@ #include "svn_props.h" #include "svn_fs.h" #include "private/svn_string_private.h" -#include "private/svn_string_private.h" #include "../svn_test_fs.h" @@ -51,65 +50,6 @@ ignore_fs_warnings(void *baton, svn_erro return; } -/* Write the format number and maximum number of files per directory - to a new format file in PATH, overwriting a previously existing - file. Use POOL for temporary allocation. - - (This implementation is largely stolen from libsvn_fs_fs/fs_fs.c.) */ -static svn_error_t * -write_format(const char *path, - int format, - int max_files_per_dir, - apr_pool_t *pool) -{ - const char *contents; - - path = svn_dirent_join(path, "format", pool); - - if (format >= SVN_FS_FS__MIN_LAYOUT_FORMAT_OPTION_FORMAT) - { - if (format >= SVN_FS_FS__MIN_LOG_ADDRESSING_FORMAT) - { - if (max_files_per_dir) - contents = apr_psprintf(pool, - "%d\n" - "layout sharded %d\n" - "addressing logical 0\n", - format, max_files_per_dir); - else - /* linear layouts never use logical addressing */ - contents = apr_psprintf(pool, - "%d\n" - "layout linear\n" - "addressing physical\n", - format); - } - else - { - if (max_files_per_dir) - contents = apr_psprintf(pool, - "%d\n" - "layout sharded %d\n", - format, max_files_per_dir); - else - contents = apr_psprintf(pool, - "%d\n" - "layout linear\n", - format); - } - } - else - { - contents = apr_psprintf(pool, "%d\n", format); - } - - SVN_ERR(svn_io_write_atomic(path, contents, strlen(contents), - NULL /* copy perms */, pool)); - - /* And set the perms to make it read only */ - return svn_io_set_file_read_only(path, FALSE, pool); -} - /* Return the expected contents of "iota" in revision REV. */ static const char * get_rev_contents(svn_revnum_t rev, apr_pool_t *pool) @@ -178,7 +118,7 @@ create_packed_filesystem(const char *dir apr_pool_t *subpool = svn_pool_create(pool); struct pack_notify_baton pnb; apr_pool_t *iterpool; - int version; + apr_hash_t *fs_config; /* Bail (with success) on known-untestable scenarios */ if (strcmp(opts->fs_type, "fsfs") != 0) @@ -189,25 +129,12 @@ create_packed_filesystem(const char *dir return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, "pre-1.6 SVN doesn't support FSFS packing"); - /* Create a filesystem, then close it */ - SVN_ERR(svn_test__create_fs(&fs, dir, opts, subpool)); - svn_pool_destroy(subpool); - - subpool = svn_pool_create(pool); + fs_config = apr_hash_make(pool); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_SHARD_SIZE, + apr_itoa(pool, shard_size)); - /* Rewrite the format file. (The rest of this function is backend-agnostic, - so we just avoid adding the FSFS-specific format information if we run on - some other backend.) */ - if ((strcmp(opts->fs_type, "fsfs") == 0)) - { - SVN_ERR(svn_io_read_version_file(&version, - svn_dirent_join(dir, "format", subpool), - subpool)); - SVN_ERR(write_format(dir, version, shard_size, subpool)); - } - - /* Reopen the filesystem */ - SVN_ERR(svn_fs_open2(&fs, dir, NULL, subpool, subpool)); + /* Create a filesystem. */ + SVN_ERR(svn_test__create_fs2(&fs, dir, opts, fs_config, subpool)); /* Revision 1: the Greek tree */ SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool)); @@ -569,7 +496,7 @@ get_set_large_revprop_packed_fs(const sv * files but do not exceed the pack size limit. */ for (rev = 0; rev <= MAX_REV; ++rev) SVN_ERR(svn_fs_change_rev_prop(fs, rev, SVN_PROP_REVISION_LOG, - large_log(rev, 15000, pool), + large_log(rev, 1000, pool), pool)); /* verify */ @@ -578,20 +505,20 @@ get_set_large_revprop_packed_fs(const sv SVN_ERR(svn_fs_revision_prop(&prop_value, fs, rev, SVN_PROP_REVISION_LOG, pool)); SVN_TEST_STRING_ASSERT(prop_value->data, - large_log(rev, 15000, pool)->data); + large_log(rev, 1000, pool)->data); } /* Put a larger revprop into the last, some middle and the first revision * of a pack. This should cause the packs to split in the middle. */ SVN_ERR(svn_fs_change_rev_prop(fs, 3, SVN_PROP_REVISION_LOG, /* rev 0 is not packed */ - large_log(3, 37000, pool), + large_log(3, 2400, pool), pool)); SVN_ERR(svn_fs_change_rev_prop(fs, 5, SVN_PROP_REVISION_LOG, - large_log(5, 25000, pool), + large_log(5, 1500, pool), pool)); SVN_ERR(svn_fs_change_rev_prop(fs, 8, SVN_PROP_REVISION_LOG, - large_log(8, 25000, pool), + large_log(8, 1500, pool), pool)); /* verify */ @@ -602,13 +529,13 @@ get_set_large_revprop_packed_fs(const sv if (rev == 3) SVN_TEST_STRING_ASSERT(prop_value->data, - large_log(rev, 37000, pool)->data); + large_log(rev, 2400, pool)->data); else if (rev == 5 || rev == 8) SVN_TEST_STRING_ASSERT(prop_value->data, - large_log(rev, 25000, pool)->data); + large_log(rev, 1500, pool)->data); else SVN_TEST_STRING_ASSERT(prop_value->data, - large_log(rev, 15000, pool)->data); + large_log(rev, 1000, pool)->data); } return SVN_NO_ERROR; @@ -856,7 +783,7 @@ pack_shard_size_one(const svn_test_opts_ #undef SHARD_SIZE #undef MAX_REV /* ------------------------------------------------------------------------ */ -#define REPO_NAME "get_set_multiple_huge_revprops_packed_fs" +#define REPO_NAME "test-repo-get_set_multiple_huge_revprops_packed_fs" #define SHARD_SIZE 4 #define MAX_REV 9 static svn_error_t * @@ -1091,7 +1018,7 @@ upgrade_txns_to_log_addressing(const svn } #undef SHARD_SIZE -#define REPO_NAME "upgrade_new_txns_to_log_addressing" +#define REPO_NAME "test-repo-upgrade_new_txns_to_log_addressing" #define MAX_REV 8 static svn_error_t * upgrade_new_txns_to_log_addressing(const svn_test_opts_t *opts, @@ -1106,7 +1033,7 @@ upgrade_new_txns_to_log_addressing(const #undef MAX_REV /* ------------------------------------------------------------------------ */ -#define REPO_NAME "upgrade_old_txns_to_log_addressing" +#define REPO_NAME "test-repo-upgrade_old_txns_to_log_addressing" #define MAX_REV 8 static svn_error_t * upgrade_old_txns_to_log_addressing(const svn_test_opts_t *opts, @@ -1123,7 +1050,7 @@ upgrade_old_txns_to_log_addressing(const /* ------------------------------------------------------------------------ */ -#define REPO_NAME "metadata_checksumming" +#define REPO_NAME "test-repo-metadata_checksumming" static svn_error_t * metadata_checksumming(const svn_test_opts_t *opts, apr_pool_t *pool) @@ -1179,7 +1106,7 @@ metadata_checksumming(const svn_test_opt /* ------------------------------------------------------------------------ */ -#define REPO_NAME "revprop_caching_on_off" +#define REPO_NAME "test-repo-revprop_caching_on_off" static svn_error_t * revprop_caching_on_off(const svn_test_opts_t *opts, apr_pool_t *pool) Propchange: subversion/branches/authzperf/subversion/tests/libsvn_fs_x/ ------------------------------------------------------------------------------ --- svn:mergeinfo (original) +++ svn:mergeinfo Sat Jan 3 14:00:41 2015 @@ -80,4 +80,4 @@ /subversion/branches/verify-at-commit/subversion/tests/libsvn_fs_x:1462039-1462408 /subversion/branches/verify-keep-going/subversion/tests/libsvn_fs_x:1439280-1492639,1546002-1546110 /subversion/branches/wc-collate-path/subversion/tests/libsvn_fs_x:1402685-1480384 -/subversion/trunk/subversion/tests/libsvn_fs_x:1414756-1509914,1613053-1617077 +/subversion/trunk/subversion/tests/libsvn_fs_x:1414756-1509914,1613053-1649188 Modified: subversion/branches/authzperf/subversion/tests/libsvn_fs_x/fs-x-pack-test.c URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/tests/libsvn_fs_x/fs-x-pack-test.c?rev=1649205&r1=1649204&r2=1649205&view=diff ============================================================================== --- subversion/branches/authzperf/subversion/tests/libsvn_fs_x/fs-x-pack-test.c (original) +++ subversion/branches/authzperf/subversion/tests/libsvn_fs_x/fs-x-pack-test.c Sat Jan 3 14:00:41 2015 @@ -1,4 +1,4 @@ -/* fs-pack-test.c --- tests for the filesystem +/* fs-x-pack-test.c --- tests for the FSX filesystem * * ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one @@ -294,30 +294,12 @@ pack_filesystem(const svn_test_opts_t *o apr_psprintf(pool, "%d.pack", i / SHARD_SIZE), "pack", SVN_VA_NULL); - /* These files should exist. */ + /* This file should exist. */ SVN_ERR(svn_io_check_path(path, &kind, pool)); if (kind != svn_node_file) return svn_error_createf(SVN_ERR_FS_GENERAL, NULL, "Expected pack file '%s' not found", path); - path = svn_dirent_join_many(pool, REPO_NAME, "revs", - apr_psprintf(pool, "%d.pack", i / SHARD_SIZE), - "pack.l2p", SVN_VA_NULL); - SVN_ERR(svn_io_check_path(path, &kind, pool)); - if (kind != svn_node_file) - return svn_error_createf(SVN_ERR_FS_GENERAL, NULL, - "Expected log-to-phys index file '%s' not found", - path); - - path = svn_dirent_join_many(pool, REPO_NAME, "revs", - apr_psprintf(pool, "%d.pack", i / SHARD_SIZE), - "pack.p2l", SVN_VA_NULL); - SVN_ERR(svn_io_check_path(path, &kind, pool)); - if (kind != svn_node_file) - return svn_error_createf(SVN_ERR_FS_GENERAL, NULL, - "Expected phys-to-log index file '%s' not found", - path); - /* This directory should not exist. */ path = svn_dirent_join_many(pool, REPO_NAME, "revs", apr_psprintf(pool, "%d", i / SHARD_SIZE),
