Line data Source code
1 : /*
2 : * Copyright (C) Catalyst.Net Ltd 2020
3 : *
4 : * This program is free software; you can redistribute it and/or modify
5 : * it under the terms of the GNU General Public License as published by
6 : * the Free Software Foundation; either version 3 of the License, or
7 : * (at your option) any later version.
8 : *
9 : * This program is distributed in the hope that it will be useful,
10 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 : * GNU General Public License for more details.
13 : *
14 : * You should have received a copy of the GNU General Public License
15 : * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 : *
17 : */
18 :
19 : /*
20 : * Tests confirming lmdb's handling of the free space list in the presence
21 : * of active and stale readers. A stale reader is a process that opens a
22 : * read lock and then exits without releasing the lock.
23 : *
24 : * lmdb uses MVCC to maintain databased consistency, new copies of updated
25 : * records are written to the database. The old entries are only
26 : * reused when they are no longer referenced in a read transaction.
27 : *
28 : * The tests all update a single record multiple times
29 : *
30 : * If there is a read transaction or a stale reader lmdb will report
31 : * out of space.
32 : *
33 : * If no read transaction and no stale reader, lmdb reclaims space from the
34 : * free list.
35 : */
36 :
37 : /*
38 : * from cmocka.c:
39 : * These headers or their equivalents should be included prior to
40 : * including
41 : * this header file.
42 : *
43 : * #include <stdarg.h>
44 : * #include <stddef.h>
45 : * #include <setjmp.h>
46 : *
47 : * This allows test applications to use custom definitions of C standard
48 : * library functions and types.
49 : *
50 : */
51 :
52 : #include <stdarg.h>
53 : #include <stddef.h>
54 : #include <stdint.h>
55 : #include <setjmp.h>
56 : #include <cmocka.h>
57 :
58 : #include <errno.h>
59 : #include <unistd.h>
60 : #include <talloc.h>
61 : #include <tevent.h>
62 : #include <ldb.h>
63 : #include <ldb_module.h>
64 : #include <ldb_private.h>
65 : #include <string.h>
66 : #include <ctype.h>
67 :
68 : #include <sys/wait.h>
69 :
70 : #include "ldb_tdb/ldb_tdb.h"
71 : #include "ldb_key_value/ldb_kv.h"
72 :
73 : #define DEFAULT_BE "mdb"
74 :
75 : #ifndef TEST_BE
76 : #define TEST_BE DEFAULT_BE
77 : #endif /* TEST_BE */
78 :
79 : const int RECORD_SIZE = 6144;
80 : const int ITERATIONS = 192;
81 :
82 : struct test_ctx {
83 : struct tevent_context *ev;
84 : struct ldb_context *ldb;
85 :
86 : const char *dbfile;
87 : const char *lockfile; /* lockfile is separate */
88 :
89 : const char *dbpath;
90 : };
91 :
92 6 : static void unlink_old_db(struct test_ctx *test_ctx)
93 : {
94 6 : int ret;
95 :
96 6 : errno = 0;
97 6 : ret = unlink(test_ctx->lockfile);
98 6 : if (ret == -1 && errno != ENOENT) {
99 0 : fail();
100 : }
101 :
102 6 : errno = 0;
103 6 : ret = unlink(test_ctx->dbfile);
104 6 : if (ret == -1 && errno != ENOENT) {
105 0 : fail();
106 : }
107 6 : }
108 :
109 3 : static int noconn_setup(void **state)
110 : {
111 3 : struct test_ctx *test_ctx;
112 :
113 3 : test_ctx = talloc_zero(NULL, struct test_ctx);
114 3 : assert_non_null(test_ctx);
115 :
116 3 : test_ctx->ev = tevent_context_init(test_ctx);
117 3 : assert_non_null(test_ctx->ev);
118 :
119 3 : test_ctx->ldb = ldb_init(test_ctx, test_ctx->ev);
120 3 : assert_non_null(test_ctx->ldb);
121 :
122 3 : test_ctx->dbfile = talloc_strdup(test_ctx, "lmdb_free_list_test.ldb");
123 3 : assert_non_null(test_ctx->dbfile);
124 :
125 6 : test_ctx->lockfile =
126 3 : talloc_asprintf(test_ctx, "%s-lock", test_ctx->dbfile);
127 3 : assert_non_null(test_ctx->lockfile);
128 :
129 6 : test_ctx->dbpath =
130 3 : talloc_asprintf(test_ctx, TEST_BE "://%s", test_ctx->dbfile);
131 3 : assert_non_null(test_ctx->dbpath);
132 :
133 3 : unlink_old_db(test_ctx);
134 3 : *state = test_ctx;
135 3 : return 0;
136 : }
137 :
138 3 : static int noconn_teardown(void **state)
139 : {
140 3 : struct test_ctx *test_ctx =
141 3 : talloc_get_type_abort(*state, struct test_ctx);
142 :
143 3 : unlink_old_db(test_ctx);
144 3 : talloc_free(test_ctx);
145 3 : return 0;
146 : }
147 :
148 3 : static int setup(void **state)
149 : {
150 3 : struct test_ctx *test_ctx;
151 3 : int ret;
152 3 : struct ldb_ldif *ldif;
153 3 : const char *index_ldif = "dn: @INDEXLIST\n"
154 : "@IDXGUID: objectUUID\n"
155 : "@IDX_DN_GUID: GUID\n"
156 : "\n";
157 : /*
158 : * Use a 1MiB DB for this test
159 : */
160 3 : const char *options[] = {"lmdb_env_size:1048576", NULL};
161 :
162 3 : noconn_setup((void **)&test_ctx);
163 :
164 3 : ret = ldb_connect(test_ctx->ldb, test_ctx->dbpath, 0, options);
165 3 : assert_int_equal(ret, 0);
166 :
167 6 : while ((ldif = ldb_ldif_read_string(test_ctx->ldb, &index_ldif))) {
168 3 : ret = ldb_add(test_ctx->ldb, ldif->msg);
169 3 : assert_int_equal(ret, LDB_SUCCESS);
170 : }
171 3 : *state = test_ctx;
172 3 : return 0;
173 : }
174 :
175 3 : static int teardown(void **state)
176 : {
177 6 : struct test_ctx *test_ctx =
178 3 : talloc_get_type_abort(*state, struct test_ctx);
179 3 : noconn_teardown((void **)&test_ctx);
180 3 : return 0;
181 : }
182 :
183 6 : static struct ldb_kv_private *get_ldb_kv(struct ldb_context *ldb)
184 : {
185 6 : void *data = NULL;
186 6 : struct ldb_kv_private *ldb_kv = NULL;
187 :
188 6 : data = ldb_module_get_private(ldb->modules);
189 6 : assert_non_null(data);
190 :
191 6 : ldb_kv = talloc_get_type(data, struct ldb_kv_private);
192 6 : assert_non_null(ldb_kv);
193 :
194 6 : return ldb_kv;
195 : }
196 :
197 3 : static int parse(struct ldb_val key, struct ldb_val data, void *private_data)
198 : {
199 3 : struct ldb_val *read = private_data;
200 :
201 : /* Yes, we leak this. That is OK */
202 3 : read->data = talloc_size(NULL, data.length);
203 3 : assert_non_null(read->data);
204 :
205 3 : memcpy(read->data, data.data, data.length);
206 3 : read->length = data.length;
207 3 : return LDB_SUCCESS;
208 : }
209 :
210 : /*
211 : * This test has the same structure as the test_free_list_read_lock
212 : * except the parent process does not keep the read lock open while the
213 : * child process is performing an update.
214 : */
215 1 : static void test_free_list_no_read_lock(void **state)
216 : {
217 1 : int ret;
218 1 : struct test_ctx *test_ctx =
219 1 : talloc_get_type_abort(*state, struct test_ctx);
220 1 : struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
221 1 : struct ldb_val key;
222 1 : struct ldb_val val;
223 :
224 1 : const char *KEY1 = "KEY01";
225 :
226 : /*
227 : * Pipes etc to coordinate the processes
228 : */
229 1 : int to_child[2];
230 1 : int to_parent[2];
231 1 : char buf[2];
232 1 : pid_t pid;
233 1 : size_t i;
234 :
235 1 : TALLOC_CTX *tmp_ctx;
236 1 : tmp_ctx = talloc_new(test_ctx);
237 1 : assert_non_null(tmp_ctx);
238 :
239 1 : ret = pipe(to_child);
240 1 : assert_int_equal(ret, 0);
241 1 : ret = pipe(to_parent);
242 1 : assert_int_equal(ret, 0);
243 : /*
244 : * Now fork a new process
245 : */
246 :
247 1 : pid = fork();
248 2 : if (pid == 0) {
249 : /*
250 : * Child process
251 : */
252 :
253 1 : struct ldb_context *ldb = NULL;
254 1 : close(to_child[1]);
255 1 : close(to_parent[0]);
256 :
257 : /*
258 : * Wait for the parent to get ready.
259 : */
260 1 : ret = read(to_child[0], buf, 2);
261 1 : assert_int_equal(ret, 2);
262 :
263 1 : ldb = ldb_init(test_ctx, test_ctx->ev);
264 1 : assert_non_null(ldb);
265 :
266 1 : ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL);
267 1 : assert_int_equal(ret, LDB_SUCCESS);
268 :
269 1 : ldb_kv = get_ldb_kv(ldb);
270 1 : assert_non_null(ldb_kv);
271 : /*
272 : * Add a record to the database
273 : */
274 1 : key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
275 1 : key.length = strlen(KEY1) + 1;
276 1 : val.data = talloc_zero_size(tmp_ctx, RECORD_SIZE);
277 1 : assert_non_null(val.data);
278 1 : memset(val.data, 'x', RECORD_SIZE);
279 1 : val.length = RECORD_SIZE;
280 : /*
281 : * Do more iterations than when a read lock, stale reader
282 : * active to confirm that the space is being re-used.
283 : */
284 1921 : for (i = 0; i < ITERATIONS * 10; i++) {
285 1920 : ret = ldb_kv->kv_ops->begin_write(ldb_kv);
286 1920 : assert_int_equal(ret, LDB_SUCCESS);
287 :
288 1920 : ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0);
289 1920 : assert_int_equal(ret, LDB_SUCCESS);
290 :
291 1920 : ret = ldb_kv->kv_ops->finish_write(ldb_kv);
292 1920 : assert_int_equal(ret, LDB_SUCCESS);
293 : }
294 :
295 : /*
296 : * Signal the parent that we've done the updates
297 : */
298 1 : ret = write(to_parent[1], "GO", 2);
299 1 : assert_int_equal(ret, 2);
300 1 : exit(0);
301 : }
302 :
303 1 : close(to_child[0]);
304 1 : close(to_parent[1]);
305 :
306 : /*
307 : * Begin a read transaction
308 : */
309 1 : ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
310 1 : assert_int_equal(ret, LDB_SUCCESS);
311 :
312 : /*
313 : * Now close it
314 : */
315 1 : ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
316 1 : assert_int_equal(ret, LDB_SUCCESS);
317 :
318 : /*
319 : * Signal the child process
320 : */
321 1 : ret = write(to_child[1], "GO", 2);
322 1 : assert_int_equal(2, ret);
323 :
324 : /*
325 : * Wait for the child process to update the record
326 : */
327 1 : ret = read(to_parent[0], buf, 2);
328 1 : assert_int_equal(2, ret);
329 :
330 : /*
331 : * Begin a read transaction
332 : */
333 1 : ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
334 1 : assert_int_equal(ret, LDB_SUCCESS);
335 : /*
336 : * read the record
337 : * and close the transaction
338 : */
339 1 : key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
340 1 : key.length = strlen(KEY1) + 1;
341 :
342 1 : ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
343 1 : assert_int_equal(ret, LDB_SUCCESS);
344 :
345 1 : ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
346 1 : assert_int_equal(ret, LDB_SUCCESS);
347 :
348 1 : close(to_child[1]);
349 1 : close(to_parent[0]);
350 1 : TALLOC_FREE(tmp_ctx);
351 1 : }
352 :
353 : /*
354 : * This test has the same structure as the test_free_list_read_lock
355 : * except the parent process keeps the read lock open while the
356 : * child process is performing an update.
357 : */
358 1 : static void test_free_list_read_lock(void **state)
359 : {
360 1 : int ret;
361 1 : struct test_ctx *test_ctx =
362 1 : talloc_get_type_abort(*state, struct test_ctx);
363 1 : struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
364 1 : struct ldb_val key;
365 1 : struct ldb_val val;
366 :
367 1 : const char *KEY1 = "KEY01";
368 :
369 : /*
370 : * Pipes etc to coordinate the processes
371 : */
372 1 : int to_child[2];
373 1 : int to_parent[2];
374 1 : char buf[2];
375 1 : pid_t pid;
376 1 : size_t i;
377 :
378 1 : TALLOC_CTX *tmp_ctx;
379 1 : tmp_ctx = talloc_new(test_ctx);
380 1 : assert_non_null(tmp_ctx);
381 :
382 1 : ret = pipe(to_child);
383 1 : assert_int_equal(ret, 0);
384 1 : ret = pipe(to_parent);
385 1 : assert_int_equal(ret, 0);
386 : /*
387 : * Now fork a new process
388 : */
389 :
390 1 : pid = fork();
391 2 : if (pid == 0) {
392 : /*
393 : * Child process
394 : */
395 :
396 1 : struct ldb_context *ldb = NULL;
397 1 : close(to_child[1]);
398 1 : close(to_parent[0]);
399 :
400 : /*
401 : * Wait for the transaction to start
402 : */
403 1 : ret = read(to_child[0], buf, 2);
404 1 : assert_int_equal(ret, 2);
405 :
406 1 : ldb = ldb_init(test_ctx, test_ctx->ev);
407 1 : assert_non_null(ldb);
408 :
409 1 : ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL);
410 1 : assert_int_equal(ret, LDB_SUCCESS);
411 :
412 1 : ldb_kv = get_ldb_kv(ldb);
413 1 : assert_non_null(ldb_kv);
414 : /*
415 : * Add a record to the database
416 : */
417 1 : key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
418 1 : key.length = strlen(KEY1) + 1;
419 1 : val.data = talloc_zero_size(tmp_ctx, RECORD_SIZE);
420 1 : assert_non_null(val.data);
421 1 : memset(val.data, 'x', RECORD_SIZE);
422 1 : val.length = RECORD_SIZE;
423 63 : for (i = 0; i < ITERATIONS; i++) {
424 63 : ret = ldb_kv->kv_ops->begin_write(ldb_kv);
425 63 : assert_int_equal(ret, 0);
426 63 : ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0);
427 63 : if (ret == LDB_ERR_BUSY && i > 0) {
428 1 : int rc = ldb_kv->kv_ops->abort_write(ldb_kv);
429 1 : assert_int_equal(rc, LDB_SUCCESS);
430 1 : break;
431 : }
432 62 : assert_int_equal(ret, LDB_SUCCESS);
433 62 : ret = ldb_kv->kv_ops->finish_write(ldb_kv);
434 62 : assert_int_equal(ret, LDB_SUCCESS);
435 : }
436 1 : assert_int_equal(ret, LDB_ERR_BUSY);
437 1 : assert_int_not_equal(i, 0);
438 :
439 : /*
440 : * Begin a read transaction
441 : */
442 1 : ret = ldb_kv->kv_ops->lock_read(ldb->modules);
443 1 : assert_int_equal(ret, LDB_SUCCESS);
444 : /*
445 : * read the record
446 : * and close the transaction
447 : */
448 1 : key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
449 1 : key.length = strlen(KEY1) + 1;
450 :
451 1 : ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
452 1 : assert_int_equal(ret, LDB_SUCCESS);
453 :
454 1 : ret = ldb_kv->kv_ops->unlock_read(ldb->modules);
455 1 : assert_int_equal(ret, LDB_SUCCESS);
456 :
457 : /*
458 : * Signal the the parent that we've done the update
459 : */
460 1 : ret = write(to_parent[1], "GO", 2);
461 1 : assert_int_equal(ret, 2);
462 1 : exit(0);
463 : }
464 :
465 1 : close(to_child[0]);
466 1 : close(to_parent[1]);
467 :
468 : /*
469 : * Begin a read transaction
470 : */
471 1 : ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
472 1 : assert_int_equal(ret, LDB_SUCCESS);
473 :
474 : /*
475 : * Signal the child process
476 : */
477 1 : ret = write(to_child[1], "GO", 2);
478 1 : assert_int_equal(ret, 2);
479 :
480 : /*
481 : * Wait for the child process to update the record
482 : */
483 1 : ret = read(to_parent[0], buf, 2);
484 1 : assert_int_equal(ret, 2);
485 :
486 : /*
487 : * read the record
488 : * and close the transaction
489 : */
490 1 : key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
491 1 : key.length = strlen(KEY1) + 1;
492 :
493 1 : ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
494 1 : assert_int_equal(ret, LDB_ERR_NO_SUCH_OBJECT);
495 1 : ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
496 1 : assert_int_equal(ret, 0);
497 :
498 1 : close(to_child[1]);
499 1 : close(to_parent[0]);
500 1 : TALLOC_FREE(tmp_ctx);
501 1 : }
502 :
503 : /*
504 : * This tests forks a child process that opens a read lock and then
505 : * exits. This results in a stale reader entry in the lmdb lock file.
506 : */
507 1 : static void test_free_list_stale_reader(void **state)
508 : {
509 1 : int ret;
510 1 : struct test_ctx *test_ctx =
511 1 : talloc_get_type_abort(*state, struct test_ctx);
512 1 : struct ldb_kv_private *ldb_kv = get_ldb_kv(test_ctx->ldb);
513 1 : struct ldb_val key;
514 1 : struct ldb_val val;
515 :
516 1 : const char *KEY1 = "KEY01";
517 :
518 : /*
519 : * Pipes etc to coordinate the processes
520 : */
521 1 : int to_child[2];
522 1 : int to_parent[2];
523 1 : char buf[2];
524 1 : pid_t pid;
525 1 : size_t i;
526 :
527 1 : TALLOC_CTX *tmp_ctx;
528 1 : tmp_ctx = talloc_new(test_ctx);
529 1 : assert_non_null(tmp_ctx);
530 :
531 1 : ret = pipe(to_child);
532 1 : assert_int_equal(ret, 0);
533 1 : ret = pipe(to_parent);
534 1 : assert_int_equal(ret, 0);
535 : /*
536 : * Now fork a new process
537 : */
538 :
539 1 : pid = fork();
540 2 : if (pid == 0) {
541 : /*
542 : * Child process
543 : */
544 :
545 1 : struct ldb_context *ldb = NULL;
546 1 : close(to_child[1]);
547 1 : close(to_parent[0]);
548 :
549 : /*
550 : * Wait for the parent to get ready
551 : */
552 1 : ret = read(to_child[0], buf, 2);
553 1 : assert_int_equal(ret, 2);
554 :
555 1 : ldb = ldb_init(test_ctx, test_ctx->ev);
556 1 : assert_non_null(ldb);
557 :
558 1 : ret = ldb_connect(ldb, test_ctx->dbpath, 0, NULL);
559 1 : assert_int_equal(ret, LDB_SUCCESS);
560 :
561 1 : ldb_kv = get_ldb_kv(ldb);
562 1 : assert_non_null(ldb_kv);
563 :
564 : /*
565 : * Begin a read transaction
566 : */
567 1 : ret = ldb_kv->kv_ops->lock_read(ldb->modules);
568 1 : assert_int_equal(ret, LDB_SUCCESS);
569 :
570 : /*
571 : * Now exit with out releasing the read lock
572 : * this will result in a stale entry in the
573 : * read lock table.
574 : */
575 :
576 1 : exit(0);
577 : }
578 :
579 1 : close(to_child[0]);
580 1 : close(to_parent[1]);
581 :
582 : /*
583 : * Tell the child to start
584 : */
585 1 : ret = write(to_child[1], "GO", 2);
586 1 : assert_int_equal(ret, 2);
587 :
588 1 : close(to_child[1]);
589 1 : close(to_parent[0]);
590 :
591 : /*
592 : * Now wait for the child process to complete
593 : */
594 1 : waitpid(pid, NULL, 0);
595 :
596 : /*
597 : * Add a record to the database
598 : */
599 1 : key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
600 1 : key.length = strlen(KEY1) + 1;
601 1 : val.data = talloc_zero_size(tmp_ctx, RECORD_SIZE);
602 1 : assert_non_null(val.data);
603 1 : memset(val.data, 'x', RECORD_SIZE);
604 1 : val.length = RECORD_SIZE;
605 193 : for (i = 0; i < ITERATIONS; i++) {
606 192 : ret = ldb_kv->kv_ops->begin_write(ldb_kv);
607 192 : assert_int_equal(ret, LDB_SUCCESS);
608 :
609 192 : ret = ldb_kv->kv_ops->store(ldb_kv, key, val, 0);
610 192 : if (ret == LDB_ERR_BUSY && i > 0) {
611 0 : int rc = ldb_kv->kv_ops->abort_write(ldb_kv);
612 0 : assert_int_equal(rc, LDB_SUCCESS);
613 0 : break;
614 : }
615 192 : assert_int_equal(ret, LDB_SUCCESS);
616 :
617 192 : ret = ldb_kv->kv_ops->finish_write(ldb_kv);
618 192 : assert_int_equal(ret, LDB_SUCCESS);
619 : }
620 : /*
621 : * We now do an explicit clear of stale readers at the start of a
622 : * write transaction so should not get LDB_ERR_BUSY any more
623 : * assert_int_equal(ret, LDB_ERR_BUSY);
624 : */
625 1 : assert_int_equal(ret, LDB_SUCCESS);
626 1 : assert_int_not_equal(i, 0);
627 :
628 : /*
629 : * Begin a read transaction
630 : */
631 1 : ret = ldb_kv->kv_ops->lock_read(test_ctx->ldb->modules);
632 1 : assert_int_equal(ret, LDB_SUCCESS);
633 : /*
634 : * read the record
635 : * and close the transaction
636 : */
637 1 : key.data = (uint8_t *)talloc_strdup(tmp_ctx, KEY1);
638 1 : key.length = strlen(KEY1) + 1;
639 :
640 1 : ret = ldb_kv->kv_ops->fetch_and_parse(ldb_kv, key, parse, &val);
641 1 : assert_int_equal(ret, LDB_SUCCESS);
642 :
643 1 : ret = ldb_kv->kv_ops->unlock_read(test_ctx->ldb->modules);
644 1 : assert_int_equal(ret, LDB_SUCCESS);
645 :
646 1 : TALLOC_FREE(tmp_ctx);
647 1 : }
648 :
649 1 : int main(int argc, const char **argv)
650 : {
651 1 : const struct CMUnitTest tests[] = {
652 : cmocka_unit_test_setup_teardown(
653 : test_free_list_no_read_lock, setup, teardown),
654 : cmocka_unit_test_setup_teardown(
655 : test_free_list_read_lock, setup, teardown),
656 : cmocka_unit_test_setup_teardown(
657 : test_free_list_stale_reader, setup, teardown),
658 : };
659 :
660 1 : cmocka_set_message_output(CM_OUTPUT_SUBUNIT);
661 :
662 1 : return cmocka_run_group_tests(tests, NULL, NULL);
663 : }
|