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];