strvec: add functions to replace and remove strings
Add two functions that allow to replace and remove strings contained in the strvec. This will be used by a subsequent commit that refactors git-mv(1). While at it, add a bunch of unit tests that cover both old and new functionality. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Patrick Steinhardt committed
May 27, 2024 at 13:47 UTC
11ce77b5cc04e2a42b98f0f9f42d367f50f3b1fc
6 files changed
+329
Makefile
+1
@@ -1336,6 +1336,7 @@ THIRD_PARTY_SOURCES += sha1dc/%
1336
1337
UNIT_TEST_PROGRAMS += t-mem-pool
1338
UNIT_TEST_PROGRAMS += t-strbuf
1339
+UNIT_TEST_PROGRAMS += t-strvec
1340
UNIT_TEST_PROGRAMS += t-ctype
1341
UNIT_TEST_PROGRAMS += t-prio-queue
1342
UNIT_TEST_PROGS = $(patsubst %,$(UNIT_TEST_BIN)/%$X,$(UNIT_TEST_PROGRAMS))
strvec.c
+20
@@ -56,6 +56,26 @@ void strvec_pushv(struct strvec *array, const char **items)
56
strvec_push(array, *items);
57
}
58
59
+const char *strvec_replace(struct strvec *array, size_t idx, const char *replacement)
60
+{
61
+ char *to_free;
62
+ if (idx >= array->nr)
63
+ BUG("index outside of array boundary");
64
+ to_free = (char *) array->v[idx];
65
+ array->v[idx] = xstrdup(replacement);
66
+ free(to_free);
67
+ return array->v[idx];
68
+}
69
+
70
+void strvec_remove(struct strvec *array, size_t idx)
71
+{
72
+ if (idx >= array->nr)
73
+ BUG("index outside of array boundary");
74
+ free((char *)array->v[idx]);
75
+ memmove(array->v + idx, array->v + idx + 1, (array->nr - idx) * sizeof(char *));
76
+ array->nr--;
77
+}
78
+
79
void strvec_pop(struct strvec *array)
80
{
81
if (!array->nr)
strvec.h
+13
@@ -64,6 +64,19 @@ void strvec_pushl(struct strvec *, ...);
64
/* Push a null-terminated array of strings onto the end of the array. */
65
void strvec_pushv(struct strvec *, const char **);
66
67
+/**
68
+ * Replace the value at the given index with a new value. The index must be
69
+ * valid. Returns a pointer to the inserted value.
70
+ */
71
+const char *strvec_replace(struct strvec *array, size_t idx, const char *replacement);
72
+
73
+/*
74
+ * Remove the value at the given index. The remainder of the array will be
75
+ * moved to fill the resulting gap. The provided index must point into the
76
+ * array.
77
+ */
78
+void strvec_remove(struct strvec *array, size_t idx);
79
+
80
/**
81
* Remove the final element from the array. If there are no
82
* elements in the array, do nothing.
t/unit-tests/t-strvec.c
new
+269
@@ -0,0 +1,269 @@
1
+#include "test-lib.h"
2
+#include "strbuf.h"
3
+#include "strvec.h"
4
+
5
+#define check_strvec(vec, ...) \
6
+ check_strvec_loc(TEST_LOCATION(), vec, __VA_ARGS__)
7
+static void check_strvec_loc(const char *loc, struct strvec *vec, ...)
8
+{
9
+ va_list ap;
10
+ size_t nr = 0;
11
+
12
+ va_start(ap, vec);
13
+ while (1) {
14
+ const char *str = va_arg(ap, const char *);
15
+ if (!str)
16
+ break;
17
+
18
+ if (!check_uint(vec->nr, >, nr) ||
19
+ !check_uint(vec->alloc, >, nr) ||
20
+ !check_str(vec->v[nr], str)) {
21
+ struct strbuf msg = STRBUF_INIT;
22
+ strbuf_addf(&msg, "strvec index %"PRIuMAX, (uintmax_t) nr);
23
+ test_assert(loc, msg.buf, 0);
24
+ strbuf_release(&msg);
25
+ return;
26
+ }
27
+
28
+ nr++;
29
+ }
30
+
31
+ check_uint(vec->nr, ==, nr);
32
+ check_uint(vec->alloc, >=, nr);
33
+ check_pointer_eq(vec->v[nr], NULL);
34
+}
35
+
36
+static void t_static_init(void)
37
+{
38
+ struct strvec vec = STRVEC_INIT;
39
+ check_pointer_eq(vec.v, empty_strvec);
40
+ check_uint(vec.nr, ==, 0);
41
+ check_uint(vec.alloc, ==, 0);
42
+}
43
+
44
+static void t_dynamic_init(void)
45
+{
46
+ struct strvec vec;
47
+ strvec_init(&vec);
48
+ check_pointer_eq(vec.v, empty_strvec);
49
+ check_uint(vec.nr, ==, 0);
50
+ check_uint(vec.alloc, ==, 0);
51
+}
52
+
53
+static void t_clear(void)
54
+{
55
+ struct strvec vec = STRVEC_INIT;
56
+ strvec_push(&vec, "foo");
57
+ strvec_clear(&vec);
58
+ check_pointer_eq(vec.v, empty_strvec);
59
+ check_uint(vec.nr, ==, 0);
60
+ check_uint(vec.alloc, ==, 0);
61
+}
62
+
63
+static void t_push(void)
64
+{
65
+ struct strvec vec = STRVEC_INIT;
66
+
67
+ strvec_push(&vec, "foo");
68
+ check_strvec(&vec, "foo", NULL);
69
+
70
+ strvec_push(&vec, "bar");
71
+ check_strvec(&vec, "foo", "bar", NULL);
72
+
73
+ strvec_clear(&vec);
74
+}
75
+
76
+static void t_pushf(void)
77
+{
78
+ struct strvec vec = STRVEC_INIT;
79
+ strvec_pushf(&vec, "foo: %d", 1);
80
+ check_strvec(&vec, "foo: 1", NULL);
81
+ strvec_clear(&vec);
82
+}
83
+
84
+static void t_pushl(void)
85
+{
86
+ struct strvec vec = STRVEC_INIT;
87
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
88
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
89
+ strvec_clear(&vec);
90
+}
91
+
92
+static void t_pushv(void)
93
+{
94
+ const char *strings[] = {
95
+ "foo", "bar", "baz", NULL,
96
+ };
97
+ struct strvec vec = STRVEC_INIT;
98
+
99
+ strvec_pushv(&vec, strings);
100
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
101
+
102
+ strvec_clear(&vec);
103
+}
104
+
105
+static void t_replace_at_head(void)
106
+{
107
+ struct strvec vec = STRVEC_INIT;
108
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
109
+ strvec_replace(&vec, 0, "replaced");
110
+ check_strvec(&vec, "replaced", "bar", "baz", NULL);
111
+ strvec_clear(&vec);
112
+}
113
+
114
+static void t_replace_at_tail(void)
115
+{
116
+ struct strvec vec = STRVEC_INIT;
117
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
118
+ strvec_replace(&vec, 2, "replaced");
119
+ check_strvec(&vec, "foo", "bar", "replaced", NULL);
120
+ strvec_clear(&vec);
121
+}
122
+
123
+static void t_replace_in_between(void)
124
+{
125
+ struct strvec vec = STRVEC_INIT;
126
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
127
+ strvec_replace(&vec, 1, "replaced");
128
+ check_strvec(&vec, "foo", "replaced", "baz", NULL);
129
+ strvec_clear(&vec);
130
+}
131
+
132
+static void t_replace_with_substring(void)
133
+{
134
+ struct strvec vec = STRVEC_INIT;
135
+ strvec_pushl(&vec, "foo", NULL);
136
+ strvec_replace(&vec, 0, vec.v[0] + 1);
137
+ check_strvec(&vec, "oo", NULL);
138
+ strvec_clear(&vec);
139
+}
140
+
141
+static void t_remove_at_head(void)
142
+{
143
+ struct strvec vec = STRVEC_INIT;
144
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
145
+ strvec_remove(&vec, 0);
146
+ check_strvec(&vec, "bar", "baz", NULL);
147
+ strvec_clear(&vec);
148
+}
149
+
150
+static void t_remove_at_tail(void)
151
+{
152
+ struct strvec vec = STRVEC_INIT;
153
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
154
+ strvec_remove(&vec, 2);
155
+ check_strvec(&vec, "foo", "bar", NULL);
156
+ strvec_clear(&vec);
157
+}
158
+
159
+static void t_remove_in_between(void)
160
+{
161
+ struct strvec vec = STRVEC_INIT;
162
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
163
+ strvec_remove(&vec, 1);
164
+ check_strvec(&vec, "foo", "baz", NULL);
165
+ strvec_clear(&vec);
166
+}
167
+
168
+static void t_pop_empty_array(void)
169
+{
170
+ struct strvec vec = STRVEC_INIT;
171
+ strvec_pop(&vec);
172
+ check_strvec(&vec, NULL);
173
+ strvec_clear(&vec);
174
+}
175
+
176
+static void t_pop_non_empty_array(void)
177
+{
178
+ struct strvec vec = STRVEC_INIT;
179
+ strvec_pushl(&vec, "foo", "bar", "baz", NULL);
180
+ strvec_pop(&vec);
181
+ check_strvec(&vec, "foo", "bar", NULL);
182
+ strvec_clear(&vec);
183
+}
184
+
185
+static void t_split_empty_string(void)
186
+{
187
+ struct strvec vec = STRVEC_INIT;
188
+ strvec_split(&vec, "");
189
+ check_strvec(&vec, NULL);
190
+ strvec_clear(&vec);
191
+}
192
+
193
+static void t_split_single_item(void)
194
+{
195
+ struct strvec vec = STRVEC_INIT;
196
+ strvec_split(&vec, "foo");
197
+ check_strvec(&vec, "foo", NULL);
198
+ strvec_clear(&vec);
199
+}
200
+
201
+static void t_split_multiple_items(void)
202
+{
203
+ struct strvec vec = STRVEC_INIT;
204
+ strvec_split(&vec, "foo bar baz");
205
+ check_strvec(&vec, "foo", "bar", "baz", NULL);
206
+ strvec_clear(&vec);
207
+}
208
+
209
+static void t_split_whitespace_only(void)
210
+{
211
+ struct strvec vec = STRVEC_INIT;
212
+ strvec_split(&vec, " \t\n");
213
+ check_strvec(&vec, NULL);
214
+ strvec_clear(&vec);
215
+}
216
+
217
+static void t_split_multiple_consecutive_whitespaces(void)
218
+{
219
+ struct strvec vec = STRVEC_INIT;
220
+ strvec_split(&vec, "foo\n\t bar");
221
+ check_strvec(&vec, "foo", "bar", NULL);
222
+ strvec_clear(&vec);
223
+}
224
+
225
+static void t_detach(void)
226
+{
227
+ struct strvec vec = STRVEC_INIT;
228
+ const char **detached;
229
+
230
+ strvec_push(&vec, "foo");
231
+
232
+ detached = strvec_detach(&vec);
233
+ check_str(detached[0], "foo");
234
+ check_pointer_eq(detached[1], NULL);
235
+
236
+ check_pointer_eq(vec.v, empty_strvec);
237
+ check_uint(vec.nr, ==, 0);
238
+ check_uint(vec.alloc, ==, 0);
239
+
240
+ free((char *) detached[0]);
241
+ free(detached);
242
+}
243
+
244
+int cmd_main(int argc, const char **argv)
245
+{
246
+ TEST(t_static_init(), "static initialization");
247
+ TEST(t_dynamic_init(), "dynamic initialization");
248
+ TEST(t_clear(), "clear");
249
+ TEST(t_push(), "push");
250
+ TEST(t_pushf(), "pushf");
251
+ TEST(t_pushl(), "pushl");
252
+ TEST(t_pushv(), "pushv");
253
+ TEST(t_replace_at_head(), "replace at head");
254
+ TEST(t_replace_in_between(), "replace in between");
255
+ TEST(t_replace_at_tail(), "replace at tail");
256
+ TEST(t_replace_with_substring(), "replace with substring");
257
+ TEST(t_remove_at_head(), "remove at head");
258
+ TEST(t_remove_in_between(), "remove in between");
259
+ TEST(t_remove_at_tail(), "remove at tail");
260
+ TEST(t_pop_empty_array(), "pop with empty array");
261
+ TEST(t_pop_non_empty_array(), "pop with non-empty array");
262
+ TEST(t_split_empty_string(), "split empty string");
263
+ TEST(t_split_single_item(), "split single item");
264
+ TEST(t_split_multiple_items(), "split multiple items");
265
+ TEST(t_split_whitespace_only(), "split whitespace only");
266
+ TEST(t_split_multiple_consecutive_whitespaces(), "split multiple consecutive whitespaces");
267
+ TEST(t_detach(), "detach");
268
+ return test_done();
269
+}
t/unit-tests/test-lib.c
+13
@@ -318,6 +318,19 @@ int check_bool_loc(const char *loc, const char *check, int ok)
318
319
union test__tmp test__tmp[2];
320
321
+int check_pointer_eq_loc(const char *loc, const char *check, int ok,
322
+ const void *a, const void *b)
323
+{
324
+ int ret = test_assert(loc, check, ok);
325
+
326
+ if (!ret) {
327
+ test_msg(" left: %p", a);
328
+ test_msg(" right: %p", b);
329
+ }
330
+
331
+ return ret;
332
+}
333
+
334
int check_int_loc(const char *loc, const char *check, int ok,
335
intmax_t a, intmax_t b)
336
{
t/unit-tests/test-lib.h
+13
@@ -75,6 +75,18 @@ int test_assert(const char *location, const char *check, int ok);
75
check_bool_loc(TEST_LOCATION(), #x, x)
76
int check_bool_loc(const char *loc, const char *check, int ok);
77
78
+/*
79
+ * Compare two integers. Prints a message with the two values if the
80
+ * comparison fails. NB this is not thread safe.
81
+ */
82
+#define check_pointer_eq(a, b) \
83
+ (test__tmp[0].p = (a), test__tmp[1].p = (b), \
84
+ check_pointer_eq_loc(TEST_LOCATION(), #a" == "#b, \
85
+ test__tmp[0].p == test__tmp[1].p, \
86
+ test__tmp[0].p, test__tmp[1].p))
87
+int check_pointer_eq_loc(const char *loc, const char *check, int ok,
88
+ const void *a, const void *b);
89
+
90
/*
91
* Compare two integers. Prints a message with the two values if the
92
* comparison fails. NB this is not thread safe.
@@ -136,6 +148,7 @@ union test__tmp {
148
intmax_t i;
149
uintmax_t u;
150
char c;
151
+ const void *p;
152
};
153
154
extern union test__tmp test__tmp[2];