diff options
Diffstat (limited to 'subversion/tests/libsvn_fs_fs')
| -rw-r--r-- | subversion/tests/libsvn_fs_fs/fs-fs-fuzzy-test.c | 393 | ||||
| -rw-r--r-- | subversion/tests/libsvn_fs_fs/fs-fs-pack-test.c | 1947 | ||||
| -rw-r--r-- | subversion/tests/libsvn_fs_fs/fs-fs-private-test.c | 434 | ||||
| -rw-r--r-- | subversion/tests/libsvn_fs_fs/fs-pack-test.c | 950 |
4 files changed, 2774 insertions, 950 deletions
diff --git a/subversion/tests/libsvn_fs_fs/fs-fs-fuzzy-test.c b/subversion/tests/libsvn_fs_fs/fs-fs-fuzzy-test.c new file mode 100644 index 0000000..818c1e0 --- /dev/null +++ b/subversion/tests/libsvn_fs_fs/fs-fs-fuzzy-test.c @@ -0,0 +1,393 @@ +/* fs-fs-fuzzy-test.c --- fuzzing 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/fs.h" +#include "../../libsvn_fs_fs/fs_fs.h" +#include "../../libsvn_fs_fs/rev_file.h" + +#include "svn_hash.h" +#include "svn_pools.h" +#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" + + + +/*** Helper Functions ***/ + +/* We won't log or malfunction() upon errors. */ +static void +dont_filter_warnings(void *baton, svn_error_t *err) +{ + return; +} + + +/*** Test core code ***/ + +/* Verify that a modification of any single byte in REVISION of FS at + * REPO_NAME using MODIFIER with BATON will be detected. */ +static svn_error_t * +fuzzing_1_byte_1_rev(const char *repo_name, + svn_fs_t *fs, + svn_revnum_t revision, + unsigned char (* modifier)(unsigned char c, void *baton), + void *baton, + apr_pool_t *pool) +{ + svn_repos_t *repos; + apr_hash_t *fs_config; + svn_fs_fs__revision_file_t *rev_file; + apr_off_t filesize = 0, offset; + apr_off_t i; + unsigned char footer_len; + + apr_pool_t *iterpool = svn_pool_create(pool); + + /* Open the revision file for modification. */ + SVN_ERR(svn_fs_fs__open_pack_or_rev_file_writable(&rev_file, fs, revision, + pool, iterpool)); + SVN_ERR(svn_fs_fs__auto_read_footer(rev_file)); + SVN_ERR(svn_io_file_seek(rev_file->file, APR_END, &filesize, iterpool)); + + offset = filesize - 1; + SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &offset, iterpool)); + SVN_ERR(svn_io_file_getc((char *)&footer_len, rev_file->file, iterpool)); + + /* We want all the caching we can get. More importantly, we want to + change the cache namespace before each test iteration. */ + fs_config = apr_hash_make(pool); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_DELTAS, "1"); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_FULLTEXTS, "1"); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, "2"); + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_BLOCK_READ, "0"); + + /* Manipulate all bytes one at a time. */ + for (i = 0; i < filesize; ++i) + { + svn_error_t *err = SVN_NO_ERROR; + + /* Read byte */ + unsigned char c_old, c_new; + SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &i, iterpool)); + SVN_ERR(svn_io_file_getc((char *)&c_old, rev_file->file, iterpool)); + + /* What to replace it with. Skip if there is no change. */ + c_new = modifier(c_old, baton); + if (c_new == c_old) + continue; + + /* Modify / corrupt the data. */ + SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &i, iterpool)); + SVN_ERR(svn_io_file_putc((char)c_new, rev_file->file, iterpool)); + SVN_ERR(svn_io_file_flush(rev_file->file, iterpool)); + + /* Make sure we use a different namespace for the caches during + this iteration. */ + svn_hash_sets(fs_config, SVN_FS_CONFIG_FSFS_CACHE_NS, + svn_uuid_generate(iterpool)); + SVN_ERR(svn_repos_open3(&repos, repo_name, fs_config, iterpool, iterpool)); + svn_fs_set_warning_func(svn_repos_fs(repos), dont_filter_warnings, NULL); + + /* This shall detect the corruption and return an error. */ + err = svn_repos_verify_fs3(repos, revision, revision, FALSE, FALSE, + NULL, NULL, NULL, NULL, NULL, NULL, + iterpool); + + /* Case-only changes in checksum digests are not an error. + * We allow upper case chars to be used in MD5 checksums in all other + * places, thus restricting them here would be inconsistent. */ + if ( i >= filesize - footer_len /* Within footer */ + && c_old >= 'a' && c_old <= 'f' /* 'a' to 'f', only appear + in checksum digests */ + && c_new == c_old - 'a' + 'A') /* respective upper case */ + { + if (err) + { + /* Let us know where we were too strict ... */ + printf("Detected case change in checksum digest at offset 0x%" + APR_UINT64_T_HEX_FMT " (%" APR_OFF_T_FMT ") in r%ld: " + "%c -> %c\n", (apr_uint64_t)i, i, revision, c_old, c_new); + + SVN_ERR(err); + } + } + else if (!err) + { + /* Let us know where we miss changes ... */ + printf("Undetected mod at offset 0x%"APR_UINT64_T_HEX_FMT + " (%"APR_OFF_T_FMT") in r%ld: 0x%02x -> 0x%02x\n", + (apr_uint64_t)i, i, revision, c_old, c_new); + + SVN_TEST_ASSERT(err); + } + + svn_error_clear(err); + + /* Undo the corruption. */ + SVN_ERR(svn_io_file_seek(rev_file->file, APR_SET, &i, iterpool)); + SVN_ERR(svn_io_file_putc((char)c_old, rev_file->file, iterpool)); + + svn_pool_clear(iterpool); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Create a greek repo with OPTS at REPO_NAME. Verify that a modification + * of any single byte using MODIFIER with BATON will be detected. */ +static svn_error_t * +fuzzing_1_byte_test(const svn_test_opts_t *opts, + const char *repo_name, + unsigned char (* modifier)(unsigned char c, void *baton), + void *baton, + apr_pool_t *pool) +{ + svn_repos_t *repos; + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + svn_revnum_t rev; + svn_revnum_t i; + + apr_pool_t *iterpool = svn_pool_create(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"); + /* Create a filesystem */ + SVN_ERR(svn_test__create_repos(&repos, repo_name, opts, pool)); + fs = svn_repos_fs(repos); + + /* Revision 1 (one and only revision): the Greek tree */ + 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(svn_fs_commit_txn(NULL, &rev, txn, pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(rev)); + + for (i = 0; i <= rev; ++i) + { + svn_pool_clear(iterpool); + SVN_ERR(fuzzing_1_byte_1_rev(repo_name, fs, i, modifier, baton, + iterpool)); + } + + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +/* Modifier function to be used with fuzzing_set_byte_test. + * We return the fixed char value given as *BATON. */ +static unsigned char +set_byte(unsigned char c, void *baton) +{ + return *(const unsigned char *)baton; +} + +/* Run the fuzzing test setting any byte in the repo to all values MIN to + * MAX-1. */ +static svn_error_t * +fuzzing_set_byte_test(const svn_test_opts_t *opts, + int min, + int max, + apr_pool_t *pool) +{ + apr_pool_t *iterpool = svn_pool_create(pool); + unsigned i = 0; + for (i = min; i < max; ++i) + { + unsigned char c = i; + const char *repo_name; + svn_pool_clear(iterpool); + + repo_name = apr_psprintf(iterpool, "test-repo-fuzzing_set_byte_%d_%d", + min, max); + SVN_ERR(fuzzing_1_byte_test(opts, repo_name, set_byte, &c, iterpool)); + } + + svn_pool_destroy(iterpool); + return SVN_NO_ERROR; +} + + + +/*** Tests ***/ + +/* ------------------------------------------------------------------------ */ + +static unsigned char +invert_byte(unsigned char c, void *baton) +{ + return ~c; +} + +static svn_error_t * +fuzzing_invert_byte_test(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + SVN_ERR(fuzzing_1_byte_test(opts, "test-repo-fuzzing_invert_byte", + invert_byte, NULL, pool)); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +static unsigned char +increment_byte(unsigned char c, void *baton) +{ + return c + 1; +} + +static svn_error_t * +fuzzing_increment_byte_test(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + SVN_ERR(fuzzing_1_byte_test(opts, "test-repo-fuzzing_increment_byte", + increment_byte, NULL, pool)); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +static unsigned char +decrement_byte(unsigned char c, void *baton) +{ + return c - 1; +} + +static svn_error_t * +fuzzing_decrement_byte_test(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + SVN_ERR(fuzzing_1_byte_test(opts, "test-repo-fuzzing_decrement_byte", + decrement_byte, NULL, pool)); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +static unsigned char +null_byte(unsigned char c, void *baton) +{ + return 0; +} + +static svn_error_t * +fuzzing_null_byte_test(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + SVN_ERR(fuzzing_1_byte_test(opts, "test-repo-fuzzing_null_byte", + null_byte, NULL, pool)); + + return SVN_NO_ERROR; +} + +/* ------------------------------------------------------------------------ */ + +/* Generator macro: define a test function covering byte values N to M-1 */ +#define FUZZING_SET_BYTE_TEST_N(N,M)\ + static svn_error_t * \ + fuzzing_set_byte_test_ ##N(const svn_test_opts_t *opts, \ + apr_pool_t *pool) \ + { \ + return svn_error_trace(fuzzing_set_byte_test(opts, N, M, pool)); \ + } + +/* Add the test function declared above to the test_funcs array. */ +#define TEST_FUZZING_SET_BYTE_TEST_N(N,M)\ + SVN_TEST_OPTS_PASS(fuzzing_set_byte_test_ ##N, \ + "set any byte to any value between " #N " and " #M) + +/* Declare tests that will cover all possible byte values. */ +FUZZING_SET_BYTE_TEST_N(0,16) +FUZZING_SET_BYTE_TEST_N(16,32) +FUZZING_SET_BYTE_TEST_N(32,48) +FUZZING_SET_BYTE_TEST_N(48,64) +FUZZING_SET_BYTE_TEST_N(64,80) +FUZZING_SET_BYTE_TEST_N(80,96) +FUZZING_SET_BYTE_TEST_N(96,112) +FUZZING_SET_BYTE_TEST_N(112,128) +FUZZING_SET_BYTE_TEST_N(128,144) +FUZZING_SET_BYTE_TEST_N(144,160) +FUZZING_SET_BYTE_TEST_N(160,176) +FUZZING_SET_BYTE_TEST_N(176,192) +FUZZING_SET_BYTE_TEST_N(192,208) +FUZZING_SET_BYTE_TEST_N(208,224) +FUZZING_SET_BYTE_TEST_N(224,240) +FUZZING_SET_BYTE_TEST_N(240,256) + + +/* The test table. */ + +/* Allow for any number of tests to run in parallel. */ +static int max_threads = 0; + +static struct svn_test_descriptor_t test_funcs[] = + { + SVN_TEST_NULL, + SVN_TEST_OPTS_PASS(fuzzing_invert_byte_test, + "fuzzing: invert any byte"), + SVN_TEST_OPTS_PASS(fuzzing_increment_byte_test, + "fuzzing: increment any byte"), + SVN_TEST_OPTS_PASS(fuzzing_decrement_byte_test, + "fuzzing: decrement any byte"), + SVN_TEST_OPTS_PASS(fuzzing_null_byte_test, + "fuzzing: set any byte to 0"), + + /* Register generated tests. */ + TEST_FUZZING_SET_BYTE_TEST_N(0,16), + TEST_FUZZING_SET_BYTE_TEST_N(16,32), + TEST_FUZZING_SET_BYTE_TEST_N(32,48), + TEST_FUZZING_SET_BYTE_TEST_N(48,64), + TEST_FUZZING_SET_BYTE_TEST_N(64,80), + TEST_FUZZING_SET_BYTE_TEST_N(80,96), + TEST_FUZZING_SET_BYTE_TEST_N(96,112), + TEST_FUZZING_SET_BYTE_TEST_N(112,128), + TEST_FUZZING_SET_BYTE_TEST_N(128,144), + TEST_FUZZING_SET_BYTE_TEST_N(144,160), + TEST_FUZZING_SET_BYTE_TEST_N(160,176), + TEST_FUZZING_SET_BYTE_TEST_N(176,192), + TEST_FUZZING_SET_BYTE_TEST_N(192,208), + TEST_FUZZING_SET_BYTE_TEST_N(208,224), + TEST_FUZZING_SET_BYTE_TEST_N(224,240), + TEST_FUZZING_SET_BYTE_TEST_N(240,256), + + SVN_TEST_NULL + }; + +SVN_TEST_MAIN 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 diff --git a/subversion/tests/libsvn_fs_fs/fs-fs-private-test.c b/subversion/tests/libsvn_fs_fs/fs-fs-private-test.c new file mode 100644 index 0000000..a1447ee --- /dev/null +++ b/subversion/tests/libsvn_fs_fs/fs-fs-private-test.c @@ -0,0 +1,434 @@ +/* fs-fs-private-test.c --- tests FSFS's private API + * + * ==================================================================== + * 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 "../svn_test.h" + +#include "svn_hash.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "svn_fs.h" + +#include "private/svn_string_private.h" +#include "private/svn_fs_fs_private.h" +#include "private/svn_subr_private.h" + +#include "../../libsvn_fs_fs/index.h" + +#include "../svn_test_fs.h" + + + +/* Utility functions */ + +/* Create a repo under REPO_NAME using OPTS. Allocate the repository in + * RESULT_POOL and return it in *REPOS. Set *REV to the revision containing + * the Greek tree addition. Use SCRATCH_POOL for temporary allocations. + */ +static svn_error_t * +create_greek_repo(svn_repos_t **repos, + svn_revnum_t *rev, + const svn_test_opts_t *opts, + const char *repo_name, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_fs_t *fs; + svn_fs_txn_t *txn; + svn_fs_root_t *txn_root; + + /* Create a filesystem */ + SVN_ERR(svn_test__create_repos(repos, repo_name, opts, result_pool)); + fs = svn_repos_fs(*repos); + + /* Add the Greek tree */ + SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, scratch_pool)); + SVN_ERR(svn_fs_txn_root(&txn_root, txn, scratch_pool)); + SVN_ERR(svn_test__create_greek_tree(txn_root, scratch_pool)); + SVN_ERR(svn_fs_commit_txn(NULL, rev, txn, scratch_pool)); + SVN_TEST_ASSERT(SVN_IS_VALID_REVNUM(*rev)); + + return SVN_NO_ERROR; +} + + +/* ------------------------------------------------------------------------ */ + +#define REPO_NAME "test-repo-get-repo-stats-test" + +static svn_error_t * +verify_representation_stats(const svn_fs_fs__representation_stats_t *stats, + apr_uint64_t expected_count) +{ + /* Small items, no packing (but inefficiency due to packing attempt). */ + SVN_TEST_ASSERT(stats->total.count == expected_count); + SVN_TEST_ASSERT( stats->total.packed_size >= 10 * expected_count + && stats->total.packed_size <= 1000 * expected_count); + SVN_TEST_ASSERT( stats->total.packed_size >= stats->total.expanded_size + && stats->total.packed_size <= 2 * stats->total.expanded_size); + SVN_TEST_ASSERT( stats->total.overhead_size >= 5 * expected_count + && stats->total.overhead_size <= 100 * expected_count); + + /* Rep sharing has no effect on the Greek tree. */ + SVN_TEST_ASSERT(stats->total.count == stats->uniques.count); + SVN_TEST_ASSERT(stats->total.packed_size == stats->uniques.packed_size); + SVN_TEST_ASSERT(stats->total.expanded_size == stats->uniques.expanded_size); + SVN_TEST_ASSERT(stats->total.overhead_size == stats->uniques.overhead_size); + + SVN_TEST_ASSERT(stats->shared.count == 0); + SVN_TEST_ASSERT(stats->shared.packed_size == 0); + SVN_TEST_ASSERT(stats->shared.expanded_size == 0); + SVN_TEST_ASSERT(stats->shared.overhead_size == 0); + + /* No rep sharing. */ + SVN_TEST_ASSERT(stats->references == stats->total.count); + SVN_TEST_ASSERT(stats->expanded_size == stats->total.expanded_size); + + return SVN_NO_ERROR; +} + +static svn_error_t * +verify_node_stats(const svn_fs_fs__node_stats_t *node_stats, + apr_uint64_t expected_count) +{ + SVN_TEST_ASSERT(node_stats->count == expected_count); + SVN_TEST_ASSERT( node_stats->size > 100 * node_stats->count + && node_stats->size < 1000 * node_stats->count); + + return SVN_NO_ERROR; +} + +static svn_error_t * +verify_large_change(const svn_fs_fs__large_change_info_t *change, + svn_revnum_t revision) +{ + if (change->revision == SVN_INVALID_REVNUM) + { + /* Unused entry due to the Greek tree being small. */ + SVN_TEST_ASSERT(change->path->len == 0); + SVN_TEST_ASSERT(change->size == 0); + } + else if (strcmp(change->path->data, "/") == 0) + { + /* The root folder nodes are always there, i.e. aren't in the + * Greek tree "do add" list. */ + SVN_TEST_ASSERT( SVN_IS_VALID_REVNUM(change->revision) + && change->revision <= revision); + } + else + { + const struct svn_test__tree_entry_t *node; + for (node = svn_test__greek_tree_nodes; node->path; node++) + if (strcmp(node->path, change->path->data + 1) == 0) + { + SVN_TEST_ASSERT(change->revision == revision); + + /* When checking content sizes, keep in mind the optional + * SVNDIFF overhead.*/ + if (node->contents) + SVN_TEST_ASSERT( change->size >= strlen(node->contents) + && change->size <= 12 + strlen(node->contents)); + + return SVN_NO_ERROR; + } + + SVN_TEST_ASSERT(!"Change is part of Greek tree"); + } + + return SVN_NO_ERROR; +} + +static svn_error_t * +verify_histogram(const svn_fs_fs__histogram_t *histogram) +{ + apr_uint64_t sum_count = 0; + apr_uint64_t sum_size = 0; + + int i; + for (i = 0; i < 64; ++i) + { + svn_fs_fs__histogram_line_t line = histogram->lines[i]; + + if (i > 10 || i < 1) + SVN_TEST_ASSERT(line.sum == 0 && line.count == 0); + else + SVN_TEST_ASSERT( line.sum >= (line.count << (i-1)) + && line.sum <= (line.count << i)); + + sum_count += line.count; + sum_size += line.sum; + } + + SVN_TEST_ASSERT(histogram->total.count == sum_count); + SVN_TEST_ASSERT(histogram->total.sum == sum_size); + + return SVN_NO_ERROR; +} + +static svn_error_t * +get_repo_stats(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_repos_t *repos; + svn_revnum_t rev; + apr_size_t i; + svn_fs_fs__stats_t *stats; + svn_fs_fs__extension_info_t *extension_info; + + /* 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"); + + /* Create a filesystem */ + SVN_ERR(create_greek_repo(&repos, &rev, opts, REPO_NAME, pool, pool)); + + /* Gather statistics info on that repo. */ + SVN_ERR(svn_fs_fs__get_stats(&stats, svn_repos_fs(repos), NULL, NULL, + NULL, NULL, pool, pool)); + + /* Check that the stats make sense. */ + SVN_TEST_ASSERT(stats->total_size > 1000 && stats->total_size < 10000); + SVN_TEST_ASSERT(stats->revision_count == 2); + SVN_TEST_ASSERT(stats->change_count == 20); + SVN_TEST_ASSERT(stats->change_len > 500 && stats->change_len < 2000); + + /* Check representation stats. */ + SVN_ERR(verify_representation_stats(&stats->total_rep_stats, 20)); + SVN_ERR(verify_representation_stats(&stats->file_rep_stats, 12)); + SVN_ERR(verify_representation_stats(&stats->dir_rep_stats, 8)); + SVN_ERR(verify_representation_stats(&stats->file_prop_rep_stats, 0)); + SVN_ERR(verify_representation_stats(&stats->dir_prop_rep_stats, 0)); + + /* Check node stats against rep stats. */ + SVN_ERR(verify_node_stats(&stats->total_node_stats, 22)); + SVN_ERR(verify_node_stats(&stats->file_node_stats, 12)); + SVN_ERR(verify_node_stats(&stats->dir_node_stats, 10)); + + /* Check largest changes. */ + SVN_TEST_ASSERT(stats->largest_changes->count == 64); + SVN_TEST_ASSERT(stats->largest_changes->min_size == 0); + + for (i = 0; i < stats->largest_changes->count; ++i) + SVN_ERR(verify_large_change(stats->largest_changes->changes[i], rev)); + + /* Check histograms. */ + SVN_ERR(verify_histogram(&stats->rep_size_histogram)); + SVN_ERR(verify_histogram(&stats->node_size_histogram)); + SVN_ERR(verify_histogram(&stats->added_rep_size_histogram)); + SVN_ERR(verify_histogram(&stats->added_node_size_histogram)); + SVN_ERR(verify_histogram(&stats->unused_rep_histogram)); + SVN_ERR(verify_histogram(&stats->file_histogram)); + SVN_ERR(verify_histogram(&stats->file_rep_histogram)); + SVN_ERR(verify_histogram(&stats->file_prop_histogram)); + SVN_ERR(verify_histogram(&stats->file_prop_rep_histogram)); + SVN_ERR(verify_histogram(&stats->dir_histogram)); + SVN_ERR(verify_histogram(&stats->dir_rep_histogram)); + SVN_ERR(verify_histogram(&stats->dir_prop_histogram)); + SVN_ERR(verify_histogram(&stats->dir_prop_rep_histogram)); + + /* No file in the Greek tree has an externsion */ + SVN_TEST_ASSERT(apr_hash_count(stats->by_extension) == 1); + extension_info = svn_hash_gets(stats->by_extension, "(none)"); + SVN_TEST_ASSERT(extension_info); + + SVN_ERR(verify_histogram(&extension_info->rep_histogram)); + SVN_ERR(verify_histogram(&extension_info->node_histogram)); + + return SVN_NO_ERROR; +} + +#undef REPO_NAME + +/* ------------------------------------------------------------------------ */ + +#define REPO_NAME "test-repo-dump-index-test" + +typedef struct dump_baton_t +{ + /* Number of callback invocations so far */ + int invocations; + + /* Rev file location we expect to be reported next */ + apr_off_t offset; + + /* All items must be from this revision. */ + svn_revnum_t revision; + + /* Track the item numbers we have already seen. */ + svn_bit_array__t *numbers_seen; +} dump_baton_t; + +static svn_error_t * +dump_index_entry(const svn_fs_fs__p2l_entry_t *entry, + void *baton_p, + apr_pool_t *scratch_pool) +{ + dump_baton_t *baton = baton_p; + + /* Count invocations. */ + baton->invocations++; + + /* We expect a report of contiguous non-empty items. */ + SVN_TEST_ASSERT(entry->offset == baton->offset); + SVN_TEST_ASSERT(entry->size > 0 && entry->size < 1000); + baton->offset += entry->size; + + /* Type must be valid. */ + SVN_TEST_ASSERT( entry->type > SVN_FS_FS__ITEM_TYPE_UNUSED + && entry->type <= SVN_FS_FS__ITEM_TYPE_CHANGES); + + /* We expect all items to be from the specified revision. */ + SVN_TEST_ASSERT(entry->item.revision == baton->revision); + + /* Item numnber must be plausibly small and unique. */ + SVN_TEST_ASSERT(entry->item.number < 100); + SVN_TEST_ASSERT(!svn_bit_array__get(baton->numbers_seen, + (apr_size_t)entry->item.number)); + svn_bit_array__set(baton->numbers_seen, (apr_size_t)entry->item.number, 1); + + return SVN_NO_ERROR; +} + +static svn_error_t * +dump_index(const svn_test_opts_t *opts, + apr_pool_t *pool) +{ + svn_repos_t *repos; + svn_revnum_t rev; + dump_baton_t baton; + + /* 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 < 9)) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "pre-1.9 SVN doesn't have FSFS indexes"); + + /* Create a filesystem */ + SVN_ERR(create_greek_repo(&repos, &rev, opts, REPO_NAME, pool, pool)); + + /* Read the index data for REV from that repo. */ + baton.invocations = 0; + baton.offset = 0; + baton.revision = rev; + baton.numbers_seen = svn_bit_array__create(100, pool); + SVN_ERR(svn_fs_fs__dump_index(svn_repos_fs(repos), rev, dump_index_entry, + &baton, NULL, NULL, pool)); + + /* Check that we've got all data (20 noderevs + 20 reps + 1 changes list). */ + SVN_TEST_ASSERT(baton.invocations == 41); + + return SVN_NO_ERROR; +} + +#undef REPO_NAME + +/* ------------------------------------------------------------------------ */ + +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; +} + +#define REPO_NAME "test-repo-load-index-test" + +static svn_error_t * +load_index(const svn_test_opts_t *opts, apr_pool_t *pool) +{ + svn_repos_t *repos; + svn_revnum_t rev; + apr_array_header_t *entries = apr_array_make(pool, 41, sizeof(void *)); + apr_array_header_t *alt_entries = apr_array_make(pool, 1, sizeof(void *)); + svn_fs_fs__p2l_entry_t entry; + + /* 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 < 9)) + return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL, + "pre-1.9 SVN doesn't have FSFS indexes"); + + /* Create a filesystem */ + SVN_ERR(create_greek_repo(&repos, &rev, opts, REPO_NAME, pool, pool)); + + /* Read the original index contents for REV in ENTRIES. */ + SVN_ERR(svn_fs_fs__dump_index(svn_repos_fs(repos), rev, receive_index, + entries, NULL, NULL, pool)); + + /* Replace it with an empty index. + * Note that the API requires at least one entry. Give it a dummy. */ + entry.offset = 0; + entry.size = 0; + entry.type = SVN_FS_FS__ITEM_TYPE_UNUSED; + entry.item.number = SVN_FS_FS__ITEM_INDEX_UNUSED; + entry.item.revision = SVN_INVALID_REVNUM; + APR_ARRAY_PUSH(alt_entries, svn_fs_fs__p2l_entry_t *) = &entry; + + SVN_ERR(svn_fs_fs__load_index(svn_repos_fs(repos), rev, alt_entries, pool)); + SVN_TEST_ASSERT_ERROR(svn_repos_verify_fs3(repos, rev, rev, FALSE, FALSE, + NULL, NULL, NULL, NULL, NULL, + NULL, pool), + SVN_ERR_FS_INDEX_CORRUPTION); + + /* Restore the original index. */ + SVN_ERR(svn_fs_fs__load_index(svn_repos_fs(repos), rev, entries, pool)); + SVN_ERR(svn_repos_verify_fs3(repos, rev, rev, FALSE, FALSE, NULL, NULL, + NULL, NULL, NULL, NULL, pool)); + + return SVN_NO_ERROR; +} + +#undef REPO_NAME + + + +/* The test table. */ + +static int max_threads = 0; + +static struct svn_test_descriptor_t test_funcs[] = + { + SVN_TEST_NULL, + SVN_TEST_OPTS_PASS(get_repo_stats, + "get statistics on a FSFS filesystem"), + SVN_TEST_OPTS_PASS(dump_index, + "dump the P2L index"), + SVN_TEST_OPTS_PASS(load_index, + "load the P2L index"), + SVN_TEST_NULL + }; + +SVN_TEST_MAIN diff --git a/subversion/tests/libsvn_fs_fs/fs-pack-test.c b/subversion/tests/libsvn_fs_fs/fs-pack-test.c deleted file mode 100644 index f0d7650..0000000 --- a/subversion/tests/libsvn_fs_fs/fs-pack-test.c +++ /dev/null @@ -1,950 +0,0 @@ -/* fs-pack-test.c --- tests for the 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/fs.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; -} - -/* 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 (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", - format); - } - else - { - contents = apr_psprintf(pool, "%d\n", format); - } - - { - const char *path_tmp; - - SVN_ERR(svn_io_write_unique(&path_tmp, - svn_dirent_dirname(path, pool), - contents, strlen(contents), - svn_io_file_del_none, pool)); - - /* rename the temp file as the real destination */ - SVN_ERR(svn_io_file_rename(path_tmp, path, 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) -{ - /* 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; -} - -/* 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, - int 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); - struct pack_notify_baton pnb; - apr_pool_t *iterpool; - int version; - - /* 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); - - /* Rewrite the format file */ - 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_open(&fs, dir, NULL, 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_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); - - /* 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, - int 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_open(fs, repo_name, NULL, 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; - - /* Bail (with success) on known-untestable scenarios */ - if ((strcmp(opts->fs_type, "fsfs") != 0) - || (opts->server_minor_version && (opts->server_minor_version < 6))) - return SVN_NO_ERROR; - - 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", 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); - - path = svn_dirent_join_many(pool, REPO_NAME, "revs", - apr_psprintf(pool, "%d.pack", i / SHARD_SIZE), - "manifest", 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), - 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), - 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; - - /* Bail (with success) on known-untestable scenarios */ - if ((strcmp(opts->fs_type, "fsfs") != 0) - || (opts->server_minor_version && (opts->server_minor_version < 6))) - return SVN_NO_ERROR; - - SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, - pool)); - - path = svn_dirent_join_many(pool, REPO_NAME, "revs", "2.pack", 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; - - /* Bail (with success) on known-untestable scenarios */ - if ((strcmp(opts->fs_type, "fsfs") != 0) - || (opts->server_minor_version && (opts->server_minor_version < 6))) - return SVN_NO_ERROR; - - SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, SHARD_SIZE, pool)); - SVN_ERR(svn_fs_open(&fs, REPO_NAME, NULL, 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; - - /* Bail (with success) on known-untestable scenarios */ - if ((strcmp(opts->fs_type, "fsfs") != 0) - || (opts->server_minor_version && (opts->server_minor_version < 6))) - return SVN_NO_ERROR; - - /* Create the packed FS and open it. */ - SVN_ERR(create_packed_filesystem(REPO_NAME, opts, MAX_REV, 5, pool)); - SVN_ERR(svn_fs_open(&fs, REPO_NAME, NULL, 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; - - /* Bail (with success) on known-untestable scenarios */ - if ((strcmp(opts->fs_type, "fsfs") != 0) - || (opts->server_minor_version && (opts->server_minor_version < 7))) - return SVN_NO_ERROR; - - /* 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; - - /* Bail (with success) on known-untestable scenarios */ - if ((strcmp(opts->fs_type, "fsfs") != 0) - || (opts->server_minor_version && (opts->server_minor_version < 7))) - return SVN_NO_ERROR; - - /* 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, 15000, 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, 15000, 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), - pool)); - SVN_ERR(svn_fs_change_rev_prop(fs, 5, SVN_PROP_REVISION_LOG, - large_log(5, 25000, pool), - pool)); - SVN_ERR(svn_fs_change_rev_prop(fs, 8, SVN_PROP_REVISION_LOG, - large_log(8, 25000, 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, 37000, pool)->data); - else if (rev == 5 || rev == 8) - SVN_TEST_STRING_ASSERT(prop_value->data, - large_log(rev, 25000, pool)->data); - else - SVN_TEST_STRING_ASSERT(prop_value->data, - large_log(rev, 15000, 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; - - /* Bail (with success) on known-untestable scenarios */ - if ((strcmp(opts->fs_type, "fsfs") != 0) - || (opts->server_minor_version && (opts->server_minor_version < 7))) - return SVN_NO_ERROR; - - /* 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; - - /* Bail (with success) on known-untestable scenarios */ - if ((strcmp(opts->fs_type, "fsfs") != 0) - || (opts->server_minor_version && (opts->server_minor_version < 7))) - return SVN_NO_ERROR; - - /* 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_open(&fs, REPO_NAME, NULL, 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), - 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; - - /* Bail (with success) on known-untestable scenarios */ - if ((strcmp(opts->fs_type, "fsfs") != 0) - || (opts->server_minor_version && (opts->server_minor_version < 8))) - return 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_open(&fs, REPO_NAME, NULL, 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 "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; - - /* Bail (with success) on known-untestable scenarios */ - if ((strcmp(opts->fs_type, "fsfs") != 0) - || (opts->server_minor_version && (opts->server_minor_version < 7))) - return SVN_NO_ERROR; - - /* 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 REPO_NAME "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); - apr_hash_set(fs_config, SVN_FS_CONFIG_FSFS_CACHE_REVPROPS, - APR_HASH_KEY_STRING, "1"); - - SVN_ERR(svn_fs_open(&fs2, svn_fs_path(fs1, pool), fs_config, 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 - -/* ------------------------------------------------------------------------ */ - -/* The test table. */ - -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(get_set_multiple_huge_revprops_packed_fs, - "set multiple huge revprops in packed FSFS"), - SVN_TEST_OPTS_PASS(revprop_caching_on_off, - "change revprops with enabled and disabled caching"), - SVN_TEST_NULL - }; |
