summaryrefslogtreecommitdiff
path: root/tests/libgit2/diff/racediffiter.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/libgit2/diff/racediffiter.c')
-rw-r--r--tests/libgit2/diff/racediffiter.c129
1 files changed, 129 insertions, 0 deletions
diff --git a/tests/libgit2/diff/racediffiter.c b/tests/libgit2/diff/racediffiter.c
new file mode 100644
index 000000000..d364d6b21
--- /dev/null
+++ b/tests/libgit2/diff/racediffiter.c
@@ -0,0 +1,129 @@
+/* This test exercises the problem described in
+** https://github.com/libgit2/libgit2/pull/3568
+** where deleting a directory during a diff/status
+** operation can cause an access violation.
+**
+** The "test_diff_racediffiter__basic() test confirms
+** the normal operation of diff on the given repo.
+**
+** The "test_diff_racediffiter__racy_rmdir() test
+** uses the new diff progress callback to delete
+** a directory (after the initial readdir() and
+** before the directory itself is visited) causing
+** the recursion and iteration to fail.
+*/
+
+#include "clar_libgit2.h"
+#include "diff_helpers.h"
+
+#define ARRAY_LEN(a) (sizeof(a) / sizeof(a[0]))
+
+void test_diff_racediffiter__initialize(void)
+{
+}
+
+void test_diff_racediffiter__cleanup(void)
+{
+ cl_git_sandbox_cleanup();
+}
+
+typedef struct
+{
+ const char *path;
+ git_delta_t t;
+
+} basic_payload;
+
+static int notify_cb__basic(
+ const git_diff *diff_so_far,
+ const git_diff_delta *delta_to_add,
+ const char *matched_pathspec,
+ void *payload)
+{
+ basic_payload *exp = (basic_payload *)payload;
+ basic_payload *e;
+
+ GIT_UNUSED(diff_so_far);
+ GIT_UNUSED(matched_pathspec);
+
+ for (e = exp; e->path; e++) {
+ if (strcmp(e->path, delta_to_add->new_file.path) == 0) {
+ cl_assert_equal_i(e->t, delta_to_add->status);
+ return 0;
+ }
+ }
+ cl_assert(0);
+ return GIT_ENOTFOUND;
+}
+
+void test_diff_racediffiter__basic(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_repository *repo = cl_git_sandbox_init("diff");
+ git_diff *diff;
+
+ basic_payload exp_a[] = {
+ { "another.txt", GIT_DELTA_MODIFIED },
+ { "readme.txt", GIT_DELTA_MODIFIED },
+ { "zzzzz/", GIT_DELTA_IGNORED },
+ { NULL, 0 }
+ };
+
+ cl_must_pass(p_mkdir("diff/zzzzz", 0777));
+
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+ opts.notify_cb = notify_cb__basic;
+ opts.payload = exp_a;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
+
+ git_diff_free(diff);
+}
+
+
+typedef struct {
+ bool first_time;
+ const char *dir;
+ basic_payload *basic_payload;
+} racy_payload;
+
+static int notify_cb__racy_rmdir(
+ const git_diff *diff_so_far,
+ const git_diff_delta *delta_to_add,
+ const char *matched_pathspec,
+ void *payload)
+{
+ racy_payload *pay = (racy_payload *)payload;
+
+ if (pay->first_time) {
+ cl_must_pass(p_rmdir(pay->dir));
+ pay->first_time = false;
+ }
+
+ return notify_cb__basic(diff_so_far, delta_to_add, matched_pathspec, pay->basic_payload);
+}
+
+void test_diff_racediffiter__racy(void)
+{
+ git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
+ git_repository *repo = cl_git_sandbox_init("diff");
+ git_diff *diff;
+
+ basic_payload exp_a[] = {
+ { "another.txt", GIT_DELTA_MODIFIED },
+ { "readme.txt", GIT_DELTA_MODIFIED },
+ { NULL, 0 }
+ };
+
+ racy_payload pay = { true, "diff/zzzzz", exp_a };
+
+ cl_must_pass(p_mkdir("diff/zzzzz", 0777));
+
+ opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_RECURSE_UNTRACKED_DIRS;
+ opts.notify_cb = notify_cb__racy_rmdir;
+ opts.payload = &pay;
+
+ cl_git_pass(git_diff_index_to_workdir(&diff, repo, NULL, &opts));
+
+ git_diff_free(diff);
+}