Blender V5.0
BLI_array_store_test.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2023 Blender Authors
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include "testing/testing.h"
6
7#include "MEM_guardedalloc.h"
8
9#include "BLI_array_store.h"
10#include "BLI_array_utils.h"
11#include "BLI_listbase.h"
12#include "BLI_rand.h"
14#include "BLI_string.h"
15#include "BLI_sys_types.h"
16#include "BLI_utildefines.h"
17
18/* print memory savings */
19// #define DEBUG_PRINT
20
21/* Print time. */
22// #define DEBUG_TIME
23#ifdef DEBUG_TIME
24# include "BLI_time.h"
25# include "BLI_time_utildefines.h"
26#endif
27
28/* -------------------------------------------------------------------- */
29/* Helper functions */
30
31#ifdef DEBUG_PRINT
32static void print_mem_saved(const char *id, const BArrayStore *bs)
33{
34 const double size_real = BLI_array_store_calc_size_compacted_get(bs);
35 const double size_expand = BLI_array_store_calc_size_expanded_get(bs);
36 const double percent = size_expand ? ((size_real / size_expand) * 100.0) : -1.0;
37 printf("%s: %.8f%%\n", id, percent);
38}
39#endif
40
41/* -------------------------------------------------------------------- */
42/* Test Chunks (building data from list of chunks) */
43
44struct TestChunk {
46 const void *data;
47 size_t data_len;
48};
49
50static TestChunk *testchunk_list_add(ListBase *lb, const void *data, size_t data_len)
51{
52 TestChunk *tc = MEM_mallocN<TestChunk>(__func__);
53 tc->data = data;
54 tc->data_len = data_len;
55 BLI_addtail(lb, tc);
56
57 return tc;
58}
59
60#if 0
61static TestChunk *testchunk_list_add_copydata(ListBase *lb, const void *data, size_t data_len)
62{
63 void *data_copy = MEM_mallocN(data_len, __func__);
64 memcpy(data_copy, data, data_len);
65 return testchunk_list_add(lb, data_copy, data_len);
66}
67#endif
68
70{
71 for (TestChunk *tc = (TestChunk *)lb->first, *tb_next; tc; tc = tb_next) {
72 tb_next = tc->next;
73 MEM_freeN(const_cast<void *>(tc->data));
74 MEM_freeN(tc);
75 }
77}
78
79#if 0
80static char *testchunk_as_data(ListBase *lb, size_t *r_data_len)
81{
82 size_t data_len = 0;
83 for (TestChunk *tc = (TestChunk *)lb->first; tc; tc = tc->next) {
84 data_len += tc->data_len;
85 }
86 char *data = MEM_malloc_arrayN<char>(data_len, __func__);
87 size_t i = 0;
88 for (TestChunk *tc = (TestChunk *)lb->first; tc; tc = tc->next) {
89 memcpy(&data[i], tc->data, tc->data_len);
90 data_len += tc->data_len;
91 i += tc->data_len;
92 }
93 if (r_data_len) {
94 *r_data_len = i;
95 }
96 return data;
97}
98#endif
99
100static char *testchunk_as_data_array(TestChunk **tc_array, int tc_array_len, size_t *r_data_len)
101{
102 size_t data_len = 0;
103 for (int tc_index = 0; tc_index < tc_array_len; tc_index++) {
104 data_len += tc_array[tc_index]->data_len;
105 }
106 char *data = MEM_malloc_arrayN<char>(data_len, __func__);
107 size_t i = 0;
108 for (int tc_index = 0; tc_index < tc_array_len; tc_index++) {
109 TestChunk *tc = tc_array[tc_index];
110 memcpy(&data[i], tc->data, tc->data_len);
111 i += tc->data_len;
112 }
113 if (r_data_len) {
114 *r_data_len = i;
115 }
116 return data;
117}
118
119/* -------------------------------------------------------------------- */
120/* Test Buffer */
121
122/* API to handle local allocation of data so we can compare it with the data in the array_store */
125 const void *data;
126 size_t data_len;
127
128 /* for reference */
130};
131
132static TestBuffer *testbuffer_list_add(ListBase *lb, const void *data, size_t data_len)
133{
134 TestBuffer *tb = MEM_mallocN<TestBuffer>(__func__);
135 tb->data = data;
136 tb->data_len = data_len;
137 tb->state = nullptr;
138 BLI_addtail(lb, tb);
139 return tb;
140}
141
142static TestBuffer *testbuffer_list_add_copydata(ListBase *lb, const void *data, size_t data_len)
143{
144 void *data_copy = MEM_mallocN(data_len, __func__);
145 memcpy(data_copy, data, data_len);
146 return testbuffer_list_add(lb, data_copy, data_len);
147}
148
149static void testbuffer_list_state_from_data(ListBase *lb, const char *data, const size_t data_len)
150{
151 testbuffer_list_add_copydata(lb, (const void *)data, data_len);
152}
153
159 const char *data,
160 const size_t data_len,
161 const size_t stride)
162{
163 if (stride == 1) {
165 }
166 else {
167 const size_t data_stride_len = data_len * stride;
168 char *data_stride = MEM_malloc_arrayN<char>(data_stride_len, __func__);
169
170 for (size_t i = 0, i_stride = 0; i < data_len; i += 1, i_stride += stride) {
171 memset(&data_stride[i_stride], data[i], stride);
172 }
173
174 testbuffer_list_add(lb, (const void *)data_stride, data_stride_len);
175 }
176}
177
178#define testbuffer_list_state_from_string_array(lb, data_array) \
179 { \
180 uint i_ = 0; \
181 const char *data; \
182 while ((data = data_array[i_++])) { \
183 testbuffer_list_state_from_data(lb, data, strlen(data)); \
184 } \
185 } \
186 ((void)0)
187
188//
189
190#define TESTBUFFER_STRINGS_CREATE(lb, ...) \
191 { \
192 BLI_listbase_clear(lb); \
193 const char *data_array[] = {__VA_ARGS__ nullptr}; \
194 testbuffer_list_state_from_string_array((lb), data_array); \
195 } \
196 ((void)0)
197
198/* test in both directions */
199#define TESTBUFFER_STRINGS(stride, chunk_count, ...) \
200 { \
201 ListBase lb; \
202 TESTBUFFER_STRINGS_CREATE(&lb, __VA_ARGS__); \
203\
204 testbuffer_run_tests_simple(&lb, stride, chunk_count); \
205\
206 testbuffer_list_free(&lb); \
207 } \
208 ((void)0)
209
211{
212 size_t data_state_len;
213 bool ok = true;
214 void *data_state = BLI_array_store_state_data_get_alloc(tb->state, &data_state_len);
215 if (tb->data_len != data_state_len) {
216 ok = false;
217 }
218 else if (memcmp(data_state, tb->data, data_state_len) != 0) {
219 ok = false;
220 }
221 MEM_freeN(data_state);
222 return ok;
223}
224
225static bool testbuffer_list_validate(const ListBase *lb)
226{
227 LISTBASE_FOREACH (TestBuffer *, tb, lb) {
228 if (!testbuffer_item_validate(tb)) {
229 return false;
230 }
231 }
232
233 return true;
234}
235
236static void testbuffer_list_data_randomize(ListBase *lb, uint random_seed)
237{
238 LISTBASE_FOREACH (TestBuffer *, tb, lb) {
239 BLI_array_randomize((void *)tb->data, 1, tb->data_len, random_seed++);
240 }
241}
242
244{
245 for (TestBuffer *tb = (TestBuffer *)lb->first, *tb_prev = nullptr; tb;
246 tb_prev = tb, tb = tb->next)
247 {
248 tb->state = BLI_array_store_state_add(
249 bs, tb->data, tb->data_len, (tb_prev ? tb_prev->state : nullptr));
250 }
251}
252
254{
255 LISTBASE_FOREACH (TestBuffer *, tb, lb) {
256 BLI_array_store_state_remove(bs, tb->state);
257 tb->state = nullptr;
258 }
259}
260
262{
263 for (TestBuffer *tb = (TestBuffer *)lb->first, *tb_next; tb; tb = tb_next) {
264 tb_next = tb->next;
265 MEM_freeN(const_cast<void *>(tb->data));
266 MEM_freeN(tb);
267 }
269}
270
272{
274 EXPECT_TRUE(testbuffer_list_validate(lb));
275 EXPECT_TRUE(BLI_array_store_is_valid(bs));
276#ifdef DEBUG_PRINT
277 print_mem_saved("data", bs);
278#endif
279}
280
281/* avoid copy-paste code to run tests */
283{
284 /* forwards */
287
289
290 /* backwards */
293}
294
295static void testbuffer_run_tests_simple(ListBase *lb, const int stride, const int chunk_count)
296{
297 BArrayStore *bs = BLI_array_store_create(stride, chunk_count);
298 testbuffer_run_tests(bs, lb);
300}
301
302/* -------------------------------------------------------------------- */
303/* Basic Tests */
304
305TEST(array_store, Nop)
306{
309}
310
311TEST(array_store, NopState)
312{
314 const uchar data[] = "test";
315 BArrayState *state = BLI_array_store_state_add(bs, data, sizeof(data) - 1, nullptr);
319}
320
321TEST(array_store, Single)
322{
324 const char data_src[] = "test";
325 const char *data_dst;
326 BArrayState *state = BLI_array_store_state_add(bs, data_src, sizeof(data_src), nullptr);
327 size_t data_dst_len;
328 data_dst = (char *)BLI_array_store_state_data_get_alloc(state, &data_dst_len);
329 EXPECT_STREQ(data_src, data_dst);
330 EXPECT_EQ(data_dst_len, sizeof(data_src));
332 MEM_freeN(data_dst);
333}
334
335TEST(array_store, DoubleNop)
336{
338 const char data_src[] = "test";
339 const char *data_dst;
340
341 BArrayState *state_a = BLI_array_store_state_add(bs, data_src, sizeof(data_src), nullptr);
342 BArrayState *state_b = BLI_array_store_state_add(bs, data_src, sizeof(data_src), state_a);
343
345 EXPECT_EQ(BLI_array_store_calc_size_expanded_get(bs), sizeof(data_src) * 2);
346
347 size_t data_dst_len;
348
349 data_dst = (char *)BLI_array_store_state_data_get_alloc(state_a, &data_dst_len);
350 EXPECT_STREQ(data_src, data_dst);
351 MEM_freeN(data_dst);
352
353 data_dst = (char *)BLI_array_store_state_data_get_alloc(state_b, &data_dst_len);
354 EXPECT_STREQ(data_src, data_dst);
355 MEM_freeN(data_dst);
356
357 EXPECT_EQ(data_dst_len, sizeof(data_src));
359}
360
361TEST(array_store, DoubleDiff)
362{
364 const char data_src_a[] = "test";
365 const char data_src_b[] = "####";
366 const char *data_dst;
367
368 BArrayState *state_a = BLI_array_store_state_add(bs, data_src_a, sizeof(data_src_a), nullptr);
369 BArrayState *state_b = BLI_array_store_state_add(bs, data_src_b, sizeof(data_src_b), state_a);
370 size_t data_dst_len;
371
372 EXPECT_EQ(BLI_array_store_calc_size_compacted_get(bs), sizeof(data_src_a) * 2);
373 EXPECT_EQ(BLI_array_store_calc_size_expanded_get(bs), sizeof(data_src_a) * 2);
374
375 data_dst = (char *)BLI_array_store_state_data_get_alloc(state_a, &data_dst_len);
376 EXPECT_STREQ(data_src_a, data_dst);
377 MEM_freeN(data_dst);
378
379 data_dst = (char *)BLI_array_store_state_data_get_alloc(state_b, &data_dst_len);
380 EXPECT_STREQ(data_src_b, data_dst);
381 MEM_freeN(data_dst);
382
384}
385
386TEST(array_store, TextMixed)
387{
388 TESTBUFFER_STRINGS(1, 4, "", );
389 TESTBUFFER_STRINGS(1, 4, "test", );
390 TESTBUFFER_STRINGS(1, 4, "", "test", );
391 TESTBUFFER_STRINGS(1, 4, "test", "", );
392 TESTBUFFER_STRINGS(1, 4, "test", "", "test", );
393 TESTBUFFER_STRINGS(1, 4, "", "test", "", );
394}
395
396TEST(array_store, TextDupeIncreaseDecrease)
397{
398 ListBase lb;
399
400#define D "#1#2#3#4"
401 TESTBUFFER_STRINGS_CREATE(&lb, D, D D, D D D, D D D D, );
402
404
405 /* forward */
407 EXPECT_TRUE(testbuffer_list_validate(&lb));
408 EXPECT_TRUE(BLI_array_store_is_valid(bs));
410
413
414 /* backwards */
416 EXPECT_TRUE(testbuffer_list_validate(&lb));
417 EXPECT_TRUE(BLI_array_store_is_valid(bs));
418 /* larger since first block doesn't de-duplicate */
420
421#undef D
423
425}
426
427/* -------------------------------------------------------------------- */
428/* Plain Text Tests */
429
434static void plain_text_helper(const char *words,
435 int words_len,
436 const char word_delim,
437 const int stride,
438 const int chunk_count,
439 const int random_seed)
440{
441
442 ListBase lb;
444
445 for (int i = 0, i_prev = 0; i < words_len; i++) {
446 if (ELEM(words[i], word_delim, '\0')) {
447 if (i != i_prev) {
448 testbuffer_list_state_from_data__stride_expand(&lb, &words[i_prev], i - i_prev, stride);
449 }
450 i_prev = i;
451 }
452 }
453
454 if (random_seed) {
455 testbuffer_list_data_randomize(&lb, random_seed);
456 }
457
458 testbuffer_run_tests_simple(&lb, stride, chunk_count);
459
461}
462
463/* split by '.' (multiple words) */
464#define WORDS words10k, sizeof(words10k)
465TEST(array_store, TextSentences_Chunk1)
466{
467 plain_text_helper(WORDS, '.', 1, 1, 0);
468}
469TEST(array_store, TextSentences_Chunk2)
470{
471 plain_text_helper(WORDS, '.', 1, 2, 0);
472}
473TEST(array_store, TextSentences_Chunk8)
474{
475 plain_text_helper(WORDS, '.', 1, 8, 0);
476}
477TEST(array_store, TextSentences_Chunk32)
478{
479 plain_text_helper(WORDS, '.', 1, 32, 0);
480}
481TEST(array_store, TextSentences_Chunk128)
482{
483 plain_text_helper(WORDS, '.', 1, 128, 0);
484}
485TEST(array_store, TextSentences_Chunk1024)
486{
487 plain_text_helper(WORDS, '.', 1, 1024, 0);
488}
489/* odd numbers */
490TEST(array_store, TextSentences_Chunk3)
491{
492 plain_text_helper(WORDS, '.', 1, 3, 0);
493}
494TEST(array_store, TextSentences_Chunk13)
495{
496 plain_text_helper(WORDS, '.', 1, 13, 0);
497}
498TEST(array_store, TextSentences_Chunk131)
499{
500 plain_text_helper(WORDS, '.', 1, 131, 0);
501}
502
503/* split by ' ', individual words */
504TEST(array_store, TextWords_Chunk1)
505{
506 plain_text_helper(WORDS, ' ', 1, 1, 0);
507}
508TEST(array_store, TextWords_Chunk2)
509{
510 plain_text_helper(WORDS, ' ', 1, 2, 0);
511}
512TEST(array_store, TextWords_Chunk8)
513{
514 plain_text_helper(WORDS, ' ', 1, 8, 0);
515}
516TEST(array_store, TextWords_Chunk32)
517{
518 plain_text_helper(WORDS, ' ', 1, 32, 0);
519}
520TEST(array_store, TextWords_Chunk128)
521{
522 plain_text_helper(WORDS, ' ', 1, 128, 0);
523}
524TEST(array_store, TextWords_Chunk1024)
525{
526 plain_text_helper(WORDS, ' ', 1, 1024, 0);
527}
528/* odd numbers */
529TEST(array_store, TextWords_Chunk3)
530{
531 plain_text_helper(WORDS, ' ', 1, 3, 0);
532}
533TEST(array_store, TextWords_Chunk13)
534{
535 plain_text_helper(WORDS, ' ', 1, 13, 0);
536}
537TEST(array_store, TextWords_Chunk131)
538{
539 plain_text_helper(WORDS, ' ', 1, 131, 0);
540}
541
542/* various tests with different strides & randomizing */
543TEST(array_store, TextSentencesRandom_Stride3_Chunk3)
544{
545 plain_text_helper(WORDS, 'q', 3, 3, 7337);
546}
547TEST(array_store, TextSentencesRandom_Stride8_Chunk8)
548{
549 plain_text_helper(WORDS, 'n', 8, 8, 5667);
550}
551TEST(array_store, TextSentencesRandom_Stride32_Chunk1)
552{
553 plain_text_helper(WORDS, 'a', 1, 32, 1212);
554}
555TEST(array_store, TextSentencesRandom_Stride12_Chunk512)
556{
557 plain_text_helper(WORDS, 'g', 12, 512, 9999);
558}
559TEST(array_store, TextSentencesRandom_Stride128_Chunk6)
560{
561 plain_text_helper(WORDS, 'b', 20, 6, 1000);
562}
563
564#undef WORDS
565
566/* -------------------------------------------------------------------- */
567/* Random Data Tests */
568
570{
571 if (min_i == max_i) {
572 return min_i;
573 }
575 BLI_assert(((min_i % step) == 0) && ((max_i % step) == 0));
576 uint range = (max_i - min_i);
577 uint value = BLI_rng_get_uint(rng) % range;
578 value = (value / step) * step;
579 return min_i + value;
580}
581
583 const size_t stride,
584 const size_t data_min_len,
585 const size_t data_max_len,
586
587 const uint mutate,
588 RNG *rng)
589{
590 size_t data_len = rand_range_i(rng, data_min_len, data_max_len + stride, stride);
591 char *data = MEM_malloc_arrayN<char>(data_len, __func__);
592
593 if (lb->last == nullptr) {
594 BLI_rng_get_char_n(rng, data, data_len);
595 }
596 else {
597 TestBuffer *tb_last = (TestBuffer *)lb->last;
598 if (tb_last->data_len >= data_len) {
599 memcpy(data, tb_last->data, data_len);
600 }
601 else {
602 memcpy(data, tb_last->data, tb_last->data_len);
603 BLI_rng_get_char_n(rng, &data[tb_last->data_len], data_len - tb_last->data_len);
604 }
605
606 /* perform multiple small mutations to the array. */
607 for (int i = 0; i < mutate; i++) {
608 enum {
609 MUTATE_NOP = 0,
610 MUTATE_ADD,
611 MUTATE_REMOVE,
612 MUTATE_ROTATE,
613 MUTATE_RANDOMIZE,
614 MUTATE_TOTAL,
615 };
616
617 switch (BLI_rng_get_uint(rng) % MUTATE_TOTAL) {
618 case MUTATE_NOP: {
619 break;
620 }
621 case MUTATE_ADD: {
622 const uint offset = rand_range_i(rng, 0, data_len, stride);
623 if (data_len < data_max_len) {
624 data_len += stride;
625 data = (char *)MEM_reallocN((void *)data, data_len);
626 memmove(&data[offset + stride], &data[offset], data_len - (offset + stride));
627 BLI_rng_get_char_n(rng, &data[offset], stride);
628 }
629 break;
630 }
631 case MUTATE_REMOVE: {
632 const uint offset = rand_range_i(rng, 0, data_len, stride);
633 if (data_len > data_min_len) {
634 memmove(&data[offset], &data[offset + stride], data_len - (offset + stride));
635 data_len -= stride;
636 }
637 break;
638 }
639 case MUTATE_ROTATE: {
640 int items = data_len / stride;
641 if (items > 1) {
642 _bli_array_wrap(data, items, stride, (BLI_rng_get_uint(rng) % 2) ? -1 : 1);
643 }
644 break;
645 }
646 case MUTATE_RANDOMIZE: {
647 if (data_len > 0) {
648 const uint offset = rand_range_i(rng, 0, data_len - stride, stride);
649 BLI_rng_get_char_n(rng, &data[offset], stride);
650 }
651 break;
652 }
653 default:
655 }
656 }
657 }
658
659 testbuffer_list_add(lb, (const void *)data, data_len);
660}
661
662static void random_data_mutate_helper(const int items_size_min,
663 const int items_size_max,
664 const int items_total,
665 const int stride,
666 const int chunk_count,
667 const int random_seed,
668 const int mutate)
669{
670
671 ListBase lb;
673
674 const size_t data_min_len = items_size_min * stride;
675 const size_t data_max_len = items_size_max * stride;
676
677 {
678 RNG *rng = BLI_rng_new(random_seed);
679 for (int i = 0; i < items_total; i++) {
680 testbuffer_list_state_random_data(&lb, stride, data_min_len, data_max_len, mutate, rng);
681 }
682 BLI_rng_free(rng);
683 }
684
685 testbuffer_run_tests_simple(&lb, stride, chunk_count);
686
688}
689
690TEST(array_store, TestData_Stride1_Chunk32_Mutate2)
691{
692 random_data_mutate_helper(0, 100, 400, 1, 32, 9779, 2);
693}
694TEST(array_store, TestData_Stride8_Chunk512_Mutate2)
695{
696 random_data_mutate_helper(0, 128, 400, 8, 512, 1001, 2);
697}
698TEST(array_store, TestData_Stride12_Chunk48_Mutate2)
699{
700 random_data_mutate_helper(200, 256, 400, 12, 48, 1331, 2);
701}
702TEST(array_store, TestData_Stride32_Chunk64_Mutate1)
703{
704 random_data_mutate_helper(0, 256, 200, 32, 64, 3112, 1);
705}
706TEST(array_store, TestData_Stride32_Chunk64_Mutate8)
707{
708 random_data_mutate_helper(0, 256, 200, 32, 64, 7117, 8);
709}
710
711/* -------------------------------------------------------------------- */
712/* Randomized Chunks Test */
713
715 const int chunks_per_buffer,
716 const int stride,
717 const int chunk_count,
718 const int random_seed)
719{
720 RNG *rng = BLI_rng_new(random_seed);
721 const size_t chunk_size_bytes = stride * chunk_count;
722 for (int i = 0; i < chunks_per_buffer; i++) {
723 char *data_chunk = MEM_malloc_arrayN<char>(chunk_size_bytes, __func__);
724 BLI_rng_get_char_n(rng, data_chunk, chunk_size_bytes);
725 testchunk_list_add(lb, data_chunk, chunk_size_bytes);
726 }
727 BLI_rng_free(rng);
728}
729
733static void random_chunk_mutate_helper(const int chunks_per_buffer,
734 const int items_total,
735 const int stride,
736 const int chunk_count,
737 const int random_seed)
738{
739 /* generate random chunks */
740
741 ListBase random_chunks;
742 BLI_listbase_clear(&random_chunks);
743 random_chunk_generate(&random_chunks, chunks_per_buffer, stride, chunk_count, random_seed);
744 TestChunk **chunks_array = MEM_malloc_arrayN<TestChunk *>(size_t(chunks_per_buffer), __func__);
745 {
746 TestChunk *tc = (TestChunk *)random_chunks.first;
747 for (int i = 0; i < chunks_per_buffer; i++, tc = tc->next) {
748 chunks_array[i] = tc;
749 }
750 }
751
752 /* add and re-order each time */
753 ListBase lb;
755
756 {
757 RNG *rng = BLI_rng_new(random_seed);
758 for (int i = 0; i < items_total; i++) {
759 BLI_rng_shuffle_array(rng, chunks_array, sizeof(TestChunk *), chunks_per_buffer);
760 size_t data_len;
761 char *data = testchunk_as_data_array(chunks_array, chunks_per_buffer, &data_len);
762 BLI_assert(data_len == chunks_per_buffer * chunk_count * stride);
763 testbuffer_list_add(&lb, (const void *)data, data_len);
764 }
765 BLI_rng_free(rng);
766 }
767
768 testchunk_list_free(&random_chunks);
769 MEM_freeN(chunks_array);
770
771 BArrayStore *bs = BLI_array_store_create(stride, chunk_count);
773
774 size_t expected_size = chunks_per_buffer * chunk_count * stride;
776
778
780}
781
782TEST(array_store, TestChunk_Rand8_Stride1_Chunk64)
783{
784 random_chunk_mutate_helper(8, 100, 1, 64, 9779);
785}
786TEST(array_store, TestChunk_Rand32_Stride1_Chunk64)
787{
788 random_chunk_mutate_helper(32, 100, 1, 64, 1331);
789}
790TEST(array_store, TestChunk_Rand64_Stride8_Chunk32)
791{
792 random_chunk_mutate_helper(64, 100, 8, 32, 2772);
793}
794TEST(array_store, TestChunk_Rand31_Stride11_Chunk21)
795{
796 random_chunk_mutate_helper(31, 100, 11, 21, 7117);
797}
798
799/* -------------------------------------------------------------------- */
802
803static bool rle_encode_decode_test(const uint8_t *data_dec,
804 size_t data_dec_len,
805 size_t *r_data_enc_len)
806{
807 size_t data_enc_len;
808 uint8_t *data_enc;
809
810#ifdef DEBUG_TIME
811 TIMEIT_START(encode);
812#endif
813 data_enc = BLI_array_store_rle_encode(data_dec, data_dec_len, 0, &data_enc_len);
814#ifdef DEBUG_TIME
815 TIMEIT_END(encode);
816#endif
817
818 uint8_t *data_dec_copy = MEM_malloc_arrayN<uint8_t>(data_dec_len, __func__);
819
820#ifdef DEBUG_TIME
821 TIMEIT_START(decode);
822#endif
823 BLI_array_store_rle_decode(data_enc, data_enc_len, data_dec_copy, data_dec_len);
824#ifdef DEBUG_TIME
825 TIMEIT_END(decode);
826#endif
827
828 MEM_freeN(data_enc);
829 const bool eq = memcmp(data_dec, data_dec_copy, data_dec_len) == 0;
830 MEM_freeN(data_dec_copy);
831 if (r_data_enc_len) {
832 *r_data_enc_len = data_enc_len;
833 }
834 return eq;
835}
836
840static void array_store_test_random_span_rle_encode(const size_t data_size,
841 const size_t span_size,
842 const int permitations)
843{
844 BLI_assert(data_size > span_size);
845
846 RNG *rng = BLI_rng_new(1);
847 uint8_t *data = MEM_malloc_arrayN<uint8_t>(data_size, __func__);
848 uint8_t *data_pattern = MEM_malloc_arrayN<uint8_t>(data_size, __func__);
849
850 for (int i = 0; i < data_size; i++) {
851 data_pattern[i] = i % 2;
852 }
853
854 /* Get the size without any RLE. */
855 const size_t data_enc_no_rle_len = [&data_pattern, &data_size]() -> size_t {
856 size_t data_enc_len;
857 rle_encode_decode_test(data_pattern, data_size, &data_enc_len);
858 return data_enc_len;
859 }();
860
861 for (int mutaiton = 0; mutaiton < permitations; mutaiton++) {
862 memcpy(data, data_pattern, data_size);
863
864 /* The first two mutations are always end-points. */
865 int index;
866 if (mutaiton == 0) {
867 index = 0;
868 }
869 else if (mutaiton == 1) {
870 index = int(data_size) - span_size;
871 }
872 else {
873 /* Place the span in a random location. */
874 index = BLI_rng_get_int(rng) % (data_size - span_size);
875 }
876
877 memset(data + index, 0, span_size);
878
879 size_t data_enc_len;
880 rle_encode_decode_test(data, data_size, &data_enc_len);
881
882 /* Ensure the RLE encoded version has at-least the memory reduction of the span. */
883 const size_t data_enc_len_expected_max = (data_enc_no_rle_len - span_size) +
884 (sizeof(size_t[2]) * 2);
885 EXPECT_LE(data_enc_len, data_enc_len_expected_max);
886 }
888 MEM_freeN(data_pattern);
889
890 BLI_rng_free(rng);
891}
892
893static void array_store_test_random_data_rle_encode(const size_t data_size,
894 const size_t data_ratio_size,
895 const int permitations)
896{
897 RNG *rng = BLI_rng_new(1);
898 uint8_t *data = MEM_malloc_arrayN<uint8_t>(data_size, __func__);
899
900 for (int mutaiton = 0; mutaiton < permitations; mutaiton++) {
901 memset(data, 1, data_ratio_size);
902 memset(data + data_ratio_size, 0, data_size - data_ratio_size);
903
904 BLI_rng_shuffle_array(rng, data, 1, data_size);
905
906 size_t data_enc_len;
907 EXPECT_TRUE(rle_encode_decode_test(data, data_size, &data_enc_len));
908 }
909
911 BLI_rng_free(rng);
912}
913
915
916/* -------------------------------------------------------------------- */
919
920TEST(array_store, RLE_Simple)
921{
922 {
923 const uint8_t data[] = {0};
924 EXPECT_TRUE(rle_encode_decode_test(data, 0, nullptr));
925 }
926 {
927 const uint8_t data[] = {0};
928 EXPECT_TRUE(rle_encode_decode_test(data, sizeof(data), nullptr));
929 }
930 {
931 const uint8_t data[] = {1};
932 EXPECT_TRUE(rle_encode_decode_test(data, sizeof(data), nullptr));
933 }
934}
935
936TEST(array_store, RLE_Uniform)
937{
938 const uint8_t data_uniform[64] = {0};
939 uint8_t data_pattern[64] = {0};
940 for (int i = 0; i < sizeof(data_pattern); i += 2) {
941 data_pattern[i] = 1;
942 }
943
944 size_t data_uniform_enc_len = 0;
945 size_t data_pattern_enc_len = 0;
946
947 EXPECT_TRUE(rle_encode_decode_test(data_uniform, sizeof(data_uniform), &data_uniform_enc_len));
948 EXPECT_TRUE(rle_encode_decode_test(data_pattern, sizeof(data_pattern), &data_pattern_enc_len));
949
950 /* This depends on implementation details of header sizes.
951 * Since there is no intention to change these, allow this.
952 * They can always be updated as needed. */
953 EXPECT_EQ(data_uniform_enc_len, sizeof(size_t) + sizeof(uint8_t) + sizeof(size_t[2]));
954 EXPECT_EQ(data_pattern_enc_len, sizeof(data_uniform) + sizeof(size_t[4]));
955}
956
957TEST(array_store, RLE_Alignment)
958{
959 /* Use a size large enough to detect usable spans
960 * but not so large as to make the tests slow. */
961 const size_t data_len_max = sizeof(void *) * 8;
962 uint8_t *data_pattern = MEM_calloc_arrayN<uint8_t>(data_len_max, __func__);
963 for (size_t i = 0; i < data_len_max; i += 2) {
964 data_pattern[i] = 1;
965 }
966
967 /* Use allocations memory checking tools will report errors on invalid buffer read/writes.
968 * It's important to offset the start of the array so as to ensure searching the array
969 * is performed at different memory alignments.
970 * It's also important to use `malloc` not `MEM_mallocN` since these hide out of bounds reads. */
971 for (int data_len = 1; data_len < data_len_max; data_len += 1) {
972 uint8_t *data = static_cast<uint8_t *>(malloc(data_len));
973 for (size_t offset = 0; offset < sizeof(void *); offset += 1) {
974 uint8_t *data_offset = data + offset;
975 if (data_len <= offset) {
976 continue;
977 }
978 const size_t data_offset_len = data_len - offset;
979 memset(data, 0, data_offset_len);
980
981 /* Uniform data. */
982 EXPECT_TRUE(rle_encode_decode_test(data_offset, data_offset_len, nullptr));
983
984 /* Non-uniform data. */
985 memcpy(data_offset, data_pattern, data_offset_len);
986 EXPECT_TRUE(rle_encode_decode_test(data_offset, data_offset_len, nullptr));
987 }
988 free(data);
989 }
990 MEM_freeN(data_pattern);
991}
992
993TEST(array_store, RLE_RandomSpan)
994{
995 /* Enable if there is suspicion of rare edge cases causing problems. */
996 const bool do_stress_test = false;
997
998 const int permutations = do_stress_test ? 256 : 8;
999
1000 array_store_test_random_span_rle_encode(63, 31, permutations);
1001 array_store_test_random_span_rle_encode(63, 32, permutations);
1002 array_store_test_random_span_rle_encode(63, 33, permutations);
1003
1004 array_store_test_random_span_rle_encode(64, 31, permutations);
1005 array_store_test_random_span_rle_encode(64, 32, permutations);
1006 array_store_test_random_span_rle_encode(64, 33, permutations);
1007
1008 array_store_test_random_span_rle_encode(65, 31, permutations);
1009 array_store_test_random_span_rle_encode(65, 32, permutations);
1010 array_store_test_random_span_rle_encode(65, 33, permutations);
1011
1012 if (do_stress_test) {
1013 const int data_size_max = 256;
1014 const int margin = sizeof(size_t[2]);
1015 for (int data_size = margin; data_size < data_size_max; data_size++) {
1016 for (int span_size = 1; span_size < data_size - margin; span_size++) {
1017 array_store_test_random_span_rle_encode(data_size, span_size, permutations);
1018 }
1019 }
1020 }
1021}
1022
1023TEST(array_store, RLE_RandomBytes)
1024{
1025 /* Enable if there is suspicion of rare edge cases causing problems. */
1026 const bool do_stress_test = false;
1027
1028 const int permutations = do_stress_test ? 256 : 8;
1029
1030 array_store_test_random_data_rle_encode(128, 16, permutations);
1031 array_store_test_random_data_rle_encode(128, 32, permutations);
1032 array_store_test_random_data_rle_encode(128, 64, permutations);
1033 array_store_test_random_data_rle_encode(128, 128, permutations);
1034
1035 array_store_test_random_data_rle_encode(131, 16, permutations);
1036 array_store_test_random_data_rle_encode(131, 32, permutations);
1037 array_store_test_random_data_rle_encode(131, 64, permutations);
1038 array_store_test_random_data_rle_encode(131, 128, permutations);
1039
1040 if (do_stress_test) {
1041 const int data_size_max = 256;
1042 const int margin = sizeof(size_t[2]);
1043 for (int data_size = margin; data_size < data_size_max; data_size++) {
1044 for (int data_ratio_size = 1; data_ratio_size < data_size - 1; data_ratio_size++) {
1045 array_store_test_random_span_rle_encode(data_size, data_ratio_size, permutations);
1046 }
1047 }
1048 }
1049
1050 if (do_stress_test) {
1051 /* Stress random data, handy for timing (20 million). */
1052 const size_t data_len_large = 32000000;
1053 array_store_test_random_data_rle_encode(data_len_large, data_len_large / 2, 4);
1054 array_store_test_random_data_rle_encode(data_len_large, 0, 4);
1055 }
1056}
1057
1059
1060#if 0
1061
1062/* -------------------------------------------------------------------- */
1067
1068static void *file_read_binary_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size)
1069{
1070 FILE *fp = fopen(filepath, "rb");
1071 void *mem = nullptr;
1072
1073 if (fp) {
1074 long int filelen_read;
1075 fseek(fp, 0L, SEEK_END);
1076 const long int filelen = ftell(fp);
1077 if (filelen == -1) {
1078 goto finally;
1079 }
1080 fseek(fp, 0L, SEEK_SET);
1081
1082 mem = MEM_mallocN(filelen + pad_bytes, __func__);
1083 if (mem == nullptr) {
1084 goto finally;
1085 }
1086
1087 filelen_read = fread(mem, 1, filelen, fp);
1088 if ((filelen_read != filelen) || ferror(fp)) {
1089 MEM_freeN(mem);
1090 mem = nullptr;
1091 goto finally;
1092 }
1093
1094 *r_size = filelen_read;
1095
1096 finally:
1097 fclose(fp);
1098 }
1099
1100 return mem;
1101}
1102
1103TEST(array_store, PlainTextFiles)
1104{
1105 ListBase lb;
1106 BLI_listbase_clear(&lb);
1107 BArrayStore *bs = BLI_array_store_create(1, 128);
1108
1109 for (int i = 0; i < 629; i++) {
1110 char str[512];
1111 BLI_snprintf(str, sizeof(str), "/src/py_array_cow/test_data/xz_data/%04d.c.xz", i);
1112 // BLI_snprintf(str, sizeof(str), "/src/py_array_cow/test_data/c_code/%04d.c", i);
1113 // printf("%s\n", str);
1114 size_t data_len;
1115 void *data;
1116 data = file_read_binary_as_mem(str, 0, &data_len);
1117
1118 testbuffer_list_add(&lb, (const void *)data, data_len);
1119 }
1120
1121 /* forwards */
1123 EXPECT_TRUE(testbuffer_list_validate(&lb));
1124 EXPECT_TRUE(BLI_array_store_is_valid(bs));
1125# ifdef DEBUG_PRINT
1126 print_mem_saved("source code forward", bs);
1127# endif
1128
1131
1132 /* backwards */
1134 EXPECT_TRUE(testbuffer_list_validate(&lb));
1135 EXPECT_TRUE(BLI_array_store_is_valid(bs));
1136# ifdef DEBUG_PRINT
1137 print_mem_saved("source code backwards", bs);
1138# endif
1139
1142}
1143#endif
1144
Efficient in-memory storage of multiple similar arrays.
BArrayState * BLI_array_store_state_add(BArrayStore *bs, const void *data, size_t data_len, const BArrayState *state_reference)
void BLI_array_store_state_remove(BArrayStore *bs, BArrayState *state)
size_t BLI_array_store_calc_size_expanded_get(const BArrayStore *bs)
void * BLI_array_store_state_data_get_alloc(const BArrayState *state, size_t *r_data_len)
bool BLI_array_store_is_valid(BArrayStore *bs)
void BLI_array_store_rle_decode(const uint8_t *data_enc, const size_t data_enc_len, void *data_dec_v, const size_t data_dec_len)
size_t BLI_array_store_state_size_get(const BArrayState *state)
void BLI_array_store_destroy(BArrayStore *bs)
size_t BLI_array_store_calc_size_compacted_get(const BArrayStore *bs)
uint8_t * BLI_array_store_rle_encode(const uint8_t *data_dec, size_t data_dec_len, size_t data_enc_extra_size, size_t *r_data_enc_len)
BArrayStore * BLI_array_store_create(unsigned int stride, unsigned int chunk_count)
static void random_data_mutate_helper(const int items_size_min, const int items_size_max, const int items_total, const int stride, const int chunk_count, const int random_seed, const int mutate)
#define TESTBUFFER_STRINGS(stride, chunk_count,...)
static void testbuffer_list_state_from_data__stride_expand(ListBase *lb, const char *data, const size_t data_len, const size_t stride)
TEST(array_store, Nop)
static bool rle_encode_decode_test(const uint8_t *data_dec, size_t data_dec_len, size_t *r_data_enc_len)
#define TESTBUFFER_STRINGS_CREATE(lb,...)
static TestChunk * testchunk_list_add(ListBase *lb, const void *data, size_t data_len)
static void testbuffer_list_state_random_data(ListBase *lb, const size_t stride, const size_t data_min_len, const size_t data_max_len, const uint mutate, RNG *rng)
static void testbuffer_list_store_populate(BArrayStore *bs, ListBase *lb)
static void testbuffer_list_store_clear(BArrayStore *bs, ListBase *lb)
static bool testbuffer_item_validate(TestBuffer *tb)
static void testbuffer_list_data_randomize(ListBase *lb, uint random_seed)
static void array_store_test_random_span_rle_encode(const size_t data_size, const size_t span_size, const int permitations)
static TestBuffer * testbuffer_list_add_copydata(ListBase *lb, const void *data, size_t data_len)
static void random_chunk_mutate_helper(const int chunks_per_buffer, const int items_total, const int stride, const int chunk_count, const int random_seed)
static void testbuffer_run_tests(BArrayStore *bs, ListBase *lb)
static void testchunk_list_free(ListBase *lb)
static void testbuffer_run_tests_single(BArrayStore *bs, ListBase *lb)
static bool testbuffer_list_validate(const ListBase *lb)
static void array_store_test_random_data_rle_encode(const size_t data_size, const size_t data_ratio_size, const int permitations)
static void random_chunk_generate(ListBase *lb, const int chunks_per_buffer, const int stride, const int chunk_count, const int random_seed)
static TestBuffer * testbuffer_list_add(ListBase *lb, const void *data, size_t data_len)
static char * testchunk_as_data_array(TestChunk **tc_array, int tc_array_len, size_t *r_data_len)
static void testbuffer_list_state_from_data(ListBase *lb, const char *data, const size_t data_len)
static void plain_text_helper(const char *words, int words_len, const char word_delim, const int stride, const int chunk_count, const int random_seed)
#define WORDS
static void testbuffer_run_tests_simple(ListBase *lb, const int stride, const int chunk_count)
#define D
static void testbuffer_list_free(ListBase *lb)
static uint rand_range_i(RNG *rng, uint min_i, uint max_i, uint step)
Generic array manipulation API.
void _bli_array_wrap(void *arr_v, uint arr_len, size_t arr_stride, int dir)
#define BLI_assert_unreachable()
Definition BLI_assert.h:93
#define BLI_assert(a)
Definition BLI_assert.h:46
EXPECT_EQ(BLI_expr_pylike_eval(expr, nullptr, 0, &result), EXPR_PYLIKE_INVALID)
void BLI_kdtree_nd_ free(KDTree *tree)
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE void BLI_listbase_clear(ListBase *lb)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void void void void void void void BLI_listbase_reverse(ListBase *lb) ATTR_NONNULL(1)
Definition listbase.cc:836
Random number functions.
void int BLI_rng_get_int(struct RNG *rng) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition rand.cc:73
void BLI_rng_shuffle_array(struct RNG *rng, void *data, unsigned int elem_size_i, unsigned int elem_num) ATTR_NONNULL(1
struct RNG * BLI_rng_new(unsigned int seed)
Definition rand.cc:39
void BLI_rng_free(struct RNG *rng) ATTR_NONNULL(1)
Definition rand.cc:53
unsigned int BLI_rng_get_uint(struct RNG *rng) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition rand.cc:78
void BLI_rng_get_char_n(RNG *rng, char *bytes, size_t bytes_len) ATTR_NONNULL(1
void BLI_array_randomize(void *data, unsigned int elem_size, unsigned int elem_num, unsigned int seed)
Definition rand.cc:156
size_t BLI_snprintf(char *__restrict dst, size_t dst_maxncpy, const char *__restrict format,...) ATTR_NONNULL(1
unsigned char uchar
unsigned int uint
Platform independent time functions.
Utility defines for timing/benchmarks.
#define TIMEIT_START(var)
#define TIMEIT_END(var)
#define ELEM(...)
int max_i(int a, int b)
Definition Basic.c:11
int min_i(int a, int b)
Definition Basic.c:7
Read Guarded memory(de)allocation.
#define MEM_reallocN(vmemh, len)
BMesh const char void * data
#define str(s)
#define printf(...)
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
void * MEM_mallocN(size_t len, const char *str)
Definition mallocn.cc:128
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
static ulong state[N]
#define L
void * last
void * first
Definition rand.cc:33
BArrayState * state
const void * data
i
Definition text_draw.cc:230