summaryrefslogtreecommitdiff
path: root/tests/diff/racediffiter.c
blob: d364d6b211d8744de91064e68e5028f4e77f57cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
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);
}