/* * * 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. * */ /* * conflict-data-test.c -- test the storage of tree conflict data */ #include #include #include #include #include "svn_props.h" #include "svn_pools.h" #include "svn_hash.h" #include "svn_types.h" #include "svn_wc.h" #include "private/svn_wc_private.h" #include "utils.h" #include "../svn_test.h" #include "../../libsvn_wc/tree_conflicts.h" #include "../../libsvn_wc/wc.h" #include "../../libsvn_wc/wc_db.h" #include "../../libsvn_wc/conflicts.h" /* A quick way to create error messages. */ static svn_error_t * fail(apr_pool_t *pool, const char *fmt, ...) { va_list ap; char *msg; va_start(ap, fmt); msg = apr_pvsprintf(pool, fmt, ap); va_end(ap); return svn_error_create(SVN_ERR_TEST_FAILED, 0, msg); } /* Assert that two integers are equal. Return an error if not. */ #define ASSERT_INT_EQ(a, b) \ do { \ if ((a) != (b)) \ return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, \ "failed: ASSERT_INT_EQ(" #a ", " #b ") " \ "-> (%d == %d)", a, b); \ } while (0) /* Assert that two strings are equal or both null. Return an error if not. */ #define ASSERT_STR_EQ(a, b) \ SVN_TEST_STRING_ASSERT(a, b) /* Assert that two version_t's are equal or both null. Return an error if not. */ static svn_error_t * compare_version(const svn_wc_conflict_version_t *actual, const svn_wc_conflict_version_t *expected) { if (actual == NULL && expected == NULL) return SVN_NO_ERROR; SVN_TEST_ASSERT(actual && expected); ASSERT_STR_EQ(actual->repos_url, expected->repos_url); ASSERT_INT_EQ((int)actual->peg_rev, (int)expected->peg_rev); ASSERT_STR_EQ(actual->path_in_repos, expected->path_in_repos); ASSERT_INT_EQ(actual->node_kind, expected->node_kind); return SVN_NO_ERROR; } /* Assert that two conflict descriptions contain exactly the same data * (including names of temporary files), or are both NULL. Return an * error if not. */ static svn_error_t * compare_conflict(const svn_wc_conflict_description2_t *actual, const svn_wc_conflict_description2_t *expected) { if (actual == NULL && expected == NULL) return SVN_NO_ERROR; SVN_TEST_ASSERT(actual && expected); ASSERT_INT_EQ(actual->kind, expected->kind); ASSERT_STR_EQ(actual->local_abspath, expected->local_abspath); ASSERT_INT_EQ(actual->node_kind, expected->node_kind); ASSERT_STR_EQ(actual->property_name, expected->property_name); ASSERT_INT_EQ(actual->is_binary, expected->is_binary); ASSERT_STR_EQ(actual->mime_type, expected->mime_type); ASSERT_INT_EQ(actual->action, expected->action); ASSERT_INT_EQ(actual->reason, expected->reason); ASSERT_STR_EQ(actual->base_abspath, expected->base_abspath); ASSERT_STR_EQ(actual->their_abspath, expected->their_abspath); ASSERT_STR_EQ(actual->my_abspath, expected->my_abspath); ASSERT_STR_EQ(actual->merged_file, expected->merged_file); ASSERT_INT_EQ(actual->operation, expected->operation); SVN_ERR(compare_version(actual->src_left_version, expected->src_left_version)); SVN_ERR(compare_version(actual->src_right_version, expected->src_right_version)); return SVN_NO_ERROR; } /* Assert that a file contains the expected data. Return an * error if not. */ static svn_error_t * compare_file_content(const char *file_abspath, const char *expected_val, apr_pool_t *scratch_pool) { svn_stringbuf_t *actual_val; SVN_ERR(svn_stringbuf_from_file2(&actual_val, file_abspath, scratch_pool)); ASSERT_STR_EQ(actual_val->data, expected_val); return SVN_NO_ERROR; } /* Assert that ACTUAL and EXPECTED both represent the same property * conflict, or are both NULL. Return an error if not. * * Compare the property values found in files named by * ACTUAL->base_abspath, ACTUAL->my_abspath, ACTUAL->merged_file * with EXPECTED_BASE_VAL, EXPECTED_MY_VAL, EXPECTED_THEIR_VAL * respectively, ignoring the corresponding fields in EXPECTED. */ static svn_error_t * compare_prop_conflict(const svn_wc_conflict_description2_t *actual, const svn_wc_conflict_description2_t *expected, const char *expected_base_val, const char *expected_my_val, const char *expected_their_val, apr_pool_t *scratch_pool) { if (actual == NULL && expected == NULL) return SVN_NO_ERROR; SVN_TEST_ASSERT(actual && expected); ASSERT_INT_EQ(actual->kind, svn_wc_conflict_kind_property); ASSERT_INT_EQ(expected->kind, svn_wc_conflict_kind_property); ASSERT_STR_EQ(actual->local_abspath, expected->local_abspath); ASSERT_INT_EQ(actual->node_kind, expected->node_kind); ASSERT_STR_EQ(actual->property_name, expected->property_name); ASSERT_INT_EQ(actual->action, expected->action); ASSERT_INT_EQ(actual->reason, expected->reason); ASSERT_INT_EQ(actual->operation, expected->operation); SVN_ERR(compare_version(actual->src_left_version, expected->src_left_version)); SVN_ERR(compare_version(actual->src_right_version, expected->src_right_version)); SVN_ERR(compare_file_content(actual->base_abspath, expected_base_val, scratch_pool)); SVN_ERR(compare_file_content(actual->my_abspath, expected_my_val, scratch_pool)); /* Historical wart: for a prop conflict, 'theirs' is in the 'merged_file' * field, and the conflict artifact file is in the 'theirs_abspath' field. */ SVN_ERR(compare_file_content(actual->merged_file, expected_their_val, scratch_pool)); /*ASSERT_STR_EQ(actual->theirs_abspath, conflict_artifact_file));*/ /* These are 'undefined' for a prop conflict */ /*ASSERT_INT_EQ(actual->is_binary, expected->is_binary);*/ /*ASSERT_STR_EQ(actual->mime_type, expected->mime_type);*/ return SVN_NO_ERROR; } /* Create and return a tree conflict description */ static svn_wc_conflict_description2_t * tree_conflict_create(const char *local_abspath, svn_node_kind_t node_kind, svn_wc_operation_t operation, svn_wc_conflict_action_t action, svn_wc_conflict_reason_t reason, const char *left_repo, const char *left_path, svn_revnum_t left_revnum, svn_node_kind_t left_kind, const char *right_repo, const char *right_path, svn_revnum_t right_revnum, svn_node_kind_t right_kind, apr_pool_t *result_pool) { svn_wc_conflict_version_t *left, *right; svn_wc_conflict_description2_t *conflict; left = svn_wc_conflict_version_create2(left_repo, NULL, left_path, left_revnum, left_kind, result_pool); right = svn_wc_conflict_version_create2(right_repo, NULL, right_path, right_revnum, right_kind, result_pool); conflict = svn_wc_conflict_description_create_tree2( local_abspath, node_kind, operation, left, right, result_pool); conflict->action = action; conflict->reason = reason; return conflict; } static svn_error_t * test_deserialize_tree_conflict(apr_pool_t *pool) { const svn_wc_conflict_description2_t *conflict; svn_wc_conflict_description2_t *exp_conflict; const char *tree_conflict_data; const char *local_abspath; const svn_skel_t *skel; tree_conflict_data = "(conflict Foo.c file update deleted edited " "(version 0 2 -1 0 0 ) (version 0 2 -1 0 0 ))"; SVN_ERR(svn_dirent_get_absolute(&local_abspath, "Foo.c", pool)); exp_conflict = svn_wc_conflict_description_create_tree2( local_abspath, svn_node_file, svn_wc_operation_update, NULL, NULL, pool); exp_conflict->action = svn_wc_conflict_action_delete; exp_conflict->reason = svn_wc_conflict_reason_edited; skel = svn_skel__parse(tree_conflict_data, strlen(tree_conflict_data), pool); SVN_ERR(svn_wc__deserialize_conflict(&conflict, skel, "", pool, pool)); if ((conflict->node_kind != exp_conflict->node_kind) || (conflict->action != exp_conflict->action) || (conflict->reason != exp_conflict->reason) || (conflict->operation != exp_conflict->operation) || (strcmp(conflict->local_abspath, exp_conflict->local_abspath) != 0)) return fail(pool, "Unexpected tree conflict"); return SVN_NO_ERROR; } static svn_error_t * test_serialize_tree_conflict_data(apr_pool_t *pool) { svn_wc_conflict_description2_t *conflict; const char *tree_conflict_data; const char *expected; const char *local_abspath; svn_skel_t *skel; SVN_ERR(svn_dirent_get_absolute(&local_abspath, "Foo.c", pool)); conflict = svn_wc_conflict_description_create_tree2( local_abspath, svn_node_file, svn_wc_operation_update, NULL, NULL, pool); conflict->action = svn_wc_conflict_action_delete; conflict->reason = svn_wc_conflict_reason_edited; SVN_ERR(svn_wc__serialize_conflict(&skel, conflict, pool, pool)); tree_conflict_data = svn_skel__unparse(skel, pool)->data; expected = "(conflict Foo.c file update deleted edited " "(version 0 2 -1 0 0 ) (version 0 2 -1 0 0 ))"; if (strcmp(expected, tree_conflict_data) != 0) return fail(pool, "Unexpected text from tree conflict\n" " Expected: %s\n" " Actual: %s\n", expected, tree_conflict_data); return SVN_NO_ERROR; } /* Test WC-DB-level conflict APIs. Especially tree conflicts. */ static svn_error_t * test_read_write_tree_conflicts(const svn_test_opts_t *opts, apr_pool_t *pool) { svn_test__sandbox_t sbox; const char *parent_abspath; const char *child1_abspath, *child2_abspath; svn_wc_conflict_description2_t *conflict1, *conflict2; SVN_ERR(svn_test__sandbox_create(&sbox, "read_write_tree_conflicts", opts, pool)); parent_abspath = svn_dirent_join(sbox.wc_abspath, "A", pool); child1_abspath = svn_dirent_join(parent_abspath, "foo", pool); child2_abspath = svn_dirent_join(parent_abspath, "bar", pool); SVN_ERR(sbox_wc_mkdir(&sbox, "A")); SVN_ERR(sbox_wc_mkdir(&sbox, "A/bar")); SVN_ERR(sbox_file_write(&sbox, "A/foo", "")); SVN_ERR(sbox_wc_add(&sbox, "A/foo")); conflict1 = tree_conflict_create(child1_abspath, svn_node_file, svn_wc_operation_merge, svn_wc_conflict_action_delete, svn_wc_conflict_reason_edited, "dummy://localhost", "path/to/foo", 51, svn_node_file, "dummy://localhost", "path/to/foo", 52, svn_node_none, pool); conflict2 = tree_conflict_create(child2_abspath, svn_node_dir, svn_wc_operation_merge, svn_wc_conflict_action_replace, svn_wc_conflict_reason_edited, "dummy://localhost", "path/to/bar", 51, svn_node_dir, "dummy://localhost", "path/to/bar", 52, svn_node_file, pool); /* Write */ SVN_ERR(svn_wc__add_tree_conflict(sbox.wc_ctx, /*child1_abspath,*/ conflict1, pool)); SVN_ERR(svn_wc__add_tree_conflict(sbox.wc_ctx, /*child2_abspath,*/ conflict2, pool)); /* Query (conflict1 through WC-DB API, conflict2 through WC API) */ { svn_boolean_t text_c, prop_c, tree_c; SVN_ERR(svn_wc__internal_conflicted_p(&text_c, &prop_c, &tree_c, sbox.wc_ctx->db, child1_abspath, pool)); SVN_TEST_ASSERT(tree_c); SVN_TEST_ASSERT(! text_c && ! prop_c); SVN_ERR(svn_wc_conflicted_p3(&text_c, &prop_c, &tree_c, sbox.wc_ctx, child2_abspath, pool)); SVN_TEST_ASSERT(tree_c); SVN_TEST_ASSERT(! text_c && ! prop_c); } /* Read conflicts back */ { const svn_wc_conflict_description2_t *read_conflict; SVN_ERR(svn_wc__get_tree_conflict(&read_conflict, sbox.wc_ctx, child1_abspath, pool, pool)); SVN_ERR(compare_conflict(read_conflict, conflict1)); SVN_ERR(svn_wc__get_tree_conflict(&read_conflict, sbox.wc_ctx, child2_abspath, pool, pool)); SVN_ERR(compare_conflict(read_conflict, conflict2)); } /* Read many */ { const apr_array_header_t *victims; SVN_ERR(svn_wc__db_read_conflict_victims(&victims, sbox.wc_ctx->db, parent_abspath, pool, pool)); SVN_TEST_ASSERT(victims->nelts == 2); } /* ### TODO: to test... * svn_wc__db_read_conflicts * svn_wc__node_get_conflict_info * svn_wc__del_tree_conflict */ return SVN_NO_ERROR; } static svn_error_t * test_serialize_prop_conflict(const svn_test_opts_t *opts, apr_pool_t *pool) { svn_test__sandbox_t sbox; svn_skel_t *conflict_skel; svn_boolean_t complete; SVN_ERR(svn_test__sandbox_create(&sbox, "test_serialize_prop_conflict", opts, pool)); conflict_skel = svn_wc__conflict_skel_create(pool); SVN_TEST_ASSERT(conflict_skel != NULL); SVN_TEST_ASSERT(svn_skel__list_length(conflict_skel) == 2); SVN_ERR(svn_wc__conflict_skel_is_complete(&complete, conflict_skel)); SVN_TEST_ASSERT(!complete); /* Nothing set */ { apr_hash_t *mine = apr_hash_make(pool); apr_hash_t *their_old = apr_hash_make(pool); apr_hash_t *theirs = apr_hash_make(pool); apr_hash_t *conflicts = apr_hash_make(pool); const char *marker_abspath; apr_hash_set(mine, "prop", APR_HASH_KEY_STRING, svn_string_create("Mine", pool)); apr_hash_set(their_old, "prop", APR_HASH_KEY_STRING, svn_string_create("Their-Old", pool)); apr_hash_set(theirs, "prop", APR_HASH_KEY_STRING, svn_string_create("Theirs", pool)); apr_hash_set(conflicts, "prop", APR_HASH_KEY_STRING, ""); SVN_ERR(svn_io_open_unique_file3(NULL, &marker_abspath, sbox.wc_abspath, svn_io_file_del_on_pool_cleanup, pool, pool)); SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflict_skel, sbox.wc_ctx->db, sbox.wc_abspath, marker_abspath, mine, their_old, theirs, conflicts, pool, pool)); } SVN_ERR(svn_wc__conflict_skel_is_complete(&complete, conflict_skel)); SVN_TEST_ASSERT(!complete); /* Misses operation */ SVN_ERR(svn_wc__conflict_skel_set_op_update( conflict_skel, svn_wc_conflict_version_create2("http://my-repos/svn", "uuid", "trunk", 12, svn_node_dir, pool), NULL /* wc_only */, pool, pool)); SVN_ERR(svn_wc__conflict_skel_is_complete(&complete, conflict_skel)); SVN_TEST_ASSERT(complete); /* Everything available */ { apr_hash_t *mine; apr_hash_t *their_old; apr_hash_t *theirs; apr_hash_t *conflicts; const char *marker_abspath; svn_string_t *v; SVN_ERR(svn_wc__conflict_read_prop_conflict(&marker_abspath, &mine, &their_old, &theirs, &conflicts, sbox.wc_ctx->db, sbox.wc_abspath, conflict_skel, pool, pool)); SVN_TEST_ASSERT(svn_dirent_is_ancestor(sbox.wc_abspath, marker_abspath)); v = apr_hash_get(mine, "prop", APR_HASH_KEY_STRING); SVN_TEST_STRING_ASSERT(v->data, "Mine"); v = apr_hash_get(their_old, "prop", APR_HASH_KEY_STRING); SVN_TEST_STRING_ASSERT(v->data, "Their-Old"); v = apr_hash_get(theirs, "prop", APR_HASH_KEY_STRING); SVN_TEST_STRING_ASSERT(v->data, "Theirs"); SVN_TEST_ASSERT(apr_hash_count(conflicts) == 1); } return SVN_NO_ERROR; } static svn_error_t * test_serialize_text_conflict(const svn_test_opts_t *opts, apr_pool_t *pool) { svn_test__sandbox_t sbox; svn_skel_t *conflict_skel; svn_boolean_t complete; SVN_ERR(svn_test__sandbox_create(&sbox, "test_serialize_text_conflict", opts, pool)); conflict_skel = svn_wc__conflict_skel_create(pool); SVN_ERR(svn_wc__conflict_skel_add_text_conflict( conflict_skel, sbox.wc_ctx->db, sbox.wc_abspath, svn_dirent_join(sbox.wc_abspath, "mine", pool), svn_dirent_join(sbox.wc_abspath, "old-theirs", pool), svn_dirent_join(sbox.wc_abspath, "theirs", pool), pool, pool)); SVN_ERR(svn_wc__conflict_skel_set_op_merge( conflict_skel, svn_wc_conflict_version_create2("http://my-repos/svn", "uuid", "trunk", 12, svn_node_dir, pool), svn_wc_conflict_version_create2("http://my-repos/svn", "uuid", "branch/my", 8, svn_node_dir, pool), pool, pool)); SVN_ERR(svn_wc__conflict_skel_is_complete(&complete, conflict_skel)); SVN_TEST_ASSERT(complete); /* Everything available */ { const char *mine_abspath; const char *old_their_abspath; const char *their_abspath; SVN_ERR(svn_wc__conflict_read_text_conflict(&mine_abspath, &old_their_abspath, &their_abspath, sbox.wc_ctx->db, sbox.wc_abspath, conflict_skel, pool, pool)); SVN_TEST_STRING_ASSERT( svn_dirent_skip_ancestor(sbox.wc_abspath, mine_abspath), "mine"); SVN_TEST_STRING_ASSERT( svn_dirent_skip_ancestor(sbox.wc_abspath, old_their_abspath), "old-theirs"); SVN_TEST_STRING_ASSERT( svn_dirent_skip_ancestor(sbox.wc_abspath, their_abspath), "theirs"); } { svn_wc_operation_t operation; svn_boolean_t text_conflicted; const apr_array_header_t *locs; SVN_ERR(svn_wc__conflict_read_info(&operation, &locs, &text_conflicted, NULL, NULL, sbox.wc_ctx->db, sbox.wc_abspath, conflict_skel, pool, pool)); SVN_TEST_ASSERT(text_conflicted); SVN_TEST_ASSERT(operation == svn_wc_operation_merge); SVN_TEST_ASSERT(locs != NULL && locs->nelts == 2); SVN_TEST_ASSERT(APR_ARRAY_IDX(locs, 0, svn_wc_conflict_version_t*) != NULL); SVN_TEST_ASSERT(APR_ARRAY_IDX(locs, 1, svn_wc_conflict_version_t*) != NULL); } { const apr_array_header_t *markers; const char *old_their_abspath; const char *their_abspath; const char *mine_abspath; SVN_ERR(svn_wc__conflict_read_markers(&markers, sbox.wc_ctx->db, sbox.wc_abspath, conflict_skel, pool, pool)); SVN_TEST_ASSERT(markers != NULL); SVN_TEST_ASSERT(markers->nelts == 3); old_their_abspath = APR_ARRAY_IDX(markers, 0, const char *); mine_abspath = APR_ARRAY_IDX(markers, 1, const char *); their_abspath = APR_ARRAY_IDX(markers, 2, const char *); SVN_TEST_STRING_ASSERT( svn_dirent_skip_ancestor(sbox.wc_abspath, mine_abspath), "mine"); SVN_TEST_STRING_ASSERT( svn_dirent_skip_ancestor(sbox.wc_abspath, old_their_abspath), "old-theirs"); SVN_TEST_STRING_ASSERT( svn_dirent_skip_ancestor(sbox.wc_abspath, their_abspath), "theirs"); } return SVN_NO_ERROR; } static svn_error_t * test_serialize_tree_conflict(const svn_test_opts_t *opts, apr_pool_t *pool) { svn_test__sandbox_t sbox; svn_skel_t *conflict_skel; svn_boolean_t complete; SVN_ERR(svn_test__sandbox_create(&sbox, "test_serialize_tree_conflict", opts, pool)); conflict_skel = svn_wc__conflict_skel_create(pool); SVN_ERR(svn_wc__conflict_skel_add_tree_conflict( conflict_skel, sbox.wc_ctx->db, sbox_wc_path(&sbox, "A/B"), svn_wc_conflict_reason_moved_away, svn_wc_conflict_action_delete, sbox_wc_path(&sbox, "A/B"), pool, pool)); SVN_ERR(svn_wc__conflict_skel_set_op_switch( conflict_skel, svn_wc_conflict_version_create2("http://my-repos/svn", "uuid", "trunk", 12, svn_node_dir, pool), NULL /* wc_only */, pool, pool)); SVN_ERR(svn_wc__conflict_skel_is_complete(&complete, conflict_skel)); SVN_TEST_ASSERT(complete); /* Everything available */ { svn_wc_conflict_reason_t reason; svn_wc_conflict_action_t action; const char *moved_away_op_root_abspath; SVN_ERR(svn_wc__conflict_read_tree_conflict(&reason, &action, &moved_away_op_root_abspath, sbox.wc_ctx->db, sbox.wc_abspath, conflict_skel, pool, pool)); SVN_TEST_ASSERT(reason == svn_wc_conflict_reason_moved_away); SVN_TEST_ASSERT(action == svn_wc_conflict_action_delete); SVN_TEST_STRING_ASSERT(moved_away_op_root_abspath, sbox_wc_path(&sbox, "A/B")); } return SVN_NO_ERROR; } /* A conflict resolver callback baton for test_prop_conflicts(). */ typedef struct test_prop_conflict_baton_t { /* Sets of properties. */ apr_hash_t *mine; apr_hash_t *their_old; apr_hash_t *theirs; /* The set of prop names in conflict. */ apr_hash_t *conflicts; /* We use all the fields of DESC except the base/theirs/mine/merged paths. */ svn_wc_conflict_description2_t *desc; int conflicts_seen; } test_prop_conflict_baton_t; /* Set *CONFLICT_SKEL_P to a new property conflict skel reflecting the * conflict details given in B. */ static svn_error_t * create_prop_conflict_skel(svn_skel_t **conflict_skel_p, svn_wc_context_t *wc_ctx, const test_prop_conflict_baton_t *b, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_skel_t *conflict_skel = svn_wc__conflict_skel_create(result_pool); const char *marker_abspath; svn_boolean_t complete; SVN_ERR(svn_io_write_unique(&marker_abspath, b->desc->local_abspath, "conflict-artifact-file-content\n", 6, svn_io_file_del_none, scratch_pool)); SVN_ERR(svn_wc__conflict_skel_add_prop_conflict(conflict_skel, wc_ctx->db, b->desc->local_abspath, marker_abspath, b->mine, b->their_old, b->theirs, b->conflicts, result_pool, scratch_pool)); switch (b->desc->operation) { case svn_wc_operation_update: SVN_ERR(svn_wc__conflict_skel_set_op_update( conflict_skel, b->desc->src_left_version, b->desc->src_right_version, result_pool, scratch_pool)); break; case svn_wc_operation_switch: SVN_ERR(svn_wc__conflict_skel_set_op_switch( conflict_skel, b->desc->src_left_version, b->desc->src_right_version, result_pool, scratch_pool)); break; case svn_wc_operation_merge: SVN_ERR(svn_wc__conflict_skel_set_op_merge( conflict_skel, b->desc->src_left_version, b->desc->src_right_version, result_pool, scratch_pool)); break; default: SVN_ERR_MALFUNCTION(); } SVN_ERR(svn_wc__conflict_skel_is_complete(&complete, conflict_skel)); SVN_TEST_ASSERT(complete); *conflict_skel_p = conflict_skel; return SVN_NO_ERROR; } /* A conflict resolver callback for test_prop_conflicts(), that checks * that the conflict described to it matches the one described in BATON, * and also counts the number of times it is called. */ static svn_error_t * prop_conflict_cb(svn_wc_conflict_result_t **result_p, const svn_wc_conflict_description2_t *desc, void *baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { test_prop_conflict_baton_t *b = baton; SVN_ERR(compare_prop_conflict( desc, b->desc, svn_prop_get_value(b->their_old, desc->property_name), svn_prop_get_value(b->mine, desc->property_name), svn_prop_get_value(b->theirs, desc->property_name), scratch_pool)); b->conflicts_seen++; *result_p = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone, NULL /*merged_file*/, result_pool); return SVN_NO_ERROR; } /* Test for correct retrieval of property conflict descriptions from * the WC DB. * * Presently it tests just one prop conflict, and only during the * 'resolve' operation. We should also test during the 'update'/ * 'switch'/'merge' operations. */ static svn_error_t * test_prop_conflicts(const svn_test_opts_t *opts, apr_pool_t *pool) { svn_test__sandbox_t sbox; svn_skel_t *conflict_skel; svn_error_t *err; const char *lock_abspath; test_prop_conflict_baton_t *b = apr_pcalloc(pool, sizeof(*b)); svn_wc_conflict_description2_t *desc = apr_pcalloc(pool, sizeof(*desc)); SVN_ERR(svn_test__sandbox_create(&sbox, "test_prop_conflicts", opts, pool)); /* Describe a property conflict */ b->mine = apr_hash_make(pool); b->their_old = apr_hash_make(pool); b->theirs = apr_hash_make(pool); b->conflicts = apr_hash_make(pool); svn_hash_sets(b->mine, "prop", svn_string_create("Mine", pool)); svn_hash_sets(b->their_old, "prop", svn_string_create("Their-Old", pool)); svn_hash_sets(b->theirs, "prop", svn_string_create("Theirs", pool)); svn_hash_sets(b->conflicts, "prop", ""); b->desc = desc; desc->local_abspath = sbox.wc_abspath; desc->kind = svn_wc_conflict_kind_property; desc->node_kind = svn_node_dir; desc->operation = svn_wc_operation_update; desc->action = svn_wc_conflict_action_edit; desc->reason = svn_wc_conflict_reason_edited; desc->mime_type = NULL; desc->is_binary = FALSE; desc->property_name = "prop"; desc->src_left_version = svn_wc_conflict_version_create2(sbox.repos_url, "uuid", "trunk", 12, svn_node_dir, pool); desc->src_right_version = NULL; /* WC only */ b->conflicts_seen = 0; /* Record a conflict */ { apr_pool_t *subpool = svn_pool_create(pool); SVN_ERR(create_prop_conflict_skel(&conflict_skel, sbox.wc_ctx, b, pool, subpool)); svn_pool_clear(subpool); SVN_ERR(svn_wc__db_op_mark_conflict(sbox.wc_ctx->db, sbox.wc_abspath, conflict_skel, NULL, subpool)); svn_pool_destroy(subpool); } /* Test the API for resolving the conflict: check that correct details * of the conflict are returned. */ SVN_ERR(svn_wc__acquire_write_lock_for_resolve(&lock_abspath, sbox.wc_ctx, sbox.wc_abspath, pool, pool)); err = svn_wc__resolve_conflicts(sbox.wc_ctx, sbox.wc_abspath, svn_depth_empty, FALSE /* resolve_text */, "" /* resolve_prop (ALL props) */, FALSE /* resolve_tree */, svn_wc_conflict_choose_unspecified, prop_conflict_cb, b, NULL, NULL, /* cancellation */ NULL, NULL, /* notification */ pool); SVN_ERR(svn_error_compose_create(err, svn_wc__release_write_lock(sbox.wc_ctx, lock_abspath, pool))); ASSERT_INT_EQ(b->conflicts_seen, 1); return SVN_NO_ERROR; } static svn_error_t * test_prop_conflict_resolving(const svn_test_opts_t *opts, apr_pool_t *pool) { svn_test__sandbox_t b; svn_skel_t *conflict; const char *A_abspath; const char *marker_abspath; apr_hash_t *conflicted_props; apr_hash_t *props; const char *value; SVN_ERR(svn_test__sandbox_create(&b, "test_prop_resolving", opts, pool)); SVN_ERR(sbox_wc_mkdir(&b, "A")); SVN_ERR(sbox_wc_propset(&b, "prop-1", "r1", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-2", "r1", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-3", "r1", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-4", "r1", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-5", "r1", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-6", "r1", "A")); SVN_ERR(sbox_wc_commit(&b, "")); SVN_ERR(sbox_wc_propset(&b, "prop-1", "r2", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-2", "r2", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-3", "r2", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-4", NULL, "A")); SVN_ERR(sbox_wc_propset(&b, "prop-5", NULL, "A")); SVN_ERR(sbox_wc_propset(&b, "prop-7", "r2", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-8", "r2", "A")); SVN_ERR(sbox_wc_commit(&b, "")); SVN_ERR(sbox_wc_propset(&b, "prop-1", "mod", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-2", "mod", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-3", "mod", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-4", "mod", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-5", "mod", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-6", "mod", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-7", "mod", "A")); SVN_ERR(sbox_wc_propset(&b, "prop-8", "mod", "A")); SVN_ERR(sbox_wc_update(&b, "", 1)); A_abspath = sbox_wc_path(&b, "A"); SVN_ERR(svn_wc__db_read_conflict(&conflict, NULL, NULL, b.wc_ctx->db, A_abspath, pool, pool)); /* We have tree conflicts... */ SVN_TEST_ASSERT(conflict != NULL); SVN_ERR(svn_wc__conflict_read_prop_conflict(&marker_abspath, NULL, NULL, NULL, &conflicted_props, b.wc_ctx->db, A_abspath, conflict, pool, pool)); SVN_TEST_ASSERT(conflicted_props != NULL); /* All properties but r6 are conflicted */ SVN_TEST_ASSERT(apr_hash_count(conflicted_props) == 7); SVN_TEST_ASSERT(! svn_hash_gets(conflicted_props, "prop-6")); /* Let's resolve a few conflicts */ SVN_ERR(sbox_wc_resolve_prop(&b, "A", "prop-1", svn_wc_conflict_choose_mine_conflict)); SVN_ERR(sbox_wc_resolve_prop(&b, "A", "prop-2", svn_wc_conflict_choose_theirs_conflict)); SVN_ERR(sbox_wc_resolve_prop(&b, "A", "prop-3", svn_wc_conflict_choose_merged)); SVN_ERR(svn_wc__db_read_conflict(&conflict, NULL, NULL, b.wc_ctx->db, A_abspath, pool, pool)); /* We have tree conflicts... */ SVN_TEST_ASSERT(conflict != NULL); SVN_ERR(svn_wc__conflict_read_prop_conflict(&marker_abspath, NULL, NULL, NULL, &conflicted_props, b.wc_ctx->db, A_abspath, conflict, pool, pool)); SVN_TEST_ASSERT(conflicted_props != NULL); SVN_TEST_ASSERT(apr_hash_count(conflicted_props) == 4); SVN_ERR(svn_wc__db_read_props(&props, b.wc_ctx->db, A_abspath, pool, pool)); value = svn_prop_get_value(props, "prop-1"); SVN_TEST_STRING_ASSERT(value, "mod"); value = svn_prop_get_value(props, "prop-2"); SVN_TEST_STRING_ASSERT(value, "r1"); value = svn_prop_get_value(props, "prop-3"); SVN_TEST_STRING_ASSERT(value, "mod"); return SVN_NO_ERROR; } static svn_error_t * test_binary_file_conflict(const svn_test_opts_t *opts, apr_pool_t *pool) { svn_test__sandbox_t sbox; const apr_array_header_t *conflicts; svn_wc_conflict_description2_t *desc; SVN_ERR(svn_test__sandbox_create(&sbox, "test_binary_file_conflict", opts, pool)); /* Create and add a binary file. */ SVN_ERR(sbox_file_write(&sbox, "binary-file", "\xff\xff")); SVN_ERR(sbox_wc_add(&sbox, "binary-file")); SVN_ERR(sbox_wc_propset(&sbox, SVN_PROP_MIME_TYPE, "application/octet-stream", "binary-file")); SVN_ERR(sbox_wc_commit(&sbox, "binary-file")); /* r1 */ /* Make a change to the binary file. */ SVN_ERR(sbox_file_write(&sbox, "binary-file", "\xfc\xfc\xfc\xfc\xfc\xfc")); SVN_ERR(sbox_wc_commit(&sbox, "binary-file")); /* r2 */ /* Update back to r1, make a conflicting change to binary file. */ SVN_ERR(sbox_wc_update(&sbox, "binary-file", 1)); SVN_ERR(sbox_file_write(&sbox, "binary-file", "\xfd\xfd\xfd\xfd")); /* Update to HEAD and ensure the conflict is marked as binary. */ SVN_ERR(sbox_wc_update(&sbox, "binary-file", 2)); SVN_ERR(svn_wc__read_conflicts(&conflicts, NULL, sbox.wc_ctx->db, sbox_wc_path(&sbox, "binary-file"), FALSE /* create_tempfiles */, FALSE /* only_tree_conflict */, pool, pool)); SVN_TEST_ASSERT(conflicts->nelts == 1); desc = APR_ARRAY_IDX(conflicts, 0, svn_wc_conflict_description2_t *); SVN_TEST_ASSERT(desc->is_binary); return SVN_NO_ERROR; } /* The test table. */ static int max_threads = 1; static struct svn_test_descriptor_t test_funcs[] = { SVN_TEST_NULL, SVN_TEST_PASS2(test_deserialize_tree_conflict, "deserialize tree conflict"), SVN_TEST_PASS2(test_serialize_tree_conflict_data, "serialize tree conflict data"), SVN_TEST_OPTS_PASS(test_read_write_tree_conflicts, "read and write tree conflict data"), SVN_TEST_OPTS_PASS(test_serialize_prop_conflict, "read and write a property conflict"), SVN_TEST_OPTS_PASS(test_serialize_text_conflict, "read and write a text conflict"), SVN_TEST_OPTS_PASS(test_serialize_tree_conflict, "read and write a tree conflict"), SVN_TEST_OPTS_PASS(test_prop_conflicts, "test prop conflicts"), SVN_TEST_OPTS_PASS(test_prop_conflict_resolving, "test property conflict resolving"), SVN_TEST_OPTS_PASS(test_binary_file_conflict, "test binary file conflict"), SVN_TEST_NULL }; SVN_TEST_MAIN