| /* changes-test.c --- test `changes' interfaces |
| * |
| * ==================================================================== |
| * Copyright (c) 2000-2004 CollabNet. All rights reserved. |
| * |
| * This software is licensed as described in the file COPYING, which |
| * you should have received as part of this distribution. The terms |
| * are also available at http://subversion.tigris.org/license-1.html. |
| * If newer versions of this license are posted there, you may use a |
| * newer version instead, at your option. |
| * |
| * This software consists of voluntary contributions made by many |
| * individuals. For exact contribution history, see the revision |
| * history and logs, available at http://subversion.tigris.org/. |
| * ==================================================================== |
| */ |
| |
| #include <stdlib.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <stdio.h> |
| |
| #include <apr.h> |
| |
| #include "svn_pools.h" |
| #include "svn_error.h" |
| |
| #include "../svn_test.h" |
| #include "../svn_test_fs.h" |
| #include "../../libsvn_fs_base/util/skel.h" |
| #include "../../libsvn_fs_base/util/fs_skels.h" |
| #include "../../libsvn_fs_base/bdb/changes-table.h" |
| |
| |
| |
| /* Helper functions/variables. */ |
| static const char *standard_txns[6] |
| = { "0", "1", "2", "3", "4", "5" }; |
| static const char *standard_changes[19][6] |
| /* KEY PATH NODEREVID KIND TEXT PROP */ |
| = { { "0", "/foo", "1.0.0", "add", 0, 0 }, |
| { "0", "/foo", "1.0.0", "modify", 0, "1" }, |
| { "0", "/bar", "2.0.0", "add", 0, 0 }, |
| { "0", "/bar", "2.0.0", "modify", "1", 0 }, |
| { "0", "/bar", "2.0.0", "modify", 0, "1" }, |
| { "0", "/baz", "3.0.0", "add", 0, 0 }, |
| { "0", "/baz", "3.0.0", "modify", "1", 0 }, |
| { "1", "/foo", "1.0.1", "modify", "1", 0 }, |
| { "2", "/foo", "1.0.2", "modify", 0, "1" }, |
| { "2", "/bar", "2.0.2", "modify", "1", 0 }, |
| { "3", "/baz", "3.0.3", "modify", "1", 0 }, |
| { "4", "/fob", "4.0.4", "add", 0, 0 }, |
| { "4", "/fob", "4.0.4", "modify", "1", 0 }, |
| { "5", "/baz", "3.0.3", "delete", 0, 0 }, |
| { "5", "/baz", "5.0.5", "add", 0, "1" }, |
| { "5", "/baz", "5.0.5", "modify", "1", 0 }, |
| { "6", "/fob", "4.0.6", "modify", "1", 0 }, |
| { "6", "/fob", "4.0.6", "reset", 0, 0 }, |
| { "6", "/fob", "4.0.6", "modify", 0, "1" } }; |
| |
| |
| static svn_fs_path_change_kind_t string_to_kind(const char *str) |
| { |
| if (strcmp(str, "add") == 0) |
| return svn_fs_path_change_add; |
| if (strcmp(str, "delete") == 0) |
| return svn_fs_path_change_delete; |
| if (strcmp(str, "replace") == 0) |
| return svn_fs_path_change_replace; |
| if (strcmp(str, "modify") == 0) |
| return svn_fs_path_change_modify; |
| if (strcmp(str, "reset") == 0) |
| return svn_fs_path_change_reset; |
| return 0; |
| } |
| |
| |
| /* Common args structure for several different txn_body_* functions. */ |
| struct changes_args |
| { |
| svn_fs_t *fs; |
| const char *key; |
| change_t *change; |
| apr_array_header_t *raw_changes; |
| apr_hash_t *changes; |
| }; |
| |
| |
| static svn_error_t * |
| txn_body_changes_add(void *baton, trail_t *trail) |
| { |
| struct changes_args *b = baton; |
| return svn_fs_bdb__changes_add(b->fs, b->key, b->change, |
| trail, trail->pool); |
| } |
| |
| |
| static svn_error_t * |
| add_standard_changes(svn_fs_t *fs, |
| apr_pool_t *pool) |
| { |
| int i; |
| struct changes_args args; |
| int num_changes = sizeof(standard_changes) / sizeof(const char *) / 6; |
| |
| for (i = 0; i < num_changes; i++) |
| { |
| change_t change; |
| |
| /* Set up the current change item. */ |
| change.path = standard_changes[i][1]; |
| change.noderev_id = svn_fs_parse_id(standard_changes[i][2], |
| strlen(standard_changes[i][2]), |
| pool); |
| change.kind = string_to_kind(standard_changes[i][3]); |
| change.text_mod = standard_changes[i][4] ? 1 : 0; |
| change.prop_mod = standard_changes[i][5] ? 1 : 0; |
| |
| /* Set up transaction baton. */ |
| args.fs = fs; |
| args.key = standard_changes[i][0]; |
| args.change = &change; |
| |
| /* Write new changes to the changes table. */ |
| SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_add, &args, |
| pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| txn_body_changes_fetch_raw(void *baton, trail_t *trail) |
| { |
| struct changes_args *b = baton; |
| return svn_fs_bdb__changes_fetch_raw(&(b->raw_changes), b->fs, b->key, |
| trail, trail->pool); |
| } |
| |
| |
| static svn_error_t * |
| txn_body_changes_fetch(void *baton, trail_t *trail) |
| { |
| struct changes_args *b = baton; |
| return svn_fs_bdb__changes_fetch(&(b->changes), b->fs, b->key, |
| trail, trail->pool); |
| } |
| |
| |
| static svn_error_t * |
| txn_body_changes_delete(void *baton, trail_t *trail) |
| { |
| struct changes_args *b = baton; |
| return svn_fs_bdb__changes_delete(b->fs, b->key, trail, trail->pool); |
| } |
| |
| |
| |
| /* The tests. */ |
| |
| static svn_error_t * |
| changes_add(const char **msg, |
| svn_boolean_t msg_only, |
| svn_test_opts_t *opts, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| |
| *msg = "add changes to the changes table"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create a new fs and repos */ |
| SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-add", |
| "bdb", pool)); |
| |
| /* Add the standard slew of changes. */ |
| SVN_ERR(add_standard_changes(fs, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| changes_fetch_raw(const char **msg, |
| svn_boolean_t msg_only, |
| svn_test_opts_t *opts, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| int i; |
| int num_txns = sizeof(standard_txns) / sizeof(const char *); |
| int cur_change_index = 0; |
| struct changes_args args; |
| |
| *msg = "fetch raw changes from the changes table"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create a new fs and repos */ |
| SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-fetch", |
| "bdb", pool)); |
| |
| /* First, verify that we can request changes for an arbitrary key |
| without error. */ |
| args.fs = fs; |
| args.key = "blahbliggityblah"; |
| SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_fetch_raw, |
| &args, pool)); |
| if ((! args.raw_changes) || (args.raw_changes->nelts)) |
| return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
| "expected empty changes array"); |
| |
| /* Add the standard slew of changes. */ |
| SVN_ERR(add_standard_changes(fs, pool)); |
| |
| /* For each transaction, fetch that transaction's changes, and |
| compare those changes against the standard changes list. Order |
| matters throughout all the changes code, so we shouldn't have to |
| worry about ordering of the arrays. */ |
| for (i = 0; i < num_txns; i++) |
| { |
| const char *txn_id = standard_txns[i]; |
| int j; |
| |
| /* Setup the trail baton. */ |
| args.fs = fs; |
| args.key = txn_id; |
| |
| /* And get those changes. */ |
| SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_fetch_raw, |
| &args, pool)); |
| if (! args.raw_changes) |
| return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, |
| "got no changes for key '%s'", txn_id); |
| |
| for (j = 0; j < args.raw_changes->nelts; j++) |
| { |
| svn_string_t *noderev_id; |
| svn_fs_path_change_kind_t kind; |
| change_t *change = APR_ARRAY_IDX(args.raw_changes, j, change_t *); |
| int mod_bit = 0; |
| |
| /* Verify that the TXN_ID matches. */ |
| if (strcmp(standard_changes[cur_change_index][0], txn_id)) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "missing some changes for key '%s'", txn_id); |
| |
| /* Verify that the PATH matches. */ |
| if (strcmp(standard_changes[cur_change_index][1], change->path)) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "paths differ in change for key '%s'", txn_id); |
| |
| /* Verify that the NODE-REV-ID matches. */ |
| noderev_id = svn_fs_unparse_id(change->noderev_id, pool); |
| if (strcmp(standard_changes[cur_change_index][2], noderev_id->data)) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "node revision ids differ in change for key '%s'", txn_id); |
| |
| /* Verify that the change KIND matches. */ |
| kind = string_to_kind(standard_changes[cur_change_index][3]); |
| if (kind != change->kind) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "change kinds differ in change for key '%s'", txn_id); |
| |
| /* Verify that the change TEXT-MOD bit matches. */ |
| mod_bit = standard_changes[cur_change_index][4] ? 1 : 0; |
| if (mod_bit != change->text_mod) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "change text-mod bits differ in change for key '%s'", txn_id); |
| |
| /* Verify that the change PROP-MOD bit matches. */ |
| mod_bit = standard_changes[cur_change_index][5] ? 1 : 0; |
| if (mod_bit != change->prop_mod) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "change prop-mod bits differ in change for key '%s'", txn_id); |
| |
| cur_change_index++; |
| } |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| changes_delete(const char **msg, |
| svn_boolean_t msg_only, |
| svn_test_opts_t *opts, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| int i; |
| int num_txns = sizeof(standard_txns) / sizeof(const char *); |
| struct changes_args args; |
| |
| *msg = "delete changes from the changes table"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create a new fs and repos */ |
| SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-delete", |
| "bdb", pool)); |
| |
| /* Add the standard slew of changes. */ |
| SVN_ERR(add_standard_changes(fs, pool)); |
| |
| /* Now, delete all the changes we know about, verifying their removal. */ |
| for (i = 0; i < num_txns; i++) |
| { |
| args.fs = fs; |
| args.key = standard_txns[i]; |
| SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_delete, |
| &args, pool)); |
| args.changes = 0; |
| SVN_ERR(svn_fs_base__retry_txn(args.fs, txn_body_changes_fetch_raw, |
| &args, pool)); |
| if ((! args.raw_changes) || (args.raw_changes->nelts)) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "expected empty changes array for txn '%s'", args.key); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static apr_hash_t * |
| get_ideal_changes(const char *txn_id, |
| apr_pool_t *pool) |
| { |
| apr_hash_t *ideal = apr_hash_make(pool); |
| svn_fs_path_change_t *change; |
| if (strcmp(txn_id, "0") == 0) |
| { |
| change = apr_palloc(pool, sizeof(*change)); |
| change->node_rev_id = svn_fs_parse_id("1.0.0", 5, pool); |
| change->change_kind = svn_fs_path_change_add; |
| change->text_mod = 0; |
| change->prop_mod = 1; |
| apr_hash_set(ideal, "/foo", APR_HASH_KEY_STRING, change); |
| |
| change = apr_palloc(pool, sizeof(*change)); |
| change->node_rev_id = svn_fs_parse_id("2.0.0", 5, pool); |
| change->change_kind = svn_fs_path_change_add; |
| change->text_mod = 1; |
| change->prop_mod = 1; |
| apr_hash_set(ideal, "/bar", APR_HASH_KEY_STRING, change); |
| |
| change = apr_palloc(pool, sizeof(*change)); |
| change->node_rev_id = svn_fs_parse_id("3.0.0", 5, pool); |
| change->change_kind = svn_fs_path_change_add; |
| change->text_mod = 1; |
| change->prop_mod = 0; |
| apr_hash_set(ideal, "/baz", APR_HASH_KEY_STRING, change); |
| } |
| if (strcmp(txn_id, "1") == 0) |
| { |
| change = apr_palloc(pool, sizeof(*change)); |
| change->node_rev_id = svn_fs_parse_id("1.0.1", 5, pool); |
| change->change_kind = svn_fs_path_change_modify; |
| change->text_mod = 1; |
| change->prop_mod = 0; |
| apr_hash_set(ideal, "/foo", APR_HASH_KEY_STRING, change); |
| } |
| if (strcmp(txn_id, "2") == 0) |
| { |
| change = apr_palloc(pool, sizeof(*change)); |
| change->node_rev_id = svn_fs_parse_id("1.0.2", 5, pool); |
| change->change_kind = svn_fs_path_change_modify; |
| change->text_mod = 0; |
| change->prop_mod = 1; |
| apr_hash_set(ideal, "/foo", APR_HASH_KEY_STRING, change); |
| |
| change = apr_palloc(pool, sizeof(*change)); |
| change->node_rev_id = svn_fs_parse_id("2.0.2", 5, pool); |
| change->change_kind = svn_fs_path_change_modify; |
| change->text_mod = 1; |
| change->prop_mod = 0; |
| apr_hash_set(ideal, "/bar", APR_HASH_KEY_STRING, change); |
| } |
| if (strcmp(txn_id, "3") == 0) |
| { |
| change = apr_palloc(pool, sizeof(*change)); |
| change->node_rev_id = svn_fs_parse_id("3.0.3", 5, pool); |
| change->change_kind = svn_fs_path_change_modify; |
| change->text_mod = 1; |
| change->prop_mod = 0; |
| apr_hash_set(ideal, "/baz", APR_HASH_KEY_STRING, change); |
| } |
| if (strcmp(txn_id, "4") == 0) |
| { |
| change = apr_palloc(pool, sizeof(*change)); |
| change->node_rev_id = svn_fs_parse_id("4.0.4", 5, pool); |
| change->change_kind = svn_fs_path_change_add; |
| change->text_mod = 1; |
| change->prop_mod = 0; |
| apr_hash_set(ideal, "/fob", APR_HASH_KEY_STRING, change); |
| } |
| if (strcmp(txn_id, "5") == 0) |
| { |
| change = apr_palloc(pool, sizeof(*change)); |
| change->node_rev_id = svn_fs_parse_id("5.0.5", 5, pool); |
| change->change_kind = svn_fs_path_change_replace; |
| change->text_mod = 1; |
| change->prop_mod = 1; |
| apr_hash_set(ideal, "/baz", APR_HASH_KEY_STRING, change); |
| } |
| if (strcmp(txn_id, "6") == 0) |
| { |
| change = apr_palloc(pool, sizeof(*change)); |
| change->node_rev_id = svn_fs_parse_id("4.0.6", 5, pool); |
| change->change_kind = svn_fs_path_change_modify; |
| change->text_mod = 0; |
| change->prop_mod = 1; |
| apr_hash_set(ideal, "/fob", APR_HASH_KEY_STRING, change); |
| } |
| return ideal; |
| } |
| |
| |
| static svn_error_t * |
| compare_changes(apr_hash_t *ideals, |
| apr_hash_t *changes, |
| svn_test_opts_t *opts, |
| const char *txn_id, |
| apr_pool_t *pool) |
| { |
| apr_hash_index_t *hi; |
| |
| for (hi = apr_hash_first(pool, ideals); hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| svn_fs_path_change_t *ideal_change, *change; |
| const char *path; |
| |
| /* KEY will be the path, VAL the change. */ |
| apr_hash_this(hi, &key, NULL, &val); |
| path = (const char *) key; |
| ideal_change = val; |
| |
| /* Now get the change that refers to PATH in the actual |
| changes hash. */ |
| change = apr_hash_get(changes, path, APR_HASH_KEY_STRING); |
| if (! change) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "missing expected change for path '%s' in txn_id '%s'", |
| path, txn_id); |
| |
| /* Verify that the NODE-REV-ID matches. */ |
| if (svn_fs_compare_ids(change->node_rev_id, |
| ideal_change->node_rev_id)) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "node revision ids differ in change for key '%s'", txn_id); |
| |
| /* Verify that the change KIND matches. */ |
| if (change->change_kind != ideal_change->change_kind) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "change kinds differ in change for key '%s'", txn_id); |
| |
| /* Verify that the change TEXT-MOD bit matches. */ |
| if (change->text_mod != ideal_change->text_mod) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "change text-mod bits differ in change for key '%s'", txn_id); |
| |
| /* Verify that the change PROP-MOD bit matches. */ |
| if (change->prop_mod != ideal_change->prop_mod) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "change prop-mod bits differ in change for key '%s'", txn_id); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| changes_fetch(const char **msg, |
| svn_boolean_t msg_only, |
| svn_test_opts_t *opts, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| int i; |
| int num_txns = sizeof(standard_txns) / sizeof(const char *); |
| struct changes_args args; |
| *msg = "fetch compressed changes from the changes table"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create a new fs and repos */ |
| SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-fetch", |
| "bdb", pool)); |
| |
| /* First, verify that we can request changes for an arbitrary key |
| without error. */ |
| args.fs = fs; |
| args.key = "blahbliggityblah"; |
| SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args, pool)); |
| if ((! args.changes) || (apr_hash_count(args.changes))) |
| return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
| "expected empty changes hash"); |
| |
| /* Add the standard slew of changes. */ |
| SVN_ERR(add_standard_changes(fs, pool)); |
| |
| /* For each transaction, fetch that transaction's changes, and |
| compare those changes against our ideal compressed changes |
| hash. */ |
| for (i = 0; i < num_txns; i++) |
| { |
| const char *txn_id = standard_txns[i]; |
| apr_hash_t *ideals; |
| |
| /* Get the ideal changes hash. */ |
| ideals = get_ideal_changes(txn_id, pool); |
| |
| /* Setup the trail baton. */ |
| args.fs = fs; |
| args.key = txn_id; |
| |
| /* And get those changes via in the internal interface, and |
| verify that they are accurate. */ |
| SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args, |
| pool)); |
| if (! args.changes) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "got no changes for key '%s'", txn_id); |
| if (apr_hash_count(ideals) != apr_hash_count(args.changes)) |
| return svn_error_createf |
| (SVN_ERR_TEST_FAILED, NULL, |
| "unexpected number of changes for key '%s'", txn_id); |
| SVN_ERR(compare_changes(ideals, args.changes, opts, txn_id, pool)); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| static svn_error_t * |
| changes_fetch_ordering(const char **msg, |
| svn_boolean_t msg_only, |
| svn_test_opts_t *opts, |
| apr_pool_t *pool) |
| { |
| svn_fs_t *fs; |
| svn_revnum_t youngest_rev = 0; |
| const char *txn_name; |
| svn_fs_txn_t *txn; |
| svn_fs_root_t *txn_root, *rev_root; |
| struct changes_args args; |
| apr_pool_t *subpool = svn_pool_create(pool); |
| apr_hash_index_t *hi; |
| |
| *msg = "verify ordered-ness of fetched compressed changes"; |
| |
| if (msg_only) |
| return SVN_NO_ERROR; |
| |
| /* Create a new fs and repos */ |
| SVN_ERR(svn_test__create_fs |
| (&fs, "test-repo-changes-fetch-ordering", |
| "bdb", pool)); |
| |
| /*** REVISION 1: Make some files and dirs. ***/ |
| SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
| SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
| { |
| static svn_test__txn_script_command_t script_entries[] = { |
| { 'a', "dir1", 0 }, |
| { 'a', "file1", "This is the file 'file1'.\n" }, |
| { 'a', "dir1/file2", "This is the file 'file2'.\n" }, |
| { 'a', "dir1/file3", "This is the file 'file3'.\n" }, |
| { 'a', "dir1/file4", "This is the file 'file4'.\n" }, |
| }; |
| SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool)); |
| } |
| SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool)); |
| svn_pool_clear(subpool); |
| |
| /*** REVISION 2: Delete and add some stuff, non-depth-first. ***/ |
| SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
| /* Don't use subpool, txn_name is used after subpool is cleared */ |
| SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool)); |
| SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
| { |
| static svn_test__txn_script_command_t script_entries[] = { |
| { 'd', "file1", "This is the file 'file1'.\n" }, |
| { 'd', "dir1/file2", "This is the file 'file2'.\n" }, |
| { 'd', "dir1/file3", "This is the file 'file3'.\n" }, |
| { 'a', "dir1/file5", "This is the file 'file4'.\n" }, |
| { 'a', "dir1/dir2", 0 }, |
| { 'd', "dir1", 0 }, |
| { 'a', "dir3", 0 }, |
| }; |
| SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 7, subpool)); |
| } |
| SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool)); |
| svn_pool_clear(subpool); |
| |
| /*** TEST: We should have only three changes, the deletion of 'file1' |
| the deletion of 'dir1', and the addition of 'dir3'. ***/ |
| args.fs = fs; |
| args.key = txn_name; |
| SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args, |
| subpool)); |
| if ((! args.changes) || (apr_hash_count(args.changes) != 3)) |
| return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
| "expected changes"); |
| for (hi = apr_hash_first(subpool, args.changes); |
| hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| svn_fs_path_change_t *change; |
| |
| /* KEY will be the path, VAL the change. */ |
| apr_hash_this(hi, &key, NULL, &val); |
| change = val; |
| |
| if ((change->change_kind == svn_fs_path_change_add) |
| && (strcmp(key, "/dir3") == 0)) |
| ; |
| else if ((change->change_kind == svn_fs_path_change_delete) |
| && ((strcmp(key, "/dir1") == 0) |
| || (strcmp(key, "/file1") == 0))) |
| ; |
| else |
| return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
| "got wrong changes"); |
| } |
| |
| /*** REVISION 3: Do the same stuff as in revision 1. ***/ |
| SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
| SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
| { |
| static svn_test__txn_script_command_t script_entries[] = { |
| { 'a', "dir1", 0 }, |
| { 'a', "file1", "This is the file 'file1'.\n" }, |
| { 'a', "dir1/file2", "This is the file 'file2'.\n" }, |
| { 'a', "dir1/file3", "This is the file 'file3'.\n" }, |
| { 'a', "dir1/file4", "This is the file 'file4'.\n" }, |
| }; |
| SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool)); |
| } |
| SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool)); |
| svn_pool_clear(subpool); |
| |
| /*** REVISION 4: Do the same stuff as in revision 2, but use a copy |
| overwrite of the top directory (instead of a delete) to test |
| that the 'replace' change type works, too. (And add 'dir4' |
| instead of 'dir3', since 'dir3' still exists). ***/ |
| SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool)); |
| /* Don't use subpool, txn_name is used after subpool is cleared */ |
| SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool)); |
| SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool)); |
| SVN_ERR(svn_fs_revision_root(&rev_root, fs, 1, subpool)); |
| { |
| static svn_test__txn_script_command_t script_entries[] = { |
| { 'd', "file1", "This is the file 'file1'.\n" }, |
| { 'd', "dir1/file2", "This is the file 'file2'.\n" }, |
| { 'd', "dir1/file3", "This is the file 'file3'.\n" }, |
| { 'a', "dir1/file5", "This is the file 'file4'.\n" }, |
| { 'a', "dir1/dir2", 0 }, |
| }; |
| SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool)); |
| SVN_ERR(svn_fs_copy(rev_root, "dir1", txn_root, "dir1", subpool)); |
| SVN_ERR(svn_fs_make_dir(txn_root, "dir4", subpool)); |
| } |
| SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool)); |
| svn_pool_clear(subpool); |
| |
| /*** TEST: We should have only three changes, the deletion of 'file1' |
| the replacement of 'dir1', and the addition of 'dir4'. ***/ |
| args.fs = fs; |
| args.key = txn_name; |
| SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args, |
| subpool)); |
| if ((! args.changes) || (apr_hash_count(args.changes) != 3)) |
| return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
| "expected changes"); |
| for (hi = apr_hash_first(subpool, args.changes); |
| hi; hi = apr_hash_next(hi)) |
| { |
| const void *key; |
| void *val; |
| svn_fs_path_change_t *change; |
| |
| /* KEY will be the path, VAL the change. */ |
| apr_hash_this(hi, &key, NULL, &val); |
| change = val; |
| |
| if ((change->change_kind == svn_fs_path_change_add) |
| && (strcmp(key, "/dir4") == 0)) |
| ; |
| else if ((change->change_kind == svn_fs_path_change_replace) |
| && (strcmp(key, "/dir1") == 0)) |
| ; |
| else if ((change->change_kind == svn_fs_path_change_delete) |
| && (strcmp(key, "/file1") == 0)) |
| ; |
| else |
| return svn_error_create(SVN_ERR_TEST_FAILED, NULL, |
| "got wrong changes"); |
| } |
| |
| return SVN_NO_ERROR; |
| } |
| |
| |
| |
| /* The test table. */ |
| |
| struct svn_test_descriptor_t test_funcs[] = |
| { |
| SVN_TEST_NULL, |
| SVN_TEST_PASS(changes_add), |
| SVN_TEST_PASS(changes_fetch_raw), |
| SVN_TEST_PASS(changes_delete), |
| SVN_TEST_PASS(changes_fetch), |
| SVN_TEST_PASS(changes_fetch_ordering), |
| SVN_TEST_NULL |
| }; |