| /* |
| * Regression tests for the diff/diff3 library -- parsing unidiffs |
| * |
| * ==================================================================== |
| * 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 "../svn_test.h" |
| |
| #include "svn_diff.h" |
| #include "svn_hash.h" |
| #include "svn_mergeinfo.h" |
| #include "svn_pools.h" |
| #include "svn_utf.h" |
| |
| /* Used to terminate lines in large multi-line string literals. */ |
| #define NL APR_EOL_STR |
| |
| static const char *unidiff = |
| "Index: A/mu (deleted)" NL |
| "===================================================================" NL |
| "Index: A/C/gamma" NL |
| "===================================================================" NL |
| "--- A/C/gamma\t(revision 2)" NL |
| "+++ A/C/gamma\t(working copy)" NL |
| "@@ -1 +1,2 @@" NL |
| " This is the file 'gamma'." NL |
| "+some more bytes to 'gamma'" NL |
| "Index: A/D/gamma" NL |
| "===================================================================" NL |
| "--- A/D/gamma.orig" NL |
| "+++ A/D/gamma" NL |
| "@@ -1,2 +1 @@" NL |
| " This is the file 'gamma'." NL |
| "-some less bytes to 'gamma'" NL |
| "" NL |
| "Property changes on: mu-ng" NL |
| "___________________________________________________________________" NL |
| "Name: newprop" NL |
| " + newpropval" NL |
| "Name: svn:mergeinfo" NL |
| "" NL; |
| |
| static const char *git_unidiff = |
| "Index: A/mu (deleted)" NL |
| "===================================================================" NL |
| "diff --git a/A/mu b/A/mu" NL |
| "deleted file mode 100644" NL |
| "Index: A/C/gamma" NL |
| "===================================================================" NL |
| "diff --git a/A/C/gamma b/A/C/gamma" NL |
| "old mode 100644" NL |
| "new mode 100755" NL |
| "--- a/A/C/gamma\t(revision 2)" NL |
| "+++ b/A/C/gamma\t(working copy)" NL |
| "@@ -1 +1,2 @@" NL |
| " This is the file 'gamma'." NL |
| "+some more bytes to 'gamma'" NL |
| "Index: iota" NL |
| "===================================================================" NL |
| "diff --git a/iota b/iota.copied" NL |
| "copy from iota" NL |
| "copy to iota.copied" NL |
| "Index: new" NL |
| "===================================================================" NL |
| "diff --git a/new b/new" NL |
| "new file mode 100644" NL |
| "" NL; |
| |
| static const char *git_tree_and_text_unidiff = |
| "Index: iota.copied" NL |
| "===================================================================" NL |
| "diff --git a/iota b/iota.copied" NL |
| "old mode 100644" NL |
| "new mode 100755" NL |
| "copy from iota" NL |
| "copy to iota.copied" NL |
| "--- a/iota\t(revision 2)" NL |
| "+++ b/iota.copied\t(working copy)" NL |
| "@@ -1 +1,2 @@" NL |
| " This is the file 'iota'." NL |
| "+some more bytes to 'iota'" NL |
| "Index: A/mu.moved" NL |
| "===================================================================" NL |
| "diff --git a/A/mu b/A/mu.moved" NL |
| "old mode 100644" NL |
| "new mode 100755" NL |
| "rename from A/mu" NL |
| "rename to A/mu.moved" NL |
| "--- a/A/mu\t(revision 2)" NL |
| "+++ b/A/mu.moved\t(working copy)" NL |
| "@@ -1 +1,2 @@" NL |
| " This is the file 'mu'." NL |
| "+some more bytes to 'mu'" NL |
| "Index: new" NL |
| "===================================================================" NL |
| "diff --git a/new b/new" NL |
| "new file mode 100644" NL |
| "--- /dev/null\t(revision 0)" NL |
| "+++ b/new\t(working copy)" NL |
| "@@ -0,0 +1 @@" NL |
| "+This is the file 'new'." NL |
| "Index: A/B/lambda" NL |
| "===================================================================" NL |
| "diff --git a/A/B/lambda b/A/B/lambda" NL |
| "deleted file mode 100755" NL |
| "--- a/A/B/lambda\t(revision 2)" NL |
| "+++ /dev/null\t(working copy)" NL |
| "@@ -1 +0,0 @@" NL |
| "-This is the file 'lambda'." NL |
| "" NL; |
| |
| /* Only the last git diff header is valid. The other ones either misses a |
| * path element or has noise between lines that must be continous. See |
| * issue #3809. */ |
| static const char *bad_git_diff_header = |
| "Index: iota.copied" NL |
| "===================================================================" NL |
| "diff --git a/foo1 b/" NL |
| "diff --git a/foo2 b" NL |
| "diff --git a/foo3 " NL |
| "diff --git a/foo3 " NL |
| "diff --git foo4 b/foo4" NL |
| "diff --git a/foo5 b/foo5" NL |
| "random noise" NL |
| "diff --git a/foo6 b/foo6" NL |
| "copy from foo6" NL |
| "random noise" NL |
| "copy to foo6" NL |
| "diff --git a/foo6 b/foo6" NL |
| "copy from foo6" NL |
| "diff --git a/iota b/iota.copied" NL |
| "copy from iota" NL |
| "copy to iota.copied" NL |
| "@@ -1 +1,2 @@" NL |
| " This is the file 'iota'." NL |
| "+some more bytes to 'iota'" NL |
| "" NL; |
| |
| static const char *property_unidiff = |
| "Index: iota" NL |
| "===================================================================" NL |
| "--- iota" NL |
| "+++ iota" NL |
| "" NL |
| "Property changes on: iota" NL |
| "___________________________________________________________________" NL |
| "Deleted: prop_del" NL |
| "## -1 +0,0 ##" NL |
| "-value" NL |
| "" NL |
| "Property changes on: iota" NL |
| "___________________________________________________________________" NL |
| "Added: prop_add" NL |
| "## -0,0 +1 ##" NL |
| "+value" NL |
| "" NL |
| "Property changes on: iota" NL |
| "___________________________________________________________________" NL |
| "Modified: prop_mod" NL |
| "## -1,4 +1,4 ##" NL |
| "-value" NL |
| "+new value" NL |
| " context" NL |
| " context" NL |
| " context" NL |
| "## -10,4 +10,4 ##" NL |
| " context" NL |
| " context" NL |
| " context" NL |
| "-value" NL |
| "+new value" NL |
| "" NL; |
| |
| /* ### Add edge cases like context lines stripped from leading whitespaces |
| * ### that starts with 'Added: ', 'Deleted: ' or 'Modified: '. */ |
| static const char *property_and_text_unidiff = |
| "Index: iota" NL |
| "===================================================================" NL |
| "--- iota" NL |
| "+++ iota" NL |
| "@@ -1 +1,2 @@" NL |
| " This is the file 'iota'." NL |
| "+some more bytes to 'iota'" NL |
| "" NL |
| "Property changes on: iota" NL |
| "___________________________________________________________________" NL |
| "Added: prop_add" NL |
| "## -0,0 +1 ##" NL |
| "+value" NL; |
| |
| /* A unidiff containing diff symbols in the body of the hunks. */ |
| static const char *diff_symbols_in_prop_unidiff = |
| "Index: iota" NL |
| "===================================================================" NL |
| "--- iota" NL |
| "+++ iota" NL |
| "" NL |
| "Property changes on: iota" NL |
| "___________________________________________________________________" NL |
| "Added: prop_add" NL |
| "## -0,0 +1,3 ##" NL |
| "+Added: bogus_prop" NL |
| "+## -0,0 +20 ##" NL |
| "+@@ -1,2 +0,0 @@" NL |
| "Deleted: prop_del" NL |
| "## -1,2 +0,0 ##" NL |
| "---- iota" NL |
| "-+++ iota" NL |
| "Modified: non-existent" NL |
| "blah, just noise - no valid hunk header" NL |
| "Modified: prop_mod" NL |
| "## -1,4 +1,4 ##" NL |
| "-## -1,2 +1,2 ##" NL |
| "+## -1,3 +1,3 ##" NL |
| " ## -1,5 -0,0 ##" NL |
| " @@ -1,5 -0,0 @@" NL |
| " Modified: prop_mod" NL |
| "## -10,4 +10,4 ##" NL |
| " context" NL |
| " context" NL |
| " context" NL |
| "-## -0,0 +1 ##" NL |
| "+## -1,2 +1,4 ##" NL |
| "" NL; |
| |
| /* A unidiff containing paths with spaces. */ |
| static const char *path_with_spaces_unidiff = |
| "diff --git a/path 1 b/path 1" NL |
| "new file mode 100644" NL |
| "diff --git a/path one 1 b/path one 1" NL |
| "new file mode 100644" NL |
| "diff --git a/dir/ b/path b/dir/ b/path" NL |
| "new file mode 100644" NL |
| "diff --git a/ b/path 1 b/ b/path 1" NL |
| "new file mode 100644" NL; |
| |
| static const char *unidiff_lacking_trailing_eol = |
| "Index: A/C/gamma" NL |
| "===================================================================" NL |
| "--- A/C/gamma\t(revision 2)" NL |
| "+++ A/C/gamma\t(working copy)" NL |
| "@@ -1 +1,2 @@" NL |
| " This is the file 'gamma'." NL |
| "+some more bytes to 'gamma'"; /* Don't add NL after this line */ |
| |
| static const char *unidiff_with_mergeinfo = |
| "Index: A/C" NL |
| "===================================================================" NL |
| "--- A/C\t(revision 2)" NL |
| "+++ A/C\t(working copy)" NL |
| "Modified: svn:ignore" NL |
| "## -7,6 +7,7 ##" NL |
| " configure" NL |
| " libtool" NL |
| " .gdb_history" NL |
| "+.swig_checked" NL |
| " *.orig" NL |
| " *.rej" NL |
| " TAGS" NL |
| "Modified: svn:mergeinfo" NL |
| "## -0,1 +0,3 ##" NL |
| " Reverse-merged /subversion/branches/1.6.x-r935631:r952683-955333" NL |
| " /subversion/branches/nfc-nfd-aware-client:r870276,870376 をマージしました"NL |
| " Fusionné /subversion/branches/1.7.x-r1507044:r1507300-1511568" NL |
| " Merged /subversion/branches/1.8.x-openssl-dirs:r1535139" NL; |
| /* The above diff intentionally contains i18n versions of some lines. */ |
| |
| /* Create a PATCH_FILE containing the contents of DIFF. */ |
| static svn_error_t * |
| create_patch_file(svn_patch_file_t **patch_file, |
| const char *diff, apr_pool_t *pool) |
| { |
| apr_size_t bytes; |
| apr_size_t len; |
| const char *path; |
| apr_file_t *apr_file; |
| |
| /* Create a patch file. */ |
| SVN_ERR(svn_io_open_unique_file3(&apr_file, &path, NULL, |
| svn_io_file_del_on_pool_cleanup, |
| pool, pool)); |
| |
| bytes = strlen(diff); |
| SVN_ERR(svn_io_file_write_full(apr_file, diff, bytes, &len, pool)); |
| if (len != bytes) |
| return svn_error_createf(SVN_ERR_TEST_FAILED, NULL, |
| "Cannot write to '%s'", path); |
| SVN_ERR(svn_io_file_close(apr_file, pool)); |
| SVN_ERR(svn_diff_open_patch_file(patch_file, path, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* svn_stream_readline() with hunk reader semantics */ |
| static svn_error_t * |
| stream_readline_diff(svn_stream_t *stream, |
| svn_stringbuf_t **buf, |
| const char *eol, |
| svn_boolean_t *eof, |
| apr_pool_t *result_pool) |
| { |
| SVN_ERR(svn_stream_readline(stream, buf, eol, eof, result_pool)); |
| |
| /* Hunks are only at EOF after they are completely read, even if |
| they don't have a final EOL in the text */ |
| if (*eof && (*buf)->len) |
| *eof = FALSE; |
| |
| return SVN_NO_ERROR; |
| } |
| |
| /* Check that reading a line from HUNK equals what's inside EXPECTED. |
| * If ORIGINAL is TRUE, read the original hunk text; else, read the |
| * modified hunk text. */ |
| static svn_error_t * |
| check_content(svn_diff_hunk_t *hunk, svn_boolean_t original, |
| const char *expected, apr_pool_t *pool) |
| { |
| svn_stream_t *exp; |
| svn_stringbuf_t *exp_buf; |
| svn_stringbuf_t *hunk_buf; |
| svn_boolean_t exp_eof; |
| svn_boolean_t hunk_eof; |
| |
| exp = svn_stream_from_string(svn_string_create(expected, pool), |
| pool); |
| |
| while (TRUE) |
| { |
| SVN_ERR(stream_readline_diff(exp, &exp_buf, NL, &exp_eof, pool)); |
| if (original) |
| SVN_ERR(svn_diff_hunk_readline_original_text(hunk, &hunk_buf, NULL, |
| &hunk_eof, pool, pool)); |
| else |
| SVN_ERR(svn_diff_hunk_readline_modified_text(hunk, &hunk_buf, NULL, |
| &hunk_eof, pool, pool)); |
| |
| SVN_TEST_ASSERT(exp_eof == hunk_eof); |
| if (exp_eof) |
| break; |
| SVN_TEST_STRING_ASSERT(exp_buf->data, hunk_buf->data); |
| } |
| |
| if (!hunk_eof) |
| SVN_TEST_ASSERT(hunk_buf->len == 0); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| test_parse_unidiff(apr_pool_t *pool) |
| { |
| svn_patch_file_t *patch_file; |
| svn_boolean_t reverse; |
| svn_boolean_t ignore_whitespace; |
| int i; |
| apr_pool_t *iterpool; |
| |
| reverse = FALSE; |
| ignore_whitespace = FALSE; |
| iterpool = svn_pool_create(pool); |
| for (i = 0; i < 2; i++) |
| { |
| svn_patch_t *patch; |
| svn_diff_hunk_t *hunk; |
| |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR(create_patch_file(&patch_file, unidiff, pool)); |
| |
| /* We have two patches with one hunk each. |
| * Parse the first patch. */ |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, reverse, |
| ignore_whitespace, iterpool, |
| iterpool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "A/C/gamma"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "A/C/gamma"); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 1); |
| |
| hunk = APR_ARRAY_IDX(patch->hunks, 0, svn_diff_hunk_t *); |
| SVN_ERR(check_content(hunk, ! reverse, |
| "This is the file 'gamma'." NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, reverse, |
| "This is the file 'gamma'." NL |
| "some more bytes to 'gamma'" NL, |
| pool)); |
| |
| /* Parse the second patch. */ |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, reverse, |
| ignore_whitespace, pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| if (reverse) |
| { |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "A/D/gamma.orig"); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "A/D/gamma"); |
| } |
| else |
| { |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "A/D/gamma.orig"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "A/D/gamma"); |
| } |
| SVN_TEST_ASSERT(patch->hunks->nelts == 1); |
| |
| hunk = APR_ARRAY_IDX(patch->hunks, 0, svn_diff_hunk_t *); |
| SVN_ERR(check_content(hunk, ! reverse, |
| "This is the file 'gamma'." NL |
| "some less bytes to 'gamma'" NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, reverse, |
| "This is the file 'gamma'." NL, |
| pool)); |
| |
| reverse = !reverse; |
| SVN_ERR(svn_diff_close_patch_file(patch_file, pool)); |
| } |
| svn_pool_destroy(iterpool); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| test_parse_git_diff(apr_pool_t *pool) |
| { |
| /* ### Should we check for reversed diffs? */ |
| |
| svn_patch_file_t *patch_file; |
| svn_patch_t *patch; |
| svn_diff_hunk_t *hunk; |
| |
| SVN_ERR(create_patch_file(&patch_file, git_unidiff, pool)); |
| |
| /* Parse a deleted empty file */ |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "A/mu"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "A/mu"); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_deleted); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 0); |
| |
| /* Parse a modified file. */ |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "A/C/gamma"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "A/C/gamma"); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_modified); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 1); |
| SVN_TEST_ASSERT(patch->old_executable_bit == svn_tristate_false); |
| SVN_TEST_ASSERT(patch->new_executable_bit == svn_tristate_true); |
| SVN_TEST_ASSERT(patch->old_symlink_bit == svn_tristate_false); |
| SVN_TEST_ASSERT(patch->new_symlink_bit == svn_tristate_false); |
| |
| hunk = APR_ARRAY_IDX(patch->hunks, 0, svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "This is the file 'gamma'." NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "This is the file 'gamma'." NL |
| "some more bytes to 'gamma'" NL, |
| pool)); |
| |
| /* Parse a copied empty file */ |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "iota"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "iota.copied"); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_copied); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 0); |
| |
| /* Parse an added empty file */ |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "new"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "new"); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_added); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 0); |
| SVN_TEST_ASSERT(patch->old_executable_bit == svn_tristate_unknown); |
| SVN_TEST_ASSERT(patch->new_executable_bit == svn_tristate_false); |
| SVN_TEST_ASSERT(patch->old_symlink_bit == svn_tristate_unknown); |
| SVN_TEST_ASSERT(patch->new_symlink_bit == svn_tristate_false); |
| |
| SVN_ERR(svn_diff_close_patch_file(patch_file, pool)); |
| |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| test_parse_git_tree_and_text_diff(apr_pool_t *pool) |
| { |
| /* ### Should we check for reversed diffs? */ |
| |
| svn_patch_file_t *patch_file; |
| svn_patch_t *patch; |
| svn_diff_hunk_t *hunk; |
| |
| SVN_ERR(create_patch_file(&patch_file, git_tree_and_text_unidiff, pool)); |
| |
| /* Parse a copied file with text modifications. */ |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "iota"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "iota.copied"); |
| SVN_TEST_ASSERT(patch->old_executable_bit == svn_tristate_false); |
| SVN_TEST_ASSERT(patch->new_executable_bit == svn_tristate_true); |
| SVN_TEST_ASSERT(patch->old_symlink_bit == svn_tristate_false); |
| SVN_TEST_ASSERT(patch->new_symlink_bit == svn_tristate_false); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_copied); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 1); |
| |
| hunk = APR_ARRAY_IDX(patch->hunks, 0, svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "This is the file 'iota'." NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "This is the file 'iota'." NL |
| "some more bytes to 'iota'" NL, |
| pool)); |
| |
| /* Parse a moved file with text modifications. */ |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "A/mu"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "A/mu.moved"); |
| SVN_TEST_ASSERT(patch->old_executable_bit == svn_tristate_false); |
| SVN_TEST_ASSERT(patch->new_executable_bit == svn_tristate_true); |
| SVN_TEST_ASSERT(patch->old_symlink_bit == svn_tristate_false); |
| SVN_TEST_ASSERT(patch->new_symlink_bit == svn_tristate_false); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_moved); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 1); |
| |
| hunk = APR_ARRAY_IDX(patch->hunks, 0, svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "This is the file 'mu'." NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "This is the file 'mu'." NL |
| "some more bytes to 'mu'" NL, |
| pool)); |
| |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "/dev/null"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "new"); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_added); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 1); |
| |
| hunk = APR_ARRAY_IDX(patch->hunks, 0, svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "", |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "This is the file 'new'." NL, |
| pool)); |
| |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "A/B/lambda"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "/dev/null"); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_deleted); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 1); |
| SVN_TEST_ASSERT(patch->old_executable_bit == svn_tristate_true); |
| SVN_TEST_ASSERT(patch->new_executable_bit == svn_tristate_unknown); |
| SVN_TEST_ASSERT(patch->old_symlink_bit == svn_tristate_false); |
| SVN_TEST_ASSERT(patch->new_symlink_bit == svn_tristate_unknown); |
| |
| hunk = APR_ARRAY_IDX(patch->hunks, 0, svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "This is the file 'lambda'." NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "", |
| pool)); |
| |
| SVN_ERR(svn_diff_close_patch_file(patch_file, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Tests to parse non-valid git diffs. */ |
| static svn_error_t * |
| test_bad_git_diff_headers(apr_pool_t *pool) |
| { |
| svn_patch_file_t *patch_file; |
| svn_patch_t *patch; |
| svn_diff_hunk_t *hunk; |
| |
| SVN_ERR(create_patch_file(&patch_file, bad_git_diff_header, pool)); |
| |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "iota"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "iota.copied"); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_copied); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 1); |
| |
| hunk = APR_ARRAY_IDX(patch->hunks, 0, svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "This is the file 'iota'." NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "This is the file 'iota'." NL |
| "some more bytes to 'iota'" NL, |
| pool)); |
| |
| SVN_ERR(svn_diff_close_patch_file(patch_file, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| /* Tests to parse a diff with three property changes, one is added, one is |
| * modified and one is deleted. */ |
| static svn_error_t * |
| test_parse_property_diff(apr_pool_t *pool) |
| { |
| svn_patch_file_t *patch_file; |
| svn_patch_t *patch; |
| svn_prop_patch_t *prop_patch; |
| svn_diff_hunk_t *hunk; |
| apr_array_header_t *hunks; |
| |
| SVN_ERR(create_patch_file(&patch_file, property_unidiff, pool)); |
| |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "iota"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "iota"); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 0); |
| SVN_TEST_ASSERT(apr_hash_count(patch->prop_patches) == 3); |
| |
| /* Check the deleted property */ |
| prop_patch = apr_hash_get(patch->prop_patches, "prop_del", |
| APR_HASH_KEY_STRING); |
| |
| SVN_TEST_ASSERT(prop_patch->operation == svn_diff_op_deleted); |
| hunks = prop_patch->hunks; |
| |
| SVN_TEST_ASSERT(hunks->nelts == 1); |
| hunk = APR_ARRAY_IDX(hunks, 0 , svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "value" NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "", |
| pool)); |
| |
| /* Check the added property */ |
| prop_patch = apr_hash_get(patch->prop_patches, "prop_add", |
| APR_HASH_KEY_STRING); |
| |
| SVN_TEST_STRING_ASSERT(prop_patch->name, "prop_add"); |
| SVN_TEST_ASSERT(prop_patch->operation == svn_diff_op_added); |
| hunks = prop_patch->hunks; |
| |
| SVN_TEST_ASSERT(hunks->nelts == 1); |
| hunk = APR_ARRAY_IDX(hunks, 0 , svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "", |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "value" NL, |
| pool)); |
| |
| /* Check the modified property */ |
| prop_patch = apr_hash_get(patch->prop_patches, "prop_mod", |
| APR_HASH_KEY_STRING); |
| |
| SVN_TEST_ASSERT(prop_patch->operation == svn_diff_op_modified); |
| hunks = prop_patch->hunks; |
| |
| SVN_TEST_ASSERT(hunks->nelts == 2); |
| hunk = APR_ARRAY_IDX(hunks, 0 , svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "value" NL |
| "context" NL |
| "context" NL |
| "context" NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "new value" NL |
| "context" NL |
| "context" NL |
| "context" NL, |
| pool)); |
| |
| hunk = APR_ARRAY_IDX(hunks, 1 , svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "context" NL |
| "context" NL |
| "context" NL |
| "value" NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "context" NL |
| "context" NL |
| "context" NL |
| "new value" NL, |
| pool)); |
| |
| SVN_ERR(svn_diff_close_patch_file(patch_file, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| test_parse_property_and_text_diff(apr_pool_t *pool) |
| { |
| svn_patch_file_t *patch_file; |
| svn_patch_t *patch; |
| svn_prop_patch_t *prop_patch; |
| svn_diff_hunk_t *hunk; |
| apr_array_header_t *hunks; |
| |
| SVN_ERR(create_patch_file(&patch_file, property_and_text_unidiff, pool)); |
| |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "iota"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "iota"); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 1); |
| SVN_TEST_ASSERT(apr_hash_count(patch->prop_patches) == 1); |
| |
| /* Check contents of text hunk */ |
| hunk = APR_ARRAY_IDX(patch->hunks, 0, svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "This is the file 'iota'." NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "This is the file 'iota'." NL |
| "some more bytes to 'iota'" NL, |
| pool)); |
| |
| /* Check the added property */ |
| prop_patch = apr_hash_get(patch->prop_patches, "prop_add", |
| APR_HASH_KEY_STRING); |
| SVN_TEST_ASSERT(prop_patch->operation == svn_diff_op_added); |
| |
| hunks = prop_patch->hunks; |
| SVN_TEST_ASSERT(hunks->nelts == 1); |
| hunk = APR_ARRAY_IDX(hunks, 0 , svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "", |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "value" NL, |
| pool)); |
| |
| SVN_ERR(svn_diff_close_patch_file(patch_file, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| test_parse_diff_symbols_in_prop_unidiff(apr_pool_t *pool) |
| { |
| svn_patch_t *patch; |
| svn_patch_file_t *patch_file; |
| svn_prop_patch_t *prop_patch; |
| svn_diff_hunk_t *hunk; |
| apr_array_header_t *hunks; |
| |
| SVN_ERR(create_patch_file(&patch_file, diff_symbols_in_prop_unidiff, pool)); |
| |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "iota"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "iota"); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 0); |
| SVN_TEST_ASSERT(apr_hash_count(patch->prop_patches) == 3); |
| |
| /* Check the added property */ |
| prop_patch = apr_hash_get(patch->prop_patches, "prop_add", |
| APR_HASH_KEY_STRING); |
| SVN_TEST_ASSERT(prop_patch->operation == svn_diff_op_added); |
| |
| hunks = prop_patch->hunks; |
| SVN_TEST_ASSERT(hunks->nelts == 1); |
| hunk = APR_ARRAY_IDX(hunks, 0 , svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "", |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "Added: bogus_prop" NL |
| "## -0,0 +20 ##" NL |
| "@@ -1,2 +0,0 @@" NL, |
| pool)); |
| |
| /* Check the deleted property */ |
| prop_patch = apr_hash_get(patch->prop_patches, "prop_del", |
| APR_HASH_KEY_STRING); |
| SVN_TEST_ASSERT(prop_patch->operation == svn_diff_op_deleted); |
| |
| hunks = prop_patch->hunks; |
| SVN_TEST_ASSERT(hunks->nelts == 1); |
| hunk = APR_ARRAY_IDX(hunks, 0 , svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "--- iota" NL |
| "+++ iota" NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "", |
| pool)); |
| |
| /* Check the modified property */ |
| prop_patch = apr_hash_get(patch->prop_patches, "prop_mod", |
| APR_HASH_KEY_STRING); |
| SVN_TEST_ASSERT(prop_patch->operation == svn_diff_op_modified); |
| hunks = prop_patch->hunks; |
| SVN_TEST_ASSERT(hunks->nelts == 2); |
| hunk = APR_ARRAY_IDX(hunks, 0 , svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "## -1,2 +1,2 ##" NL |
| "## -1,5 -0,0 ##" NL |
| "@@ -1,5 -0,0 @@" NL |
| "Modified: prop_mod" NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "## -1,3 +1,3 ##" NL |
| "## -1,5 -0,0 ##" NL |
| "@@ -1,5 -0,0 @@" NL |
| "Modified: prop_mod" NL, |
| pool)); |
| |
| hunk = APR_ARRAY_IDX(hunks, 1 , svn_diff_hunk_t *); |
| |
| SVN_ERR(check_content(hunk, TRUE, |
| "context" NL |
| "context" NL |
| "context" NL |
| "## -0,0 +1 ##" NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, FALSE, |
| "context" NL |
| "context" NL |
| "context" NL |
| "## -1,2 +1,4 ##" NL, |
| pool)); |
| |
| SVN_ERR(svn_diff_close_patch_file(patch_file, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| test_git_diffs_with_spaces_diff(apr_pool_t *pool) |
| { |
| svn_patch_file_t *patch_file; |
| svn_patch_t *patch; |
| |
| SVN_ERR(create_patch_file(&patch_file, path_with_spaces_unidiff, pool)); |
| |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "path 1"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "path 1"); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_added); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 0); |
| |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "path one 1"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "path one 1"); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_added); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 0); |
| |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "dir/ b/path"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "dir/ b/path"); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_added); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 0); |
| |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, |
| FALSE, /* reverse */ |
| FALSE, /* ignore_whitespace */ |
| pool, pool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, " b/path 1"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, " b/path 1"); |
| SVN_TEST_ASSERT(patch->operation == svn_diff_op_added); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 0); |
| |
| SVN_ERR(svn_diff_close_patch_file(patch_file, pool)); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| test_parse_unidiff_lacking_trailing_eol(apr_pool_t *pool) |
| { |
| svn_patch_file_t *patch_file; |
| svn_boolean_t reverse; |
| svn_boolean_t ignore_whitespace; |
| int i; |
| apr_pool_t *iterpool; |
| |
| reverse = FALSE; |
| ignore_whitespace = FALSE; |
| iterpool = svn_pool_create(pool); |
| for (i = 0; i < 2; i++) |
| { |
| svn_patch_t *patch; |
| svn_diff_hunk_t *hunk; |
| |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR(create_patch_file(&patch_file, unidiff_lacking_trailing_eol, |
| pool)); |
| |
| /* We have one patch with one hunk. Parse it. */ |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, reverse, |
| ignore_whitespace, iterpool, |
| iterpool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "A/C/gamma"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "A/C/gamma"); |
| SVN_TEST_ASSERT(patch->hunks->nelts == 1); |
| |
| hunk = APR_ARRAY_IDX(patch->hunks, 0, svn_diff_hunk_t *); |
| SVN_ERR(check_content(hunk, ! reverse, |
| "This is the file 'gamma'." NL, |
| pool)); |
| |
| SVN_ERR(check_content(hunk, reverse, |
| "This is the file 'gamma'." NL |
| "some more bytes to 'gamma'", |
| pool)); |
| |
| reverse = !reverse; |
| SVN_ERR(svn_diff_close_patch_file(patch_file, pool)); |
| } |
| svn_pool_destroy(iterpool); |
| return SVN_NO_ERROR; |
| } |
| |
| static svn_error_t * |
| test_parse_unidiff_with_mergeinfo(apr_pool_t *pool) |
| { |
| svn_patch_file_t *patch_file; |
| svn_boolean_t reverse; |
| svn_boolean_t ignore_whitespace; |
| int i; |
| apr_pool_t *iterpool; |
| |
| reverse = FALSE; |
| ignore_whitespace = FALSE; |
| iterpool = svn_pool_create(pool); |
| for (i = 0; i < 2; i++) |
| { |
| svn_patch_t *patch; |
| svn_mergeinfo_t mergeinfo; |
| svn_mergeinfo_t reverse_mergeinfo; |
| svn_rangelist_t *rangelist; |
| svn_merge_range_t *range; |
| |
| svn_pool_clear(iterpool); |
| |
| SVN_ERR(create_patch_file(&patch_file, unidiff_with_mergeinfo, |
| pool)); |
| |
| SVN_ERR(svn_diff_parse_next_patch(&patch, patch_file, reverse, |
| ignore_whitespace, iterpool, |
| iterpool)); |
| SVN_TEST_ASSERT(patch); |
| SVN_TEST_STRING_ASSERT(patch->old_filename, "A/C"); |
| SVN_TEST_STRING_ASSERT(patch->new_filename, "A/C"); |
| |
| /* svn:ignore */ |
| SVN_TEST_ASSERT(apr_hash_count(patch->prop_patches) == 1); |
| |
| SVN_TEST_ASSERT(patch->mergeinfo); |
| SVN_TEST_ASSERT(patch->reverse_mergeinfo); |
| |
| if (reverse) |
| { |
| mergeinfo = patch->reverse_mergeinfo; |
| reverse_mergeinfo = patch->mergeinfo; |
| } |
| else |
| { |
| mergeinfo = patch->mergeinfo; |
| reverse_mergeinfo = patch->reverse_mergeinfo; |
| } |
| |
| rangelist = svn_hash_gets(reverse_mergeinfo, |
| "/subversion/branches/1.6.x-r935631"); |
| SVN_TEST_ASSERT(rangelist); |
| SVN_TEST_ASSERT(rangelist->nelts == 1); |
| range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *); |
| SVN_TEST_ASSERT(range->start == 952682); |
| SVN_TEST_ASSERT(range->end == 955333); |
| |
| rangelist = svn_hash_gets(mergeinfo, |
| "/subversion/branches/nfc-nfd-aware-client"); |
| SVN_TEST_ASSERT(rangelist); |
| SVN_TEST_ASSERT(rangelist->nelts == 2); |
| range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *); |
| SVN_TEST_ASSERT(range->end == 870276); |
| range = APR_ARRAY_IDX(rangelist, 1, svn_merge_range_t *); |
| SVN_TEST_ASSERT(range->end == 870376); |
| |
| rangelist = svn_hash_gets(mergeinfo, |
| "/subversion/branches/1.8.x-openssl-dirs"); |
| SVN_TEST_ASSERT(rangelist); |
| SVN_TEST_ASSERT(rangelist->nelts == 1); |
| range = APR_ARRAY_IDX(rangelist, 0, svn_merge_range_t *); |
| SVN_TEST_ASSERT(range->end == 1535139); |
| |
| reverse = !reverse; |
| SVN_ERR(svn_diff_close_patch_file(patch_file, pool)); |
| } |
| svn_pool_destroy(iterpool); |
| return SVN_NO_ERROR; |
| } |
| |
| /* ========================================================================== */ |
| |
| |
| static int max_threads = 1; |
| |
| static struct svn_test_descriptor_t test_funcs[] = |
| { |
| SVN_TEST_NULL, |
| SVN_TEST_PASS2(test_parse_unidiff, |
| "test unidiff parsing"), |
| SVN_TEST_PASS2(test_parse_git_diff, |
| "test git unidiff parsing"), |
| SVN_TEST_PASS2(test_parse_git_tree_and_text_diff, |
| "test git unidiff parsing of tree and text changes"), |
| SVN_TEST_PASS2(test_bad_git_diff_headers, |
| "test badly formatted git diff headers"), |
| SVN_TEST_PASS2(test_parse_property_diff, |
| "test property unidiff parsing"), |
| SVN_TEST_PASS2(test_parse_property_and_text_diff, |
| "test property and text unidiff parsing"), |
| SVN_TEST_PASS2(test_parse_diff_symbols_in_prop_unidiff, |
| "test property diffs with odd symbols"), |
| SVN_TEST_PASS2(test_git_diffs_with_spaces_diff, |
| "test git diffs with spaces in paths"), |
| SVN_TEST_PASS2(test_parse_unidiff_lacking_trailing_eol, |
| "test parsing unidiffs lacking trailing eol"), |
| SVN_TEST_PASS2(test_parse_unidiff_with_mergeinfo, |
| "test parsing unidiffs with mergeinfo"), |
| SVN_TEST_NULL |
| }; |
| |
| SVN_TEST_MAIN |