diff options
Diffstat (limited to 'subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c')
-rw-r--r-- | subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c | 1947 |
1 files changed, 1947 insertions, 0 deletions
diff --git a/subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c b/subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c new file mode 100644 index 0000000..aa07469 --- /dev/null +++ b/subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c @@ -0,0 +1,1947 @@ +/* fs-fs-pack-test.c --- tests for the FSFS filesystem + * + * ==================================================================== + * 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. + * ==================================================================== + */ + +#include <stdlib.h> +#include <string.h> +#include <apr_pools.h> + +#include "../svn_test.h" +#include "../../libsvn_fs/fs-loader.h" +#include "../../libsvn_fs_fs/fs.h" +#include "../../libsvn_fs_fs/fs_fs.h" +#include "../../libsvn_fs_fs/low_level.h" +#include "../../libsvn_fs_fs/pack.h" +#include "../../libsvn_fs_fs/util.h" + +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "svn_fs.h" +#include "private/svn_string_private.h" + +#include "../svn_test_fs.h" + + + +/*** Helper Functions ***/ + +static void +ignore_fs_warnings(void *baton, svn_error_t *err) +{ +#ifdef SVN_DEBUG + SVN_DBG(("Ignoring FS warning %s\n", + svn_error_symbolic_name(err ? err->apr_err : 0))); +#endif + return; +} + +/* Return the expected contents of "iota" in revision REV. */ +static const char * +get_rev_contents(svn_revnum_t rev, apr_pool_t *pool) +{ + /* Toss in a bunch of magic numbers for spice. */ + apr_int64_t num = ((rev * 1234353 + 4358) * 4583 + ((rev % 4) << 1)) / 42; + return apr_psprintf(pool, "%" APR_INT64_T_FMT "\n", num); +} + +struct pack_notify_baton +{ + apr_int64_t expected_shard; + svn_fs_pack_notify_action_t expected_action; +}; + +static svn_error_t * +pack_notify(void *baton, + apr_int64_t shard, + svn_fs_pack_notify_action_t action, + apr_pool_t *pool) +{ + struct pack_notify_baton *pnb = baton; + + SVN_TEST_ASSERT(shard == pnb->expected_shard); + SVN_TEST_ASSERT(action == pnb->expected_action); + + /* Update expectations. */ + switch (action) + { + case svn_fs_pack_notify_start: + pnb->expected_action = svn_fs_pack_notify_end; + break; + + case svn_fs_pack_notify_end: + pnb->expected_action = svn_fs_pack_notify_start; + pnb->expected_shard++; + break; + + default: + return svn_error_create(SVN_ERR_TEST_FAILED, NULL, + "Unknown notification action when packing"); + } + + return SVN_NO_ERROR; +} + +#define R1_LOG_MSG "Let's serf" + +/* Create a filesystem in DIR. Set the shard size to SHARD_SIZE and create + NUM_REVS number of revisions (in addition to r0). Use POOL for + allocations. After this function successfully completes, the filesystem's + youngest revision number will be NUM_REVS. */ +static svn_error_t * +create_non_packed_filesystem(const char *dir, + const svn_test_opts_t *opts, + svn_revnum_t num_revs, + int shard_size, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + const char *conflict; + svn_revnum_t after_rev; + apr_pool_t *subpool = svn_pool_create(pool); + apr_pool_t *iterpool; + apr_hash_t *fs_config; + + /* 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"); + + fs_config = apr_hash_make(pool); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_SHARD_SIZE, + apr_itoa(pool, shard_size)); + + /* 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)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); + SVN_ERR(svn_test__create_greek_tree(txn_root, subpool)); + SVN_ERR(svn_fs_change_txn_prop(txn, SVN_PROP_REVISION_LOG, + svn_string_create(R1_LOG_MSG, pool), + pool)); + SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, subpool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev)); + + /* Revisions 2 thru NUM_REVS-1: content tweaks to "iota". */ + iterpool = svn_pool_create(subpool); + while (after_rev < num_revs) + { + svn_pool_clear(iterpool); + SVN_ERR(svn_fs_begin_txn(&txn, fs, after_rev, iterpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, iterpool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "iota", + get_rev_contents(after_rev + 1, + iterpool), + iterpool)); + SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, iterpool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev)); + } + svn_pool_destroy(iterpool); + svn_pool_destroy(subpool); + + /* Done */ + return SVN_NO_ERROR; +} + +/* Create a packed filesystem in DIR. Set the shard size to + SHARD_SIZE and create NUM_REVS number of revisions (in addition to + r0). Use POOL for allocations. After this function successfully + completes, the filesystem's youngest revision number will be the + same as NUM_REVS. */ +static svn_error_t * +create_packed_filesystem(const char *dir, + const svn_test_opts_t *opts, + svn_revnum_t num_revs, + int shard_size, + apr_pool_t *pool) +{ + struct pack_notify_baton pnb; + + /* Create the repo and fill it. */ + SVN_ERR(create_non_packed_filesystem(dir, opts, num_revs, shard_size, + pool)); + + /* Now pack the FS */ + pnb.expected_shard = 0; + pnb.expected_action = svn_fs_pack_notify_start; + return svn_fs_pack(dir, pack_notify, &pnb, NULL, NULL, pool); +} + +/* Create a packed FSFS filesystem for revprop tests at REPO_NAME with + * MAX_REV revisions and the given SHARD_SIZE and OPTS. Return it in *FS. + * Use POOL for allocations. + */ +static svn_error_t * +prepare_revprop_repo(svn_fs_t **fs, + const char *repo_name, + svn_revnum_t max_rev, + int shard_size, + const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + const char *conflict; + svn_revnum_t after_rev; + apr_pool_t *subpool; + + /* Create the packed FS and open it. */ + SVN_ERR(create_packed_filesystem(repo_name, opts, max_rev, shard_size, pool)); + SVN_ERR(svn_fs_open2(fs, repo_name, NULL, pool, pool)); + + subpool = svn_pool_create(pool); + /* Do a commit to trigger packing. */ + SVN_ERR(svn_fs_begin_txn(&txn, *fs, max_rev, subpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "iota", "new-iota", subpool)); + SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, subpool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev)); + svn_pool_destroy(subpool); + + /* Pack the repository. */ + SVN_ERR(svn_fs_pack(repo_name, NULL, NULL, NULL, NULL, pool)); + + return SVN_NO_ERROR; +} + +/* For revision REV, return a short log message allocated in POOL. + */ +static svn_string_t * +default_log(svn_revnum_t rev, apr_pool_t *pool) +{ + return svn_string_createf(pool, "Default message for rev %ld", rev); +} + +/* For revision REV, return a long log message allocated in POOL. + */ +static svn_string_t * +large_log(svn_revnum_t rev, apr_size_t length, apr_pool_t *pool) +{ + svn_stringbuf_t *temp = svn_stringbuf_create_ensure(100000, pool); + int i, count = (int)(length - 50) / 6; + + svn_stringbuf_appendcstr(temp, "A "); + for (i = 0; i < count; ++i) + svn_stringbuf_appendcstr(temp, "very, "); + + svn_stringbuf_appendcstr(temp, + apr_psprintf(pool, "very long message for rev %ld, indeed", rev)); + + return svn_stringbuf__morph_into_string(temp); +} + +/* For revision REV, return a long log message allocated in POOL. + */ +static svn_string_t * +huge_log(svn_revnum_t rev, apr_pool_t *pool) +{ + return large_log(rev, 90000, pool); +} + + +/*** Tests ***/ + +/* ------------------------------------------------------------------------ */ +#define REPO_NAME "test-repo-fsfs-pack" +#define SHARD_SIZE 7 +#define MAX_REV 53 +static svn_error_t * +pack_filesystem(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + int i; + svn_node_kind_t kind; + const char *path; + char buf[80]; + apr_file_t *file; + apr_size_t len; + + SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, + pool)); + + /* Check to see that the pack files exist, and that the rev directories + don't. */ + for (i = 0; i < (MAX_REV + 1) / SHARD_SIZE; i++) + { + path = svn_dirent_join_many(pool, REPO_NAME, "revs", + apr_psprintf(pool, "%d.pack", i / SHARD_SIZE), + "pack", SVN_VA_NULL); + + /* These files 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); + + if (opts->server_minor_version && (opts->server_minor_version < 9)) + { + path = svn_dirent_join_many(pool, REPO_NAME, "revs", + apr_psprintf(pool, "%d.pack", i / SHARD_SIZE), + "manifest", 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 manifest 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), + SVN_VA_NULL); + SVN_ERR(svn_io_check_path(path, &kind, pool)); + if (kind != svn_node_none) + return svn_error_createf(SVN_ERR_FS_GENERAL, NULL, + "Unexpected directory '%s' found", path); + } + + /* Ensure the min-unpacked-rev jives with the above operations. */ + SVN_ERR(svn_io_file_open(&file, + svn_dirent_join(REPO_NAME, PATH_MIN_UNPACKED_REV, + pool), + APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool)); + len = sizeof(buf); + SVN_ERR(svn_io_read_length_line(file, buf, &len, pool)); + SVN_ERR(svn_io_file_close(file, pool)); + if (SVN_STR_TO_REV(buf) != (MAX_REV / SHARD_SIZE) * SHARD_SIZE) + return svn_error_createf(SVN_ERR_FS_GENERAL, NULL, + "Bad '%s' contents", PATH_MIN_UNPACKED_REV); + + /* Finally, make sure the final revision directory does exist. */ + path = svn_dirent_join_many(pool, REPO_NAME, "revs", + apr_psprintf(pool, "%d", (i / SHARD_SIZE) + 1), + SVN_VA_NULL); + SVN_ERR(svn_io_check_path(path, &kind, pool)); + if (kind != svn_node_none) + return svn_error_createf(SVN_ERR_FS_GENERAL, NULL, + "Expected directory '%s' not found", path); + + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef SHARD_SIZE +#undef MAX_REV + +/* ------------------------------------------------------------------------ */ +#define REPO_NAME "test-repo-fsfs-pack-even" +#define SHARD_SIZE 4 +#define MAX_REV 11 +static svn_error_t * +pack_even_filesystem(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_node_kind_t kind; + const char *path; + + SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, + pool)); + + path = svn_dirent_join_many(pool, REPO_NAME, "revs", "2.pack", SVN_VA_NULL); + SVN_ERR(svn_io_check_path(path, &kind, pool)); + if (kind != svn_node_dir) + return svn_error_createf(SVN_ERR_FS_GENERAL, NULL, + "Packing did not complete as expected"); + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef SHARD_SIZE +#undef MAX_REV + +/* ------------------------------------------------------------------------ */ +#define REPO_NAME "test-repo-read-packed-fs" +#define SHARD_SIZE 5 +#define MAX_REV 11 +static svn_error_t * +read_packed_fs(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_stream_t *rstream; + svn_stringbuf_t *rstring; + svn_revnum_t i; + + SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, pool)); + SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, pool, pool)); + + for (i = 1; i < (MAX_REV + 1); i++) + { + svn_fs_root_t *rev_root; + svn_stringbuf_t *sb; + + SVN_ERR(svn_fs_revision_root(&rev_root, fs, i, pool)); + SVN_ERR(svn_fs_file_contents(&rstream, rev_root, "iota", pool)); + SVN_ERR(svn_test__stream_to_string(&rstring, rstream, pool)); + + if (i == 1) + sb = svn_stringbuf_create("This is the file 'iota'.\n", pool); + else + sb = svn_stringbuf_create(get_rev_contents(i, pool), pool); + + if (! svn_stringbuf_compare(rstring, sb)) + return svn_error_createf(SVN_ERR_FS_GENERAL, NULL, + "Bad data in revision %ld.", i); + } + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef SHARD_SIZE +#undef MAX_REV + +/* ------------------------------------------------------------------------ */ +#define REPO_NAME "test-repo-commit-packed-fs" +#define SHARD_SIZE 5 +#define MAX_REV 10 +static svn_error_t * +commit_packed_fs(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; + const char *conflict; + svn_revnum_t after_rev; + + /* Create the packed FS and open it. */ + SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, 5, pool)); + SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, pool, pool)); + + /* Now do a commit. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, MAX_REV, pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "iota", + "How much better is it to get wisdom than gold! and to get " + "understanding rather to be chosen than silver!", pool)); + SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev)); + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef MAX_REV +#undef SHARD_SIZE + +/* ------------------------------------------------------------------------ */ +#define REPO_NAME "test-repo-get-set-revprop-packed-fs" +#define SHARD_SIZE 4 +#define MAX_REV 10 +static svn_error_t * +get_set_revprop_packed_fs(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_string_t *prop_value; + + /* Create the packed FS and open it. */ + SVN_ERR(prepare_revprop_repo(&fs, REPO_NAME, MAX_REV, SHARD_SIZE, opts, + pool)); + + /* Try to get revprop for revision 0 + * (non-packed due to special handling). */ + SVN_ERR(svn_fs_revision_prop(&prop_value, fs, 0, SVN_PROP_REVISION_AUTHOR, + pool)); + + /* Try to change revprop for revision 0 + * (non-packed due to special handling). */ + SVN_ERR(svn_fs_change_rev_prop(fs, 0, SVN_PROP_REVISION_AUTHOR, + svn_string_create("tweaked-author", pool), + pool)); + + /* verify */ + SVN_ERR(svn_fs_revision_prop(&prop_value, fs, 0, SVN_PROP_REVISION_AUTHOR, + pool)); + SVN_TEST_STRING_ASSERT(prop_value->data, "tweaked-author"); + + /* Try to get packed revprop for revision 5. */ + SVN_ERR(svn_fs_revision_prop(&prop_value, fs, 5, SVN_PROP_REVISION_AUTHOR, + pool)); + + /* Try to change packed revprop for revision 5. */ + SVN_ERR(svn_fs_change_rev_prop(fs, 5, SVN_PROP_REVISION_AUTHOR, + svn_string_create("tweaked-author2", pool), + pool)); + + /* verify */ + SVN_ERR(svn_fs_revision_prop(&prop_value, fs, 5, SVN_PROP_REVISION_AUTHOR, + pool)); + SVN_TEST_STRING_ASSERT(prop_value->data, "tweaked-author2"); + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef MAX_REV +#undef SHARD_SIZE + +/* ------------------------------------------------------------------------ */ +#define REPO_NAME "test-repo-get-set-large-revprop-packed-fs" +#define SHARD_SIZE 4 +#define MAX_REV 11 +static svn_error_t * +get_set_large_revprop_packed_fs(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_string_t *prop_value; + svn_revnum_t rev; + + /* Create the packed FS and open it. */ + SVN_ERR(prepare_revprop_repo(&fs, REPO_NAME, MAX_REV, SHARD_SIZE, opts, + pool)); + + /* Set commit messages to different, large values that fill the pack + * 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, 1000, pool), + pool)); + + /* verify */ + for (rev = 0; rev <= MAX_REV; ++rev) + { + 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, 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, 2400, pool), + pool)); + SVN_ERR(svn_fs_change_rev_prop(fs, 5, SVN_PROP_REVISION_LOG, + large_log(5, 1500, pool), + pool)); + SVN_ERR(svn_fs_change_rev_prop(fs, 8, SVN_PROP_REVISION_LOG, + large_log(8, 1500, pool), + pool)); + + /* verify */ + for (rev = 0; rev <= MAX_REV; ++rev) + { + SVN_ERR(svn_fs_revision_prop(&prop_value, fs, rev, + SVN_PROP_REVISION_LOG, pool)); + + if (rev == 3) + SVN_TEST_STRING_ASSERT(prop_value->data, + large_log(rev, 2400, pool)->data); + else if (rev == 5 || rev == 8) + SVN_TEST_STRING_ASSERT(prop_value->data, + large_log(rev, 1500, pool)->data); + else + SVN_TEST_STRING_ASSERT(prop_value->data, + large_log(rev, 1000, pool)->data); + } + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef MAX_REV +#undef SHARD_SIZE + +/* ------------------------------------------------------------------------ */ +#define REPO_NAME "test-repo-get-set-huge-revprop-packed-fs" +#define SHARD_SIZE 4 +#define MAX_REV 10 +static svn_error_t * +get_set_huge_revprop_packed_fs(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_string_t *prop_value; + svn_revnum_t rev; + + /* Create the packed FS and open it. */ + SVN_ERR(prepare_revprop_repo(&fs, REPO_NAME, MAX_REV, SHARD_SIZE, opts, + pool)); + + /* Set commit messages to different values */ + for (rev = 0; rev <= MAX_REV; ++rev) + SVN_ERR(svn_fs_change_rev_prop(fs, rev, SVN_PROP_REVISION_LOG, + default_log(rev, pool), + pool)); + + /* verify */ + for (rev = 0; rev <= MAX_REV; ++rev) + { + SVN_ERR(svn_fs_revision_prop(&prop_value, fs, rev, + SVN_PROP_REVISION_LOG, pool)); + SVN_TEST_STRING_ASSERT(prop_value->data, default_log(rev, pool)->data); + } + + /* Put a huge revprop into the last, some middle and the first revision + * of a pack. They will cause the pack files to split accordingly. */ + SVN_ERR(svn_fs_change_rev_prop(fs, 3, SVN_PROP_REVISION_LOG, + huge_log(3, pool), + pool)); + SVN_ERR(svn_fs_change_rev_prop(fs, 5, SVN_PROP_REVISION_LOG, + huge_log(5, pool), + pool)); + SVN_ERR(svn_fs_change_rev_prop(fs, 8, SVN_PROP_REVISION_LOG, + huge_log(8, pool), + pool)); + + /* verify */ + for (rev = 0; rev <= MAX_REV; ++rev) + { + SVN_ERR(svn_fs_revision_prop(&prop_value, fs, rev, + SVN_PROP_REVISION_LOG, pool)); + + if (rev == 3 || rev == 5 || rev == 8) + SVN_TEST_STRING_ASSERT(prop_value->data, + huge_log(rev, pool)->data); + else + SVN_TEST_STRING_ASSERT(prop_value->data, + default_log(rev, pool)->data); + } + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef MAX_REV +#undef SHARD_SIZE + +/* ------------------------------------------------------------------------ */ +/* Regression test for issue #3571 (fsfs 'svnadmin recover' expects + youngest revprop to be outside revprops.db). */ +#define REPO_NAME "test-repo-recover-fully-packed" +#define SHARD_SIZE 4 +#define MAX_REV 7 +static svn_error_t * +recover_fully_packed(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + apr_pool_t *subpool; + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + const char *conflict; + svn_revnum_t after_rev; + svn_error_t *err; + + /* Create a packed FS for which every revision will live in a pack + digest file, and then recover it. */ + SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, pool)); + SVN_ERR(svn_fs_recover(REPO_NAME, NULL, NULL, pool)); + + /* Add another revision, re-pack, re-recover. */ + subpool = svn_pool_create(pool); + SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, subpool, subpool)); + SVN_ERR(svn_fs_begin_txn(&txn, fs, MAX_REV, subpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "A/mu", "new-mu", subpool)); + SVN_ERR(svn_fs_commit_txn(&conflict, &after_rev, txn, subpool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(after_rev)); + svn_pool_destroy(subpool); + SVN_ERR(svn_fs_pack(REPO_NAME, NULL, NULL, NULL, NULL, pool)); + SVN_ERR(svn_fs_recover(REPO_NAME, NULL, NULL, pool)); + + /* Now, delete the youngest revprop file, and recover again. This + time we want to see an error! */ + SVN_ERR(svn_io_remove_file2( + svn_dirent_join_many(pool, REPO_NAME, PATH_REVPROPS_DIR, + apr_psprintf(pool, "%ld/%ld", + after_rev / SHARD_SIZE, + after_rev), + SVN_VA_NULL), + FALSE, pool)); + err = svn_fs_recover(REPO_NAME, NULL, NULL, pool); + if (! err) + return svn_error_create(SVN_ERR_TEST_FAILED, NULL, + "Expected SVN_ERR_FS_CORRUPT error; got none"); + if (err->apr_err != SVN_ERR_FS_CORRUPT) + return svn_error_create(SVN_ERR_TEST_FAILED, err, + "Expected SVN_ERR_FS_CORRUPT error; got:"); + svn_error_clear(err); + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef MAX_REV +#undef SHARD_SIZE + +/* ------------------------------------------------------------------------ */ +/* Regression test for issue #4320 (fsfs file-hinting fails when reading a rep + from the transaction that is commiting rev = SHARD_SIZE). */ +#define REPO_NAME "test-repo-file-hint-at-shard-boundary" +#define SHARD_SIZE 4 +#define MAX_REV (SHARD_SIZE - 1) +static svn_error_t * +file_hint_at_shard_boundary(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + apr_pool_t *subpool; + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + const char *file_contents; + svn_stringbuf_t *retrieved_contents; + svn_error_t *err = SVN_NO_ERROR; + + /* Create a packed FS and MAX_REV revisions */ + SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, pool)); + + /* Reopen the filesystem */ + subpool = svn_pool_create(pool); + SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, subpool, subpool)); + + /* Revision = SHARD_SIZE */ + file_contents = get_rev_contents(SHARD_SIZE, subpool); + SVN_ERR(svn_fs_begin_txn(&txn, fs, MAX_REV, subpool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); + SVN_ERR(svn_test__set_file_contents(txn_root, "iota", file_contents, + subpool)); + + /* Retrieve the file. */ + SVN_ERR(svn_test__get_file_contents(txn_root, "iota", &retrieved_contents, + subpool)); + if (strcmp(retrieved_contents->data, file_contents)) + { + err = svn_error_create(SVN_ERR_TEST_FAILED, err, + "Retrieved incorrect contents from iota."); + } + + /* Close the repo. */ + svn_pool_destroy(subpool); + + return err; +} +#undef REPO_NAME +#undef MAX_REV +#undef SHARD_SIZE + +/* ------------------------------------------------------------------------ */ +#define REPO_NAME "test-repo-fsfs-info" +#define SHARD_SIZE 3 +#define MAX_REV 5 +static svn_error_t * +test_info(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + const svn_fs_fsfs_info_t *fsfs_info; + const svn_fs_info_placeholder_t *info; + + SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, + pool)); + + SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, pool, pool)); + SVN_ERR(svn_fs_info(&info, fs, pool, pool)); + info = svn_fs_info_dup(info, pool, pool); + + SVN_TEST_STRING_ASSERT(opts->fs_type, info->fs_type); + + /* Bail (with success) on known-untestable scenarios */ + if (strcmp(opts->fs_type, "fsfs") != 0) + return SVN_NO_ERROR; + + fsfs_info = (const void *)info; + if (opts->server_minor_version && (opts->server_minor_version < 6)) + { + SVN_TEST_ASSERT(fsfs_info->shard_size == 0); + SVN_TEST_ASSERT(fsfs_info->min_unpacked_rev == 0); + } + else + { + SVN_TEST_ASSERT(fsfs_info->shard_size == SHARD_SIZE); + SVN_TEST_ASSERT(fsfs_info->min_unpacked_rev + == (MAX_REV + 1) / SHARD_SIZE * SHARD_SIZE); + } + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef SHARD_SIZE +#undef MAX_REV + +/* ------------------------------------------------------------------------ */ +#define REPO_NAME "test-repo-fsfs-pack-shard-size-one" +#define SHARD_SIZE 1 +#define MAX_REV 4 +static svn_error_t * +pack_shard_size_one(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_string_t *propval; + svn_fs_t *fs; + + SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, + pool)); + SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, pool, pool)); + /* whitebox: revprop packing special-cases r0, which causes + (start_rev==1, end_rev==0) in pack_revprops_shard(). So test that. */ + SVN_ERR(svn_fs_revision_prop(&propval, fs, 1, SVN_PROP_REVISION_LOG, pool)); + SVN_TEST_STRING_ASSERT(propval->data, R1_LOG_MSG); + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef SHARD_SIZE +#undef MAX_REV +/* ------------------------------------------------------------------------ */ +#define REPO_NAME "test-repo-get_set_multiple_huge_revprops_packed_fs" +#define SHARD_SIZE 4 +#define MAX_REV 9 +static svn_error_t * +get_set_multiple_huge_revprops_packed_fs(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_string_t *prop_value; + svn_revnum_t rev; + + /* Create the packed FS and open it. */ + SVN_ERR(prepare_revprop_repo(&fs, REPO_NAME, MAX_REV, SHARD_SIZE, opts, + pool)); + + /* Set commit messages to different values */ + for (rev = 0; rev <= MAX_REV; ++rev) + SVN_ERR(svn_fs_change_rev_prop(fs, rev, SVN_PROP_REVISION_LOG, + default_log(rev, pool), + pool)); + + /* verify */ + for (rev = 0; rev <= MAX_REV; ++rev) + { + SVN_ERR(svn_fs_revision_prop(&prop_value, fs, rev, + SVN_PROP_REVISION_LOG, pool)); + SVN_TEST_STRING_ASSERT(prop_value->data, default_log(rev, pool)->data); + } + + /* Put a huge revprop into revision 1 and 2. */ + SVN_ERR(svn_fs_change_rev_prop(fs, 1, SVN_PROP_REVISION_LOG, + huge_log(1, pool), + pool)); + SVN_ERR(svn_fs_change_rev_prop(fs, 2, SVN_PROP_REVISION_LOG, + huge_log(2, pool), + pool)); + SVN_ERR(svn_fs_change_rev_prop(fs, 5, SVN_PROP_REVISION_LOG, + huge_log(5, pool), + pool)); + SVN_ERR(svn_fs_change_rev_prop(fs, 6, SVN_PROP_REVISION_LOG, + huge_log(6, pool), + pool)); + + /* verify */ + for (rev = 0; rev <= MAX_REV; ++rev) + { + SVN_ERR(svn_fs_revision_prop(&prop_value, fs, rev, + SVN_PROP_REVISION_LOG, pool)); + + if (rev == 1 || rev == 2 || rev == 5 || rev == 6) + SVN_TEST_STRING_ASSERT(prop_value->data, + huge_log(rev, pool)->data); + else + SVN_TEST_STRING_ASSERT(prop_value->data, + default_log(rev, pool)->data); + } + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef MAX_REV +#undef SHARD_SIZE + +/* ------------------------------------------------------------------------ */ +#define SHARD_SIZE 4 +static svn_error_t * +upgrade_txns_to_log_addressing(const svn_test_opts_t *opts, + const char *repo_name, + svn_revnum_t max_rev, + svn_boolean_t upgrade_before_txns, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_revnum_t rev; + apr_array_header_t *txns; + apr_array_header_t *txn_names; + int i, k; + svn_test_opts_t temp_opts; + svn_fs_root_t *root; + apr_pool_t *iterpool = svn_pool_create(pool); + + static const char * const paths[SHARD_SIZE][2] + = { + { "A/mu", "A/B/lambda" }, + { "A/B/E/alpha", "A/D/H/psi" }, + { "A/D/gamma", "A/B/E/beta" }, + { "A/D/G/pi", "A/D/G/rho" } + }; + + /* Bail (with success) on known-untestable scenarios */ + if ((strcmp(opts->fs_type, "fsfs") != 0) + || (opts->server_minor_version && (opts->server_minor_version < 9))) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "pre-1.9 SVN doesn't support log addressing"); + + /* Create the packed FS in phys addressing format and open it. */ + temp_opts = *opts; + temp_opts.server_minor_version = 8; + SVN_ERR(prepare_revprop_repo(&fs, repo_name, max_rev, SHARD_SIZE, + &temp_opts, pool)); + + if (upgrade_before_txns) + { + /* upgrade to final repo format (using log addressing) and re-open */ + SVN_ERR(svn_fs_upgrade2(repo_name, NULL, NULL, NULL, NULL, pool)); + SVN_ERR(svn_fs_open2(&fs, repo_name, svn_fs_config(fs, pool), pool, + pool)); + } + + /* Create 4 concurrent transactions */ + txns = apr_array_make(pool, SHARD_SIZE, sizeof(svn_fs_txn_t *)); + txn_names = apr_array_make(pool, SHARD_SIZE, sizeof(const char *)); + for (i = 0; i < SHARD_SIZE; ++i) + { + svn_fs_txn_t *txn; + const char *txn_name; + + SVN_ERR(svn_fs_begin_txn(&txn, fs, max_rev, pool)); + APR_ARRAY_PUSH(txns, svn_fs_txn_t *) = txn; + + SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool)); + APR_ARRAY_PUSH(txn_names, const char *) = txn_name; + } + + /* Let all txns touch at least 2 files. + * Thus, the addressing data of at least one representation in the txn + * will differ between addressing modes. */ + for (i = 0; i < SHARD_SIZE; ++i) + { + svn_fs_txn_t *txn = APR_ARRAY_IDX(txns, i, svn_fs_txn_t *); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + + for (k = 0; k < 2; ++k) + { + svn_stream_t *stream; + const char *file_path = paths[i][k]; + svn_pool_clear(iterpool); + + SVN_ERR(svn_fs_apply_text(&stream, root, file_path, NULL, iterpool)); + SVN_ERR(svn_stream_printf(stream, iterpool, + "This is file %s in txn %d", + file_path, i)); + SVN_ERR(svn_stream_close(stream)); + } + } + + if (!upgrade_before_txns) + { + /* upgrade to final repo format (using log addressing) and re-open */ + SVN_ERR(svn_fs_upgrade2(repo_name, NULL, NULL, NULL, NULL, pool)); + SVN_ERR(svn_fs_open2(&fs, repo_name, svn_fs_config(fs, pool), pool, + pool)); + } + + /* Commit all transactions + * (in reverse order to make things more interesting) */ + for (i = SHARD_SIZE - 1; i >= 0; --i) + { + svn_fs_txn_t *txn; + const char *txn_name = APR_ARRAY_IDX(txn_names, i, const char *); + svn_pool_clear(iterpool); + + SVN_ERR(svn_fs_open_txn(&txn, fs, txn_name, iterpool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, iterpool)); + } + + /* Further changes to fill the shard */ + + SVN_ERR(svn_fs_youngest_rev(&rev, fs, pool)); + SVN_TEST_ASSERT(rev == SHARD_SIZE + max_rev + 1); + + while ((rev + 1) % SHARD_SIZE) + { + svn_fs_txn_t *txn; + if (rev % SHARD_SIZE == 0) + break; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, iterpool)); + SVN_ERR(svn_fs_txn_root(&root, txn, iterpool)); + SVN_ERR(svn_test__set_file_contents(root, "iota", + get_rev_contents(rev + 1, iterpool), + iterpool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, iterpool)); + } + + /* Make sure to close all file handles etc. from the last iteration */ + + svn_pool_clear(iterpool); + + /* Pack repo to verify that old and new shard get packed according to + their respective addressing mode */ + + SVN_ERR(svn_fs_pack(repo_name, NULL, NULL, NULL, NULL, pool)); + + /* verify that our changes got in */ + + SVN_ERR(svn_fs_revision_root(&root, fs, rev, pool)); + for (i = 0; i < SHARD_SIZE; ++i) + { + for (k = 0; k < 2; ++k) + { + svn_stream_t *stream; + const char *file_path = paths[i][k]; + svn_string_t *string; + const char *expected; + + svn_pool_clear(iterpool); + + SVN_ERR(svn_fs_file_contents(&stream, root, file_path, iterpool)); + SVN_ERR(svn_string_from_stream(&string, stream, iterpool, iterpool)); + + expected = apr_psprintf(pool,"This is file %s in txn %d", + file_path, i); + SVN_TEST_STRING_ASSERT(string->data, expected); + } + } + + /* verify that the indexes are consistent, we calculated the correct + low-level checksums etc. */ + SVN_ERR(svn_fs_verify(repo_name, NULL, + SVN_INVALID_REVNUM, SVN_INVALID_REVNUM, + NULL, NULL, NULL, NULL, pool)); + for (; rev >= 0; --rev) + { + svn_pool_clear(iterpool); + SVN_ERR(svn_fs_revision_root(&root, fs, rev, iterpool)); + SVN_ERR(svn_fs_verify_root(root, iterpool)); + } + + return SVN_NO_ERROR; +} +#undef SHARD_SIZE + +#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, + apr_pool_t *pool) +{ + SVN_ERR(upgrade_txns_to_log_addressing(opts, REPO_NAME, MAX_REV, TRUE, + pool)); + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef MAX_REV + +/* ------------------------------------------------------------------------ */ +#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, + apr_pool_t *pool) +{ + SVN_ERR(upgrade_txns_to_log_addressing(opts, REPO_NAME, MAX_REV, FALSE, + pool)); + + return SVN_NO_ERROR; +} + +#undef REPO_NAME +#undef MAX_REV + +/* ------------------------------------------------------------------------ */ + +#define REPO_NAME "test-repo-metadata_checksumming" +static svn_error_t * +metadata_checksumming(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + const char *repo_path, *r0_path; + apr_hash_t *fs_config = apr_hash_make(pool); + svn_stringbuf_t *r0; + svn_fs_root_t *root; + apr_hash_t *dir; + + /* Skip this test unless we are FSFS f7+ */ + if ((strcmp(opts->fs_type, "fsfs") != 0) + || (opts->server_minor_version && (opts->server_minor_version < 9))) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "pre-1.9 SVN doesn't checksum metadata"); + + /* Create the file system to fiddle with. */ + SVN_ERR(svn_test__create_fs(&fs, REPO_NAME, opts, pool)); + repo_path = svn_fs_path(fs, pool); + + /* Manipulate the data on disk. + * (change id from '0.0.*' to '1.0.*') */ + r0_path = svn_dirent_join_many(pool, repo_path, "revs", "0", "0", + SVN_VA_NULL); + SVN_ERR(svn_stringbuf_from_file2(&r0, r0_path, pool)); + r0->data[21] = '1'; + SVN_ERR(svn_io_remove_file2(r0_path, FALSE, pool)); + SVN_ERR(svn_io_file_create_bytes(r0_path, r0->data, r0->len, pool)); + + /* Reading the corrupted data on the normal code path triggers no error. + * Use a separate namespace to avoid simply reading data from cache. */ + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, + svn_uuid_generate(pool)); + SVN_ERR(svn_fs_open2(&fs, repo_path, fs_config, pool, pool)); + SVN_ERR(svn_fs_revision_root(&root, fs, 0, pool)); + SVN_ERR(svn_fs_dir_entries(&dir, root, "/", pool)); + + /* The block-read code path uses the P2L index information and compares + * low-level checksums. Again, separate cache namespace. */ + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, + svn_uuid_generate(pool)); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ, "1"); + SVN_ERR(svn_fs_open2(&fs, repo_path, fs_config, pool, pool)); + SVN_ERR(svn_fs_revision_root(&root, fs, 0, pool)); + SVN_TEST_ASSERT_ERROR(svn_fs_dir_entries(&dir, root, "/", pool), + SVN_ERR_CHECKSUM_MISMATCH); + + return SVN_NO_ERROR; +} + +#undef REPO_NAME + +/* ------------------------------------------------------------------------ */ + +#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) +{ + svn_fs_t *fs1; + svn_fs_t *fs2; + apr_hash_t *fs_config; + svn_string_t *value; + const svn_string_t *another_value_for_avoiding_warnings_from_a_broken_api; + const svn_string_t *new_value = svn_string_create("new", pool); + + if (strcmp(opts->fs_type, "fsfs") != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, NULL); + + /* Open two filesystem objects, enable revision property caching + * in one of them. */ + SVN_ERR(svn_test__create_fs(&fs1, REPO_NAME, opts, pool)); + + fs_config = apr_hash_make(pool); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "1"); + + SVN_ERR(svn_fs_open2(&fs2, svn_fs_path(fs1, pool), fs_config, pool, pool)); + + /* With inefficient named atomics, the filesystem will output a warning + and disable the revprop caching, but we still would like to test + these cases. Ignore the warning(s). */ + svn_fs_set_warning_func(fs2, ignore_fs_warnings, NULL); + + SVN_ERR(svn_fs_revision_prop(&value, fs2, 0, "svn:date", pool)); + another_value_for_avoiding_warnings_from_a_broken_api = value; + SVN_ERR(svn_fs_change_rev_prop2( + fs1, 0, "svn:date", + &another_value_for_avoiding_warnings_from_a_broken_api, + new_value, pool)); + + /* Expect the change to be visible through both objects.*/ + SVN_ERR(svn_fs_revision_prop(&value, fs1, 0, "svn:date", pool)); + SVN_TEST_STRING_ASSERT(value->data, "new"); + + SVN_ERR(svn_fs_revision_prop(&value, fs2, 0, "svn:date", pool)); + SVN_TEST_STRING_ASSERT(value->data, "new"); + + return SVN_NO_ERROR; +} + +#undef REPO_NAME + +/* ------------------------------------------------------------------------ */ + +static svn_error_t * +id_parser_test(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + #define LONG_MAX_STR #LONG_MAX + + /* Verify the revision number parser (e.g. first element of a txn ID) */ + svn_fs_fs__id_part_t id_part; + SVN_ERR(svn_fs_fs__id_txn_parse(&id_part, "0-0")); + +#if LONG_MAX == 2147483647L + SVN_ERR(svn_fs_fs__id_txn_parse(&id_part, "2147483647-0")); + + /* Trigger all sorts of overflow conditions. */ + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "2147483648-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "21474836470-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "21474836479-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "4294967295-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "4294967296-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "4294967304-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "4294967305-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "42949672950-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "42949672959-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + + /* 0x120000000 = 4831838208. + * 483183820 < 10*483183820 mod 2^32 = 536870904 */ + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "4831838208-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); +#else + SVN_ERR(svn_fs_fs__id_txn_parse(&id_part, "9223372036854775807-0")); + + /* Trigger all sorts of overflow conditions. */ + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, + "9223372036854775808-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, + "92233720368547758070-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, + "92233720368547758079-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, + "18446744073709551615-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, + "18446744073709551616-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, + "18446744073709551624-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, + "18446744073709551625-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, + "184467440737095516150-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, + "184467440737095516159-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + + /* 0x12000000000000000 = 20752587082923245568. + * 2075258708292324556 < 10*2075258708292324556 mod 2^32 = 2305843009213693944 */ + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, + "20752587082923245568-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); +#endif + + /* Invalid characters */ + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "2e4-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + SVN_TEST_ASSERT_ERROR(svn_fs_fs__id_txn_parse(&id_part, "2-4-0"), + SVN_ERR_FS_MALFORMED_TXN_ID); + + return SVN_NO_ERROR; +} + +#undef REPO_NAME + +/* ------------------------------------------------------------------------ */ + +#define REPO_NAME "test-repo-plain_0_length" + +static svn_error_t * +receive_index(const svn_fs_fs__p2l_entry_t *entry, + void *baton, + apr_pool_t *scratch_pool) +{ + apr_array_header_t *entries = baton; + APR_ARRAY_PUSH(entries, svn_fs_fs__p2l_entry_t *) + = apr_pmemdup(entries->pool, entry, sizeof(*entry)); + + return SVN_NO_ERROR; +} + +static apr_size_t +stringbuf_find(svn_stringbuf_t *rev_contents, + const char *substring) +{ + apr_size_t i; + apr_size_t len = strlen(substring); + + for (i = 0; i < rev_contents->len - len + 1; ++i) + if (!memcmp(rev_contents->data + i, substring, len)) + return i; + + return APR_SIZE_MAX; +} + +static svn_error_t * +plain_0_length(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + fs_fs_data_t *ffd; + svn_fs_txn_t *txn; + svn_fs_root_t *root; + svn_revnum_t rev; + const char *rev_path; + svn_stringbuf_t *rev_contents; + apr_hash_t *fs_config; + svn_filesize_t file_length; + apr_size_t offset; + + if (strcmp(opts->fs_type, "fsfs") != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, NULL); + + /* Create a repo that does not deltify properties and does not share reps + on its own - makes it easier to do that later by hand. */ + SVN_ERR(svn_test__create_fs(&fs, REPO_NAME, opts, pool)); + ffd = fs->fsap_data; + ffd->deltify_properties = FALSE; + ffd->rep_sharing_allowed = FALSE; + + /* Create one file node with matching contents and property reps. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_fs_make_file(root, "foo", pool)); + SVN_ERR(svn_test__set_file_contents(root, "foo", "END\n", pool)); + SVN_ERR(svn_fs_change_node_prop(root, "foo", "x", NULL, pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Redirect text rep to props rep. */ + rev_path = svn_fs_fs__path_rev_absolute(fs, rev, pool); + SVN_ERR(svn_stringbuf_from_file2(&rev_contents, rev_path, pool)); + + offset = stringbuf_find(rev_contents, "id: "); + if (offset != APR_SIZE_MAX) + { + node_revision_t *noderev; + svn_stringbuf_t *noderev_str; + + /* Read the noderev. */ + svn_stream_t *stream = svn_stream_from_stringbuf(rev_contents, pool); + SVN_ERR(svn_stream_skip(stream, offset)); + SVN_ERR(svn_fs_fs__read_noderev(&noderev, stream, pool, pool)); + SVN_ERR(svn_stream_close(stream)); + + /* Tweak the DATA_REP. */ + noderev->data_rep->revision = noderev->prop_rep->revision; + noderev->data_rep->item_index = noderev->prop_rep->item_index; + noderev->data_rep->size = noderev->prop_rep->size; + noderev->data_rep->expanded_size = 0; + + /* Serialize it back. */ + noderev_str = svn_stringbuf_create_empty(pool); + stream = svn_stream_from_stringbuf(noderev_str, pool); + SVN_ERR(svn_fs_fs__write_noderev(stream, noderev, ffd->format, + svn_fs_fs__fs_supports_mergeinfo(fs), + pool)); + SVN_ERR(svn_stream_close(stream)); + + /* Patch the revision contents */ + memcpy(rev_contents->data + offset, noderev_str->data, noderev_str->len); + } + + SVN_ERR(svn_io_write_atomic(rev_path, rev_contents->data, + rev_contents->len, NULL, pool)); + + if (svn_fs_fs__use_log_addressing(fs)) + { + /* Refresh index data (checksums). */ + apr_array_header_t *entries = apr_array_make(pool, 4, sizeof(void *)); + SVN_ERR(svn_fs_fs__dump_index(fs, rev, receive_index, entries, + NULL, NULL, pool)); + SVN_ERR(svn_fs_fs__load_index(fs, rev, entries, pool)); + } + + /* Create an independent FS instances with separate caches etc. */ + fs_config = apr_hash_make(pool); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, + svn_uuid_generate(pool)); + SVN_ERR(svn_fs_open2(&fs, REPO_NAME, fs_config, pool, pool)); + + /* Now, check that we get the correct file length. */ + SVN_ERR(svn_fs_revision_root(&root, fs, rev, pool)); + SVN_ERR(svn_fs_file_length(&file_length, root, "foo", pool)); + + SVN_TEST_ASSERT(file_length == 4); + + return SVN_NO_ERROR; +} + +#undef REPO_NAME + +/* ------------------------------------------------------------------------ */ + +#define REPO_NAME "test-repo-rep_sharing_effectiveness" + +static int +count_substring(svn_stringbuf_t *string, + const char *needle) +{ + int count = 0; + apr_size_t len = strlen(needle); + apr_size_t pos; + + for (pos = 0; pos + len <= string->len; ++pos) + if (memcmp(string->data + pos, needle, len) == 0) + ++count; + + return count; +} + +static svn_error_t * +count_representations(int *count, + svn_fs_t *fs, + svn_revnum_t revision, + apr_pool_t *pool) +{ + svn_stringbuf_t *rev_contents; + const char *rev_path = svn_fs_fs__path_rev_absolute(fs, revision, pool); + SVN_ERR(svn_stringbuf_from_file2(&rev_contents, rev_path, pool)); + + *count = count_substring(rev_contents, "PLAIN") + + count_substring(rev_contents, "DELTA"); + + return SVN_NO_ERROR; +} + +/* Repeat string S many times to make it big enough for deltification etc. + to kick in. */ +static const char* +multiply_string(const char *s, + apr_pool_t *pool) +{ + svn_stringbuf_t *temp = svn_stringbuf_create(s, pool); + + int i; + for (i = 0; i < 7; ++i) + svn_stringbuf_insert(temp, temp->len, temp->data, temp->len); + + return temp->data; +} + +static svn_error_t * +rep_sharing_effectiveness(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + fs_fs_data_t *ffd; + svn_fs_txn_t *txn; + svn_fs_root_t *root; + svn_revnum_t rev; + const char *hello_str = multiply_string("Hello, ", pool); + const char *world_str = multiply_string("World!", pool); + const char *goodbye_str = multiply_string("Goodbye!", pool); + + if (strcmp(opts->fs_type, "fsfs") != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, NULL); + + /* Create a repo that and explicitly enable rep sharing. */ + SVN_ERR(svn_test__create_fs(&fs, REPO_NAME, opts, pool)); + + ffd = fs->fsap_data; + if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, NULL); + + ffd->rep_sharing_allowed = TRUE; + + /* Revision 1: create 2 files with different content. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_fs_make_file(root, "foo", pool)); + SVN_ERR(svn_test__set_file_contents(root, "foo", hello_str, pool)); + SVN_ERR(svn_fs_make_file(root, "bar", pool)); + SVN_ERR(svn_test__set_file_contents(root, "bar", world_str, pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Revision 2: modify a file to match another file's r1 content and + add another with the same content. + (classic rep-sharing). */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_test__set_file_contents(root, "foo", world_str, pool)); + SVN_ERR(svn_fs_make_file(root, "baz", pool)); + SVN_ERR(svn_test__set_file_contents(root, "baz", hello_str, pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Revision 3: modify all files to some new, identical content and add + another with the same content. + (in-revision rep-sharing). */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_test__set_file_contents(root, "foo", goodbye_str, pool)); + SVN_ERR(svn_test__set_file_contents(root, "bar", goodbye_str, pool)); + SVN_ERR(svn_test__set_file_contents(root, "baz", goodbye_str, pool)); + SVN_ERR(svn_fs_make_file(root, "qux", pool)); + SVN_ERR(svn_test__set_file_contents(root, "qux", goodbye_str, pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Verify revision contents. */ + { + const struct { + svn_revnum_t revision; + const char *file; + const char *contents; + } expected[] = { + { 1, "foo", "Hello, " }, + { 1, "bar", "World!" }, + { 2, "foo", "World!" }, + { 2, "bar", "World!" }, + { 2, "baz", "Hello, " }, + { 3, "foo", "Goodbye!" }, + { 3, "bar", "Goodbye!" }, + { 3, "baz", "Goodbye!" }, + { 3, "qux", "Goodbye!" }, + { SVN_INVALID_REVNUM, NULL, NULL } + }; + + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + for (i = 0; SVN_IS_VALID_REVNUM(expected[i].revision); ++i) + { + svn_stringbuf_t *str; + + SVN_ERR(svn_fs_revision_root(&root, fs, expected[i].revision, + iterpool)); + SVN_ERR(svn_test__get_file_contents(root, expected[i].file, &str, + iterpool)); + + SVN_TEST_STRING_ASSERT(str->data, + multiply_string(expected[i].contents, + iterpool)); + } + + svn_pool_destroy(iterpool); + } + + /* Verify that rep sharing eliminated most reps. */ + { + /* Number of expected representations (including the root directory). */ + const int expected[] = { 1, 3, 1, 2 } ; + + svn_revnum_t i; + apr_pool_t *iterpool = svn_pool_create(pool); + for (i = 0; i <= rev; ++i) + { + int count; + SVN_ERR(count_representations(&count, fs, i, iterpool)); + SVN_TEST_ASSERT(count == expected[i]); + } + + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + +#undef REPO_NAME + +/* ------------------------------------------------------------------------ */ + +#define REPO_NAME "test-repo-delta_chain_with_plain" + +static svn_error_t * +delta_chain_with_plain(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + fs_fs_data_t *ffd; + svn_fs_txn_t *txn; + svn_fs_root_t *root; + svn_revnum_t rev; + svn_stringbuf_t *prop_value, *contents, *contents2, *hash_rep; + int i; + apr_hash_t *fs_config, *props; + + if (strcmp(opts->fs_type, "fsfs") != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, NULL); + + /* Reproducing issue #4577 without the r1676667 fix is much harder in 1.9+ + * than it was in 1.8. The reason is that 1.9+ won't deltify small reps + * nor against small reps. So, we must construct relatively large PLAIN + * and DELTA reps. + * + * The idea is to construct a PLAIN prop rep, make a file share that as + * its text rep, grow the file considerably (to make the PLAIN rep later + * read beyond EOF) and then replace it entirely with another longish + * contents. + */ + + /* Create a repo that and explicitly enable rep sharing. */ + SVN_ERR(svn_test__create_fs(&fs, REPO_NAME, opts, pool)); + + ffd = fs->fsap_data; + if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, NULL); + + ffd->rep_sharing_allowed = TRUE; + + /* Make sure all props are stored as PLAIN reps. */ + ffd->deltify_properties = FALSE; + + /* Construct various content strings. + * Note that props need to be shorter than the file contents. */ + prop_value = svn_stringbuf_create("prop", pool); + for (i = 0; i < 10; ++i) + svn_stringbuf_appendstr(prop_value, prop_value); + + contents = svn_stringbuf_create("Some text.", pool); + for (i = 0; i < 10; ++i) + svn_stringbuf_appendstr(contents, contents); + + contents2 = svn_stringbuf_create("Totally new!", pool); + for (i = 0; i < 10; ++i) + svn_stringbuf_appendstr(contents2, contents2); + + /* Revision 1: create a property rep. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_fs_change_node_prop(root, "/", "p", + svn_string_create(prop_value->data, pool), + pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Revision 2: create a file that shares the text rep with the PLAIN + * property rep from r1. */ + props = apr_hash_make(pool); + svn_hash_sets(props, "p", svn_string_create(prop_value->data, pool)); + + hash_rep = svn_stringbuf_create_empty(pool); + svn_hash_write2(props, svn_stream_from_stringbuf(hash_rep, pool), "END", + pool); + + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_fs_make_file(root, "foo", pool)); + SVN_ERR(svn_test__set_file_contents(root, "foo", hash_rep->data, pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Revision 3: modify the file contents to a long-ish full text + * (~10kByte, longer than the r1 revision file). */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_test__set_file_contents(root, "foo", contents->data, pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Revision 4: replace file contents to something disjoint from r3. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_test__set_file_contents(root, "foo", contents2->data, pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Getting foo@4 must work. To make sure we actually read from disk, + * use a new FS instance with disjoint caches. */ + fs_config = apr_hash_make(pool); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, + svn_uuid_generate(pool)); + SVN_ERR(svn_fs_open2(&fs, REPO_NAME, fs_config, pool, pool)); + + SVN_ERR(svn_fs_revision_root(&root, fs, rev, pool)); + SVN_ERR(svn_test__get_file_contents(root, "foo", &contents, pool)); + SVN_TEST_STRING_ASSERT(contents->data, contents2->data); + + return SVN_NO_ERROR; +} + +#undef REPO_NAME + +/* ------------------------------------------------------------------------ */ + +#define REPO_NAME "test-repo-compare_0_length_rep" + +static svn_error_t * +compare_0_length_rep(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *root; + svn_revnum_t rev; + int i, k; + apr_hash_t *fs_config; + + /* Test expectations. */ +#define no_rep_file "no-rep" +#define empty_plain_file "empty-plain" +#define plain_file "plain" +#define empty_delta_file "empty-delta" +#define delta_file "delta" + + enum { COUNT = 5 }; + const char *file_names[COUNT] = { no_rep_file, + empty_delta_file, + plain_file, + empty_delta_file, + delta_file }; + + int equal[COUNT][COUNT] = { { 1, 1, 0, 1, 0 }, + { 1, 1, 0, 1, 0 }, + { 0, 0, 1, 0, 1 }, + { 1, 1, 0, 1, 0 }, + { 0, 0, 1, 0, 1 } }; + + /* Well, this club is FSFS only ... */ + if (strcmp(opts->fs_type, "fsfs") != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, NULL); + + /* We want to check that whether NULL reps, empty PLAIN reps and empty + * DELTA reps are all considered equal, yet different from non-empty reps. + * + * Because we can't create empty PLAIN reps with recent formats anymore, + * some format selection & upgrade gymnastics is needed. */ + + /* Create a format 1 repository. + * This one does not support DELTA reps, so all is PLAIN. */ + fs_config = apr_hash_make(pool); + svn_hash_sets(fs_config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE, "x"); + SVN_ERR(svn_test__create_fs2(&fs, REPO_NAME, opts, fs_config, pool)); + + /* Revision 1, create 3 files: + * One with no rep, one with an empty rep and a non-empty one. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_fs_make_file(root, no_rep_file, pool)); + SVN_ERR(svn_fs_make_file(root, empty_plain_file, pool)); + SVN_ERR(svn_test__set_file_contents(root, empty_plain_file, "", pool)); + SVN_ERR(svn_fs_make_file(root, plain_file, pool)); + SVN_ERR(svn_test__set_file_contents(root, plain_file, "x", pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Upgrade the file system format. */ + SVN_ERR(svn_fs_upgrade2(REPO_NAME, NULL, NULL, NULL, NULL, pool)); + SVN_ERR(svn_fs_open2(&fs, REPO_NAME, NULL, pool, pool)); + + /* Revision 2, create two more files: + * a file with an empty DELTA rep and a non-empty one. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, rev, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_fs_make_file(root, empty_delta_file, pool)); + SVN_ERR(svn_test__set_file_contents(root, empty_delta_file, "", pool)); + SVN_ERR(svn_fs_make_file(root, delta_file, pool)); + SVN_ERR(svn_test__set_file_contents(root, delta_file, "x", pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Now compare. */ + SVN_ERR(svn_fs_revision_root(&root, fs, rev, pool)); + for (i = 0; i < COUNT; ++i) + for (k = 0; k < COUNT; ++k) + { + svn_boolean_t different; + SVN_ERR(svn_fs_contents_different(&different, root, file_names[i], + root, file_names[k], pool)); + SVN_TEST_ASSERT(different != equal[i][k]); + } + + return SVN_NO_ERROR; +} + +#undef REPO_NAME + +/* ------------------------------------------------------------------------ */ +/* Verify that the format 7 pack logic works even if we can't fit all index + metadata into memory. */ +#define REPO_NAME "test-repo-pack-with-limited-memory" +#define SHARD_SIZE 4 +#define MAX_REV (2 * SHARD_SIZE - 1) +static svn_error_t * +pack_with_limited_memory(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + apr_size_t max_mem; + apr_pool_t *iterpool = svn_pool_create(pool); + + /* Bail (with success) on known-untestable scenarios */ + if (opts->server_minor_version && (opts->server_minor_version < 9)) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "pre-1.9 SVN doesn't support reordering packs"); + + /* Run with an increasing memory allowance such that we cover all + splitting scenarios. */ + for (max_mem = 350; max_mem < 8000; max_mem += max_mem / 2) + { + const char *dir; + svn_fs_t *fs; + + svn_pool_clear(iterpool); + + /* Create a filesystem. */ + dir = apr_psprintf(iterpool, "%s-%d", REPO_NAME, (int)max_mem); + SVN_ERR(create_non_packed_filesystem(dir, opts, MAX_REV, SHARD_SIZE, + iterpool)); + + /* Pack it with a narrow memory budget. */ + SVN_ERR(svn_fs_open2(&fs, dir, NULL, iterpool, iterpool)); + SVN_ERR(svn_fs_fs__pack(fs, max_mem, NULL, NULL, NULL, NULL, + iterpool)); + + /* To be sure: Verify that we didn't break the repo. */ + SVN_ERR(svn_fs_verify(dir, NULL, 0, MAX_REV, NULL, NULL, NULL, NULL, + iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} +#undef REPO_NAME +#undef MAX_REV +#undef SHARD_SIZE + +/* ------------------------------------------------------------------------ */ + +#define REPO_NAME "test-repo-large_delta_against_plain" + +static svn_error_t * +large_delta_against_plain(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_fs_t *fs; + fs_fs_data_t *ffd; + svn_fs_txn_t *txn; + svn_fs_root_t *root; + svn_revnum_t rev; + svn_stringbuf_t *prop_value; + svn_string_t *prop_read; + int i; + apr_hash_t *fs_config; + + if (strcmp(opts->fs_type, "fsfs") != 0) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, NULL); + + /* Create a repo that and explicitly enable rep sharing. */ + SVN_ERR(svn_test__create_fs(&fs, REPO_NAME, opts, pool)); + ffd = fs->fsap_data; + + /* Make sure all props are stored as PLAIN reps. */ + ffd->deltify_properties = FALSE; + + /* Construct a property larger than 2 txdelta windows. */ + prop_value = svn_stringbuf_create("prop", pool); + while (prop_value->len <= 2 * 102400) + svn_stringbuf_appendstr(prop_value, prop_value); + + /* Revision 1: create a property rep. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_fs_change_node_prop(root, "/", "p", + svn_string_create(prop_value->data, pool), + pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Now, store them as DELTA reps. */ + ffd->deltify_properties = TRUE; + + /* Construct a property larger than 2 txdelta windows, distinct from the + * previous one but with a matching "tail". */ + prop_value = svn_stringbuf_create("blob", pool); + while (prop_value->len <= 2 * 102400) + svn_stringbuf_appendstr(prop_value, prop_value); + for (i = 0; i < 100; ++i) + svn_stringbuf_appendcstr(prop_value, "prop"); + + /* Revision 2: modify the property. */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 1, pool)); + SVN_ERR(svn_fs_txn_root(&root, txn, pool)); + SVN_ERR(svn_fs_change_node_prop(root, "/", "p", + svn_string_create(prop_value->data, pool), + pool)); + SVN_ERR(svn_fs_commit_txn(NULL, &rev, txn, pool)); + + /* Reconstructing the property deltified must work. To make sure we + * actually read from disk, use a new FS instance with disjoint caches. */ + fs_config = apr_hash_make(pool); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, + svn_uuid_generate(pool)); + SVN_ERR(svn_fs_open2(&fs, REPO_NAME, fs_config, pool, pool)); + + SVN_ERR(svn_fs_revision_root(&root, fs, rev, pool)); + SVN_ERR(svn_fs_node_prop(&prop_read, root, "/", "p", pool)); + SVN_TEST_STRING_ASSERT(prop_read->data, prop_value->data); + + return SVN_NO_ERROR; +} + +#undef REPO_NAME + + + +/* The test table. */ + +static int max_threads = 4; + +static struct svn_test_descriptor_t test_funcs[] = + { + SVN_TEST_NULL, + SVN_TEST_OPTS_PASS(pack_filesystem, + "pack a FSFS filesystem"), + SVN_TEST_OPTS_PASS(pack_even_filesystem, + "pack FSFS where revs % shard = 0"), + SVN_TEST_OPTS_PASS(read_packed_fs, + "read from a packed FSFS filesystem"), + SVN_TEST_OPTS_PASS(commit_packed_fs, + "commit to a packed FSFS filesystem"), + SVN_TEST_OPTS_PASS(get_set_revprop_packed_fs, + "get/set revprop while packing FSFS filesystem"), + SVN_TEST_OPTS_PASS(get_set_large_revprop_packed_fs, + "get/set large packed revprops in FSFS"), + SVN_TEST_OPTS_PASS(get_set_huge_revprop_packed_fs, + "get/set huge packed revprops in FSFS"), + SVN_TEST_OPTS_PASS(recover_fully_packed, + "recover a fully packed filesystem"), + SVN_TEST_OPTS_PASS(file_hint_at_shard_boundary, + "test file hint at shard boundary"), + SVN_TEST_OPTS_PASS(test_info, + "test svn_fs_info"), + SVN_TEST_OPTS_PASS(pack_shard_size_one, + "test packing with shard size = 1"), + SVN_TEST_OPTS_PASS(get_set_multiple_huge_revprops_packed_fs, + "set multiple huge revprops in packed FSFS"), + SVN_TEST_OPTS_PASS(upgrade_new_txns_to_log_addressing, + "upgrade txns to log addressing in shared FSFS"), + SVN_TEST_OPTS_PASS(upgrade_old_txns_to_log_addressing, + "upgrade txns started before svnadmin upgrade"), + SVN_TEST_OPTS_PASS(metadata_checksumming, + "metadata checksums being checked"), + SVN_TEST_OPTS_PASS(revprop_caching_on_off, + "change revprops with enabled and disabled caching"), + SVN_TEST_OPTS_PASS(id_parser_test, + "id parser test"), + SVN_TEST_OPTS_PASS(plain_0_length, + "file with 0 expanded-length, issue #4554"), + SVN_TEST_OPTS_PASS(rep_sharing_effectiveness, + "rep-sharing effectiveness"), + SVN_TEST_OPTS_PASS(delta_chain_with_plain, + "delta chains starting with PLAIN, issue #4577"), + SVN_TEST_OPTS_PASS(compare_0_length_rep, + "compare empty PLAIN and non-existent reps"), + SVN_TEST_OPTS_PASS(pack_with_limited_memory, + "pack with limited memory for metadata"), + SVN_TEST_OPTS_PASS(large_delta_against_plain, + "large deltas against PLAIN, issue #4658"), + SVN_TEST_NULL + }; + +SVN_TEST_MAIN |