summaryrefslogtreecommitdiff
path: root/deps/jemalloc/test/src/test.c
blob: 4cd803e5f892d75e263ad69d4702599d6af1bde7 (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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
#include "test/jemalloc_test.h"

/* Test status state. */

static unsigned		test_count = 0;
static test_status_t	test_counts[test_status_count] = {0, 0, 0};
static test_status_t	test_status = test_status_pass;
static const char *	test_name = "";

/* Reentrancy testing helpers. */

#define NUM_REENTRANT_ALLOCS 20
typedef enum {
	non_reentrant = 0,
	libc_reentrant = 1,
	arena_new_reentrant = 2
} reentrancy_t;
static reentrancy_t reentrancy;

static bool libc_hook_ran = false;
static bool arena_new_hook_ran = false;

static const char *
reentrancy_t_str(reentrancy_t r) {
	switch (r) {
	case non_reentrant:
		return "non-reentrant";
	case libc_reentrant:
		return "libc-reentrant";
	case arena_new_reentrant:
		return "arena_new-reentrant";
	default:
		unreachable();
	}
}

static void
do_hook(bool *hook_ran, void (**hook)()) {
	*hook_ran = true;
	*hook = NULL;

	size_t alloc_size = 1;
	for (int i = 0; i < NUM_REENTRANT_ALLOCS; i++) {
		free(malloc(alloc_size));
		alloc_size *= 2;
	}
}

static void
libc_reentrancy_hook() {
	do_hook(&libc_hook_ran, &test_hooks_libc_hook);
}

static void
arena_new_reentrancy_hook() {
	do_hook(&arena_new_hook_ran, &test_hooks_arena_new_hook);
}

/* Actual test infrastructure. */
bool
test_is_reentrant() {
	return reentrancy != non_reentrant;
}

JEMALLOC_FORMAT_PRINTF(1, 2)
void
test_skip(const char *format, ...) {
	va_list ap;

	va_start(ap, format);
	malloc_vcprintf(NULL, NULL, format, ap);
	va_end(ap);
	malloc_printf("\n");
	test_status = test_status_skip;
}

JEMALLOC_FORMAT_PRINTF(1, 2)
void
test_fail(const char *format, ...) {
	va_list ap;

	va_start(ap, format);
	malloc_vcprintf(NULL, NULL, format, ap);
	va_end(ap);
	malloc_printf("\n");
	test_status = test_status_fail;
}

static const char *
test_status_string(test_status_t current_status) {
	switch (current_status) {
	case test_status_pass: return "pass";
	case test_status_skip: return "skip";
	case test_status_fail: return "fail";
	default: not_reached();
	}
}

void
p_test_init(const char *name) {
	test_count++;
	test_status = test_status_pass;
	test_name = name;
}

void
p_test_fini(void) {
	test_counts[test_status]++;
	malloc_printf("%s (%s): %s\n", test_name, reentrancy_t_str(reentrancy),
	    test_status_string(test_status));
}

static void
check_global_slow(test_status_t *status) {
#ifdef JEMALLOC_UNIT_TEST
	/*
	 * This check needs to peek into tsd internals, which is why it's only
	 * exposed in unit tests.
	 */
	if (tsd_global_slow()) {
		malloc_printf("Testing increased global slow count\n");
		*status = test_status_fail;
	}
#endif
}

static test_status_t
p_test_impl(bool do_malloc_init, bool do_reentrant, test_t *t, va_list ap) {
	test_status_t ret;

	if (do_malloc_init) {
		/*
		 * Make sure initialization occurs prior to running tests.
		 * Tests are special because they may use internal facilities
		 * prior to triggering initialization as a side effect of
		 * calling into the public API.
		 */
		if (nallocx(1, 0) == 0) {
			malloc_printf("Initialization error");
			return test_status_fail;
		}
	}

	ret = test_status_pass;
	for (; t != NULL; t = va_arg(ap, test_t *)) {
		/* Non-reentrant run. */
		reentrancy = non_reentrant;
		test_hooks_arena_new_hook = test_hooks_libc_hook = NULL;
		t();
		if (test_status > ret) {
			ret = test_status;
		}
		check_global_slow(&ret);
		/* Reentrant run. */
		if (do_reentrant) {
			reentrancy = libc_reentrant;
			test_hooks_arena_new_hook = NULL;
			test_hooks_libc_hook = &libc_reentrancy_hook;
			t();
			if (test_status > ret) {
				ret = test_status;
			}
			check_global_slow(&ret);

			reentrancy = arena_new_reentrant;
			test_hooks_libc_hook = NULL;
			test_hooks_arena_new_hook = &arena_new_reentrancy_hook;
			t();
			if (test_status > ret) {
				ret = test_status;
			}
			check_global_slow(&ret);
		}
	}

	malloc_printf("--- %s: %u/%u, %s: %u/%u, %s: %u/%u ---\n",
	    test_status_string(test_status_pass),
	    test_counts[test_status_pass], test_count,
	    test_status_string(test_status_skip),
	    test_counts[test_status_skip], test_count,
	    test_status_string(test_status_fail),
	    test_counts[test_status_fail], test_count);

	return ret;
}

test_status_t
p_test(test_t *t, ...) {
	test_status_t ret;
	va_list ap;

	ret = test_status_pass;
	va_start(ap, t);
	ret = p_test_impl(true, true, t, ap);
	va_end(ap);

	return ret;
}

test_status_t
p_test_no_reentrancy(test_t *t, ...) {
	test_status_t ret;
	va_list ap;

	ret = test_status_pass;
	va_start(ap, t);
	ret = p_test_impl(true, false, t, ap);
	va_end(ap);

	return ret;
}

test_status_t
p_test_no_malloc_init(test_t *t, ...) {
	test_status_t ret;
	va_list ap;

	ret = test_status_pass;
	va_start(ap, t);
	/*
	 * We also omit reentrancy from bootstrapping tests, since we don't
	 * (yet) care about general reentrancy during bootstrapping.
	 */
	ret = p_test_impl(false, false, t, ap);
	va_end(ap);

	return ret;
}

void
p_test_fail(const char *prefix, const char *message) {
	malloc_cprintf(NULL, NULL, "%s%s\n", prefix, message);
	test_status = test_status_fail;
}