Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorBrecht Van Lommel <brecht@blender.org>2020-08-07 17:43:42 +0300
committerBrecht Van Lommel <brecht@blender.org>2020-08-10 19:14:00 +0300
commit53d203dea8230da4e80f3cc61468a4e24ff6759c (patch)
tree3f1b7498fb1a3108e60a4355bec0e4eef76110e4 /source
parentaf77bf1f0f94cb07d5bf681d1f771d4106873780 (diff)
Tests: move remaining gtests into their own module folders
And make them part of the blender_test runner. The one exception is blenlib performance tests, which we don't want to run by default. They remain in their own executable. Differential Revision: https://developer.blender.org/D8498
Diffstat (limited to 'source')
-rw-r--r--source/blender/blenlib/CMakeLists.txt34
-rw-r--r--source/blender/blenlib/tests/BLI_array_store_test.cc884
-rw-r--r--source/blender/blenlib/tests/BLI_array_utils_test.cc191
-rw-r--r--source/blender/blenlib/tests/BLI_delaunay_2d_test.cc1762
-rw-r--r--source/blender/blenlib/tests/BLI_expr_pylike_eval_test.cc363
-rw-r--r--source/blender/blenlib/tests/BLI_ghash_test.cc209
-rw-r--r--source/blender/blenlib/tests/BLI_hash_mm2a_test.cc74
-rw-r--r--source/blender/blenlib/tests/BLI_heap_simple_test.cc121
-rw-r--r--source/blender/blenlib/tests/BLI_heap_test.cc207
-rw-r--r--source/blender/blenlib/tests/BLI_kdopbvh_test.cc134
-rw-r--r--source/blender/blenlib/tests/BLI_linklist_lockfree_test.cc111
-rw-r--r--source/blender/blenlib/tests/BLI_listbase_test.cc255
-rw-r--r--source/blender/blenlib/tests/BLI_math_base_test.cc115
-rw-r--r--source/blender/blenlib/tests/BLI_math_bits_test.cc50
-rw-r--r--source/blender/blenlib/tests/BLI_math_color_test.cc76
-rw-r--r--source/blender/blenlib/tests/BLI_math_geom_test.cc19
-rw-r--r--source/blender/blenlib/tests/BLI_math_matrix_test.cc99
-rw-r--r--source/blender/blenlib/tests/BLI_math_vector_test.cc47
-rw-r--r--source/blender/blenlib/tests/BLI_memiter_test.cc277
-rw-r--r--source/blender/blenlib/tests/BLI_path_util_test.cc606
-rw-r--r--source/blender/blenlib/tests/BLI_polyfill_2d_test.cc751
-rw-r--r--source/blender/blenlib/tests/BLI_ressource_strings.h610
-rw-r--r--source/blender/blenlib/tests/BLI_session_uuid_test.cc20
-rw-r--r--source/blender/blenlib/tests/BLI_stack_test.cc216
-rw-r--r--source/blender/blenlib/tests/BLI_string_test.cc794
-rw-r--r--source/blender/blenlib/tests/BLI_string_utf8_test.cc286
-rw-r--r--source/blender/blenlib/tests/BLI_task_graph_test.cc188
-rw-r--r--source/blender/blenlib/tests/BLI_task_test.cc183
-rw-r--r--source/blender/blenlib/tests/performance/BLI_ghash_performance_test.cc571
-rw-r--r--source/blender/blenlib/tests/performance/BLI_task_performance_test.cc210
-rw-r--r--source/blender/blenlib/tests/performance/CMakeLists.txt37
-rw-r--r--source/blender/blenloader/CMakeLists.txt14
-rw-r--r--source/blender/blenloader/tests/blendfile_load_test.cc31
-rw-r--r--source/blender/blenloader/tests/blendfile_loading_base_test.cc162
-rw-r--r--source/blender/blenloader/tests/blendfile_loading_base_test.h64
-rw-r--r--source/blender/bmesh/CMakeLists.txt13
-rw-r--r--source/blender/bmesh/tests/bmesh_core_test.cc40
-rw-r--r--source/blender/io/alembic/CMakeLists.txt14
-rw-r--r--source/blender/io/alembic/tests/abc_export_test.cc161
-rw-r--r--source/blender/io/alembic/tests/abc_matrix_test.cc292
-rw-r--r--source/blender/io/common/CMakeLists.txt5
-rw-r--r--source/blender/io/common/intern/abstract_hierarchy_iterator_test.cc3
42 files changed, 10296 insertions, 3 deletions
diff --git a/source/blender/blenlib/CMakeLists.txt b/source/blender/blenlib/CMakeLists.txt
index a5af517ecca..819c74b6946 100644
--- a/source/blender/blenlib/CMakeLists.txt
+++ b/source/blender/blenlib/CMakeLists.txt
@@ -347,26 +347,58 @@ blender_add_lib(bf_blenlib "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
if(WITH_GTESTS)
set(TEST_SRC
+ tests/BLI_array_store_test.cc
tests/BLI_array_test.cc
+ tests/BLI_array_utils_test.cc
+ tests/BLI_delaunay_2d_test.cc
tests/BLI_disjoint_set_test.cc
tests/BLI_edgehash_test.cc
+ tests/BLI_expr_pylike_eval_test.cc
+ tests/BLI_ghash_test.cc
+ tests/BLI_hash_mm2a_test.cc
+ tests/BLI_heap_simple_test.cc
+ tests/BLI_heap_test.cc
tests/BLI_index_mask_test.cc
tests/BLI_index_range_test.cc
+ tests/BLI_kdopbvh_test.cc
tests/BLI_linear_allocator_test.cc
+ tests/BLI_linklist_lockfree_test.cc
+ tests/BLI_listbase_test.cc
tests/BLI_map_test.cc
tests/BLI_math_base_safe_test.cc
+ tests/BLI_math_base_test.cc
+ tests/BLI_math_bits_test.cc
+ tests/BLI_math_color_test.cc
+ tests/BLI_math_geom_test.cc
+ tests/BLI_math_matrix_test.cc
+ tests/BLI_math_vector_test.cc
+ tests/BLI_memiter_test.cc
tests/BLI_memory_utils_test.cc
tests/BLI_multi_value_map_test.cc
+ tests/BLI_path_util_test.cc
+ tests/BLI_polyfill_2d_test.cc
+ tests/BLI_ressource_strings.h
+ tests/BLI_session_uuid_test.cc
tests/BLI_set_test.cc
tests/BLI_span_test.cc
tests/BLI_stack_cxx_test.cc
+ tests/BLI_stack_test.cc
tests/BLI_string_ref_test.cc
+ tests/BLI_string_test.cc
+ tests/BLI_string_utf8_test.cc
+ tests/BLI_task_graph_test.cc
+ tests/BLI_task_test.cc
tests/BLI_vector_set_test.cc
tests/BLI_vector_test.cc
)
+ set(TEST_INC
+ ../imbuf
+ )
set(TEST_LIB
bf_blenlib
)
include(GTestTesting)
- blender_add_test_lib(bf_bli_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
+ blender_add_test_lib(bf_blenlib_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
+
+ add_subdirectory(tests/performance)
endif()
diff --git a/source/blender/blenlib/tests/BLI_array_store_test.cc b/source/blender/blenlib/tests/BLI_array_store_test.cc
new file mode 100644
index 00000000000..a1ec8ec7bb3
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_array_store_test.cc
@@ -0,0 +1,884 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_array_store.h"
+#include "BLI_array_utils.h"
+#include "BLI_listbase.h"
+#include "BLI_rand.h"
+#include "BLI_ressource_strings.h"
+#include "BLI_string.h"
+#include "BLI_sys_types.h"
+#include "BLI_utildefines.h"
+
+/* print memory savings */
+// #define DEBUG_PRINT
+
+/* -------------------------------------------------------------------- */
+/* Helper functions */
+
+#ifdef DEBUG_PRINT
+static void print_mem_saved(const char *id, const BArrayStore *bs)
+{
+ const double size_real = BLI_array_store_calc_size_compacted_get(bs);
+ const double size_expand = BLI_array_store_calc_size_expanded_get(bs);
+ const double percent = size_expand ? ((size_real / size_expand) * 100.0) : -1.0;
+ printf("%s: %.8f%%\n", id, percent);
+}
+#endif
+
+/* -------------------------------------------------------------------- */
+/* Test Chunks (building data from list of chunks) */
+
+typedef struct TestChunk {
+ struct TestChunk *next, *prev;
+ const void *data;
+ size_t data_len;
+} TestChunk;
+
+static TestChunk *testchunk_list_add(ListBase *lb, const void *data, size_t data_len)
+{
+ TestChunk *tc = (TestChunk *)MEM_mallocN(sizeof(*tc), __func__);
+ tc->data = data;
+ tc->data_len = data_len;
+ BLI_addtail(lb, tc);
+
+ return tc;
+}
+
+#if 0
+static TestChunk *testchunk_list_add_copydata(ListBase *lb, const void *data, size_t data_len)
+{
+ void *data_copy = MEM_mallocN(data_len, __func__);
+ memcpy(data_copy, data, data_len);
+ return testchunk_list_add(lb, data_copy, data_len);
+}
+#endif
+
+static void testchunk_list_free(ListBase *lb)
+{
+ for (TestChunk *tc = (TestChunk *)lb->first, *tb_next; tc; tc = tb_next) {
+ tb_next = tc->next;
+ MEM_freeN((void *)tc->data);
+ MEM_freeN(tc);
+ }
+ BLI_listbase_clear(lb);
+}
+
+#if 0
+static char *testchunk_as_data(ListBase *lb, size_t *r_data_len)
+{
+ size_t data_len = 0;
+ for (TestChunk *tc = (TestChunk *)lb->first; tc; tc = tc->next) {
+ data_len += tc->data_len;
+ }
+ char *data = (char *)MEM_mallocN(data_len, __func__);
+ size_t i = 0;
+ for (TestChunk *tc = (TestChunk *)lb->first; tc; tc = tc->next) {
+ memcpy(&data[i], tc->data, tc->data_len);
+ data_len += tc->data_len;
+ i += tc->data_len;
+ }
+ if (r_data_len) {
+ *r_data_len = i;
+ }
+ return data;
+}
+#endif
+
+static char *testchunk_as_data_array(TestChunk **tc_array, int tc_array_len, size_t *r_data_len)
+{
+ size_t data_len = 0;
+ for (int tc_index = 0; tc_index < tc_array_len; tc_index++) {
+ data_len += tc_array[tc_index]->data_len;
+ }
+ char *data = (char *)MEM_mallocN(data_len, __func__);
+ size_t i = 0;
+ for (int tc_index = 0; tc_index < tc_array_len; tc_index++) {
+ TestChunk *tc = tc_array[tc_index];
+ memcpy(&data[i], tc->data, tc->data_len);
+ i += tc->data_len;
+ }
+ if (r_data_len) {
+ *r_data_len = i;
+ }
+ return data;
+}
+
+/* -------------------------------------------------------------------- */
+/* Test Buffer */
+
+/* API to handle local allocation of data so we can compare it with the data in the array_store */
+typedef struct TestBuffer {
+ struct TestBuffer *next, *prev;
+ const void *data;
+ size_t data_len;
+
+ /* for reference */
+ BArrayState *state;
+} TestBuffer;
+
+static TestBuffer *testbuffer_list_add(ListBase *lb, const void *data, size_t data_len)
+{
+ TestBuffer *tb = (TestBuffer *)MEM_mallocN(sizeof(*tb), __func__);
+ tb->data = data;
+ tb->data_len = data_len;
+ tb->state = NULL;
+ BLI_addtail(lb, tb);
+ return tb;
+}
+
+static TestBuffer *testbuffer_list_add_copydata(ListBase *lb, const void *data, size_t data_len)
+{
+ void *data_copy = MEM_mallocN(data_len, __func__);
+ memcpy(data_copy, data, data_len);
+ return testbuffer_list_add(lb, data_copy, data_len);
+}
+
+static void testbuffer_list_state_from_data(ListBase *lb, const char *data, const size_t data_len)
+{
+ testbuffer_list_add_copydata(lb, (const void *)data, data_len);
+}
+
+/**
+ * A version of testbuffer_list_state_from_data that expand data by stride,
+ * handy so we can test data at different strides.
+ */
+static void testbuffer_list_state_from_data__stride_expand(ListBase *lb,
+ const char *data,
+ const size_t data_len,
+ const size_t stride)
+{
+ if (stride == 1) {
+ testbuffer_list_state_from_data(lb, data, data_len);
+ }
+ else {
+ const size_t data_stride_len = data_len * stride;
+ char *data_stride = (char *)MEM_mallocN(data_stride_len, __func__);
+
+ for (size_t i = 0, i_stride = 0; i < data_len; i += 1, i_stride += stride) {
+ memset(&data_stride[i_stride], data[i], stride);
+ }
+
+ testbuffer_list_add(lb, (const void *)data_stride, data_stride_len);
+ }
+}
+
+#define testbuffer_list_state_from_string_array(lb, data_array) \
+ { \
+ unsigned int i_ = 0; \
+ const char *data; \
+ while ((data = data_array[i_++])) { \
+ testbuffer_list_state_from_data(lb, data, strlen(data)); \
+ } \
+ } \
+ ((void)0)
+
+//
+
+#define TESTBUFFER_STRINGS_CREATE(lb, ...) \
+ { \
+ BLI_listbase_clear(lb); \
+ const char *data_array[] = {__VA_ARGS__ NULL}; \
+ testbuffer_list_state_from_string_array((lb), data_array); \
+ } \
+ ((void)0)
+
+/* test in both directions */
+#define TESTBUFFER_STRINGS_EX(bs, ...) \
+ { \
+ ListBase lb; \
+ TESTBUFFER_STRINGS_CREATE(&lb, __VA_ARGS__); \
+\
+ testbuffer_run_tests(bs, &lb); \
+\
+ testbuffer_list_free(&lb); \
+ } \
+ ((void)0)
+
+#define TESTBUFFER_STRINGS(stride, chunk_count, ...) \
+ { \
+ ListBase lb; \
+ TESTBUFFER_STRINGS_CREATE(&lb, __VA_ARGS__); \
+\
+ testbuffer_run_tests_simple(&lb, stride, chunk_count); \
+\
+ testbuffer_list_free(&lb); \
+ } \
+ ((void)0)
+
+static bool testbuffer_item_validate(TestBuffer *tb)
+{
+ size_t data_state_len;
+ bool ok = true;
+ void *data_state = BLI_array_store_state_data_get_alloc(tb->state, &data_state_len);
+ if (tb->data_len != data_state_len) {
+ ok = false;
+ }
+ else if (memcmp(data_state, tb->data, data_state_len) != 0) {
+ ok = false;
+ }
+ MEM_freeN(data_state);
+ return ok;
+}
+
+static bool testbuffer_list_validate(const ListBase *lb)
+{
+ for (TestBuffer *tb = (TestBuffer *)lb->first; tb; tb = tb->next) {
+ if (!testbuffer_item_validate(tb)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void testbuffer_list_data_randomize(ListBase *lb, unsigned int random_seed)
+{
+ for (TestBuffer *tb = (TestBuffer *)lb->first; tb; tb = tb->next) {
+ BLI_array_randomize((void *)tb->data, 1, tb->data_len, random_seed++);
+ }
+}
+
+static void testbuffer_list_store_populate(BArrayStore *bs, ListBase *lb)
+{
+ for (TestBuffer *tb = (TestBuffer *)lb->first, *tb_prev = NULL; tb;
+ tb_prev = tb, tb = tb->next) {
+ tb->state = BLI_array_store_state_add(
+ bs, tb->data, tb->data_len, (tb_prev ? tb_prev->state : NULL));
+ }
+}
+
+static void testbuffer_list_store_clear(BArrayStore *bs, ListBase *lb)
+{
+ for (TestBuffer *tb = (TestBuffer *)lb->first; tb; tb = tb->next) {
+ BLI_array_store_state_remove(bs, tb->state);
+ tb->state = NULL;
+ }
+}
+
+static void testbuffer_list_free(ListBase *lb)
+{
+ for (TestBuffer *tb = (TestBuffer *)lb->first, *tb_next; tb; tb = tb_next) {
+ tb_next = tb->next;
+ MEM_freeN((void *)tb->data);
+ MEM_freeN(tb);
+ }
+ BLI_listbase_clear(lb);
+}
+
+static void testbuffer_run_tests_single(BArrayStore *bs, ListBase *lb)
+{
+ testbuffer_list_store_populate(bs, lb);
+ EXPECT_TRUE(testbuffer_list_validate(lb));
+ EXPECT_TRUE(BLI_array_store_is_valid(bs));
+#ifdef DEBUG_PRINT
+ print_mem_saved("data", bs);
+#endif
+}
+
+/* avoid copy-paste code to run tests */
+static void testbuffer_run_tests(BArrayStore *bs, ListBase *lb)
+{
+ /* forwards */
+ testbuffer_run_tests_single(bs, lb);
+ testbuffer_list_store_clear(bs, lb);
+
+ BLI_listbase_reverse(lb);
+
+ /* backwards */
+ testbuffer_run_tests_single(bs, lb);
+ testbuffer_list_store_clear(bs, lb);
+}
+
+static void testbuffer_run_tests_simple(ListBase *lb, const int stride, const int chunk_count)
+{
+ BArrayStore *bs = BLI_array_store_create(stride, chunk_count);
+ testbuffer_run_tests(bs, lb);
+ BLI_array_store_destroy(bs);
+}
+
+/* -------------------------------------------------------------------- */
+/* Basic Tests */
+
+TEST(array_store, Nop)
+{
+ BArrayStore *bs = BLI_array_store_create(1, 32);
+ BLI_array_store_destroy(bs);
+}
+
+TEST(array_store, NopState)
+{
+ BArrayStore *bs = BLI_array_store_create(1, 32);
+ const unsigned char data[] = "test";
+ BArrayState *state = BLI_array_store_state_add(bs, data, sizeof(data) - 1, NULL);
+ EXPECT_EQ(BLI_array_store_state_size_get(state), sizeof(data) - 1);
+ BLI_array_store_state_remove(bs, state);
+ BLI_array_store_destroy(bs);
+}
+
+TEST(array_store, Single)
+{
+ BArrayStore *bs = BLI_array_store_create(1, 32);
+ const char data_src[] = "test";
+ const char *data_dst;
+ BArrayState *state = BLI_array_store_state_add(bs, data_src, sizeof(data_src), NULL);
+ size_t data_dst_len;
+ data_dst = (char *)BLI_array_store_state_data_get_alloc(state, &data_dst_len);
+ EXPECT_STREQ(data_src, data_dst);
+ EXPECT_EQ(data_dst_len, sizeof(data_src));
+ BLI_array_store_destroy(bs);
+ MEM_freeN((void *)data_dst);
+}
+
+TEST(array_store, DoubleNop)
+{
+ BArrayStore *bs = BLI_array_store_create(1, 32);
+ const char data_src[] = "test";
+ const char *data_dst;
+
+ BArrayState *state_a = BLI_array_store_state_add(bs, data_src, sizeof(data_src), NULL);
+ BArrayState *state_b = BLI_array_store_state_add(bs, data_src, sizeof(data_src), state_a);
+
+ EXPECT_EQ(BLI_array_store_calc_size_compacted_get(bs), sizeof(data_src));
+ EXPECT_EQ(BLI_array_store_calc_size_expanded_get(bs), sizeof(data_src) * 2);
+
+ size_t data_dst_len;
+
+ data_dst = (char *)BLI_array_store_state_data_get_alloc(state_a, &data_dst_len);
+ EXPECT_STREQ(data_src, data_dst);
+ MEM_freeN((void *)data_dst);
+
+ data_dst = (char *)BLI_array_store_state_data_get_alloc(state_b, &data_dst_len);
+ EXPECT_STREQ(data_src, data_dst);
+ MEM_freeN((void *)data_dst);
+
+ EXPECT_EQ(data_dst_len, sizeof(data_src));
+ BLI_array_store_destroy(bs);
+}
+
+TEST(array_store, DoubleDiff)
+{
+ BArrayStore *bs = BLI_array_store_create(1, 32);
+ const char data_src_a[] = "test";
+ const char data_src_b[] = "####";
+ const char *data_dst;
+
+ BArrayState *state_a = BLI_array_store_state_add(bs, data_src_a, sizeof(data_src_a), NULL);
+ BArrayState *state_b = BLI_array_store_state_add(bs, data_src_b, sizeof(data_src_b), state_a);
+ size_t data_dst_len;
+
+ EXPECT_EQ(BLI_array_store_calc_size_compacted_get(bs), sizeof(data_src_a) * 2);
+ EXPECT_EQ(BLI_array_store_calc_size_expanded_get(bs), sizeof(data_src_a) * 2);
+
+ data_dst = (char *)BLI_array_store_state_data_get_alloc(state_a, &data_dst_len);
+ EXPECT_STREQ(data_src_a, data_dst);
+ MEM_freeN((void *)data_dst);
+
+ data_dst = (char *)BLI_array_store_state_data_get_alloc(state_b, &data_dst_len);
+ EXPECT_STREQ(data_src_b, data_dst);
+ MEM_freeN((void *)data_dst);
+
+ BLI_array_store_destroy(bs);
+}
+
+TEST(array_store, TextMixed)
+{
+ TESTBUFFER_STRINGS(1, 4, "", );
+ TESTBUFFER_STRINGS(1, 4, "test", );
+ TESTBUFFER_STRINGS(1, 4, "", "test", );
+ TESTBUFFER_STRINGS(1, 4, "test", "", );
+ TESTBUFFER_STRINGS(1, 4, "test", "", "test", );
+ TESTBUFFER_STRINGS(1, 4, "", "test", "", );
+}
+
+TEST(array_store, TextDupeIncreaseDecrease)
+{
+ ListBase lb;
+
+#define D "#1#2#3#4"
+ TESTBUFFER_STRINGS_CREATE(&lb, D, D D, D D D, D D D D, );
+
+ BArrayStore *bs = BLI_array_store_create(1, 8);
+
+ /* forward */
+ testbuffer_list_store_populate(bs, &lb);
+ EXPECT_TRUE(testbuffer_list_validate(&lb));
+ EXPECT_TRUE(BLI_array_store_is_valid(bs));
+ EXPECT_EQ(BLI_array_store_calc_size_compacted_get(bs), strlen(D));
+
+ testbuffer_list_store_clear(bs, &lb);
+ BLI_listbase_reverse(&lb);
+
+ /* backwards */
+ testbuffer_list_store_populate(bs, &lb);
+ EXPECT_TRUE(testbuffer_list_validate(&lb));
+ EXPECT_TRUE(BLI_array_store_is_valid(bs));
+ /* larger since first block doesn't de-duplicate */
+ EXPECT_EQ(BLI_array_store_calc_size_compacted_get(bs), strlen(D) * 4);
+
+#undef D
+ testbuffer_list_free(&lb);
+
+ BLI_array_store_destroy(bs);
+}
+
+/* -------------------------------------------------------------------- */
+/* Plain Text Tests */
+
+/**
+ * Test that uses text input with different params for the array-store
+ * to ensure no corner cases fail.
+ */
+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)
+{
+
+ ListBase lb;
+ BLI_listbase_clear(&lb);
+
+ for (int i = 0, i_prev = 0; i < words_len; i++) {
+ if (ELEM(words[i], word_delim, '\0')) {
+ if (i != i_prev) {
+ testbuffer_list_state_from_data__stride_expand(&lb, &words[i_prev], i - i_prev, stride);
+ }
+ i_prev = i;
+ }
+ }
+
+ if (random_seed) {
+ testbuffer_list_data_randomize(&lb, random_seed);
+ }
+
+ testbuffer_run_tests_simple(&lb, stride, chunk_count);
+
+ testbuffer_list_free(&lb);
+}
+
+/* split by '.' (multiple words) */
+#define WORDS words10k, sizeof(words10k)
+TEST(array_store, TextSentences_Chunk1)
+{
+ plain_text_helper(WORDS, '.', 1, 1, 0);
+}
+TEST(array_store, TextSentences_Chunk2)
+{
+ plain_text_helper(WORDS, '.', 1, 2, 0);
+}
+TEST(array_store, TextSentences_Chunk8)
+{
+ plain_text_helper(WORDS, '.', 1, 8, 0);
+}
+TEST(array_store, TextSentences_Chunk32)
+{
+ plain_text_helper(WORDS, '.', 1, 32, 0);
+}
+TEST(array_store, TextSentences_Chunk128)
+{
+ plain_text_helper(WORDS, '.', 1, 128, 0);
+}
+TEST(array_store, TextSentences_Chunk1024)
+{
+ plain_text_helper(WORDS, '.', 1, 1024, 0);
+}
+/* odd numbers */
+TEST(array_store, TextSentences_Chunk3)
+{
+ plain_text_helper(WORDS, '.', 1, 3, 0);
+}
+TEST(array_store, TextSentences_Chunk13)
+{
+ plain_text_helper(WORDS, '.', 1, 13, 0);
+}
+TEST(array_store, TextSentences_Chunk131)
+{
+ plain_text_helper(WORDS, '.', 1, 131, 0);
+}
+
+/* split by ' ', individual words */
+TEST(array_store, TextWords_Chunk1)
+{
+ plain_text_helper(WORDS, ' ', 1, 1, 0);
+}
+TEST(array_store, TextWords_Chunk2)
+{
+ plain_text_helper(WORDS, ' ', 1, 2, 0);
+}
+TEST(array_store, TextWords_Chunk8)
+{
+ plain_text_helper(WORDS, ' ', 1, 8, 0);
+}
+TEST(array_store, TextWords_Chunk32)
+{
+ plain_text_helper(WORDS, ' ', 1, 32, 0);
+}
+TEST(array_store, TextWords_Chunk128)
+{
+ plain_text_helper(WORDS, ' ', 1, 128, 0);
+}
+TEST(array_store, TextWords_Chunk1024)
+{
+ plain_text_helper(WORDS, ' ', 1, 1024, 0);
+}
+/* odd numbers */
+TEST(array_store, TextWords_Chunk3)
+{
+ plain_text_helper(WORDS, ' ', 1, 3, 0);
+}
+TEST(array_store, TextWords_Chunk13)
+{
+ plain_text_helper(WORDS, ' ', 1, 13, 0);
+}
+TEST(array_store, TextWords_Chunk131)
+{
+ plain_text_helper(WORDS, ' ', 1, 131, 0);
+}
+
+/* various tests with different strides & randomizing */
+TEST(array_store, TextSentencesRandom_Stride3_Chunk3)
+{
+ plain_text_helper(WORDS, 'q', 3, 3, 7337);
+}
+TEST(array_store, TextSentencesRandom_Stride8_Chunk8)
+{
+ plain_text_helper(WORDS, 'n', 8, 8, 5667);
+}
+TEST(array_store, TextSentencesRandom_Stride32_Chunk1)
+{
+ plain_text_helper(WORDS, 'a', 1, 32, 1212);
+}
+TEST(array_store, TextSentencesRandom_Stride12_Chunk512)
+{
+ plain_text_helper(WORDS, 'g', 12, 512, 9999);
+}
+TEST(array_store, TextSentencesRandom_Stride128_Chunk6)
+{
+ plain_text_helper(WORDS, 'b', 20, 6, 1000);
+}
+
+#undef WORDS
+
+/* -------------------------------------------------------------------- */
+/* Random Data Tests */
+
+static unsigned int rand_range_i(RNG *rng,
+ unsigned int min_i,
+ unsigned int max_i,
+ unsigned int step)
+{
+ if (min_i == max_i) {
+ return min_i;
+ }
+ BLI_assert(min_i <= max_i);
+ BLI_assert(((min_i % step) == 0) && ((max_i % step) == 0));
+ unsigned int range = (max_i - min_i);
+ unsigned int value = BLI_rng_get_uint(rng) % range;
+ value = (value / step) * step;
+ return min_i + value;
+}
+
+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 unsigned int mutate,
+ RNG *rng)
+{
+ size_t data_len = rand_range_i(rng, data_min_len, data_max_len + stride, stride);
+ char *data = (char *)MEM_mallocN(data_len, __func__);
+
+ if (lb->last == NULL) {
+ BLI_rng_get_char_n(rng, data, data_len);
+ }
+ else {
+ TestBuffer *tb_last = (TestBuffer *)lb->last;
+ if (tb_last->data_len >= data_len) {
+ memcpy(data, tb_last->data, data_len);
+ }
+ else {
+ memcpy(data, tb_last->data, tb_last->data_len);
+ BLI_rng_get_char_n(rng, &data[tb_last->data_len], data_len - tb_last->data_len);
+ }
+
+ /* perform multiple small mutations to the array. */
+ for (int i = 0; i < mutate; i++) {
+ enum {
+ MUTATE_NOP = 0,
+ MUTATE_ADD,
+ MUTATE_REMOVE,
+ MUTATE_ROTATE,
+ MUTATE_RANDOMIZE,
+ MUTATE_TOTAL,
+ };
+
+ switch ((BLI_rng_get_uint(rng) % MUTATE_TOTAL)) {
+ case MUTATE_NOP: {
+ break;
+ }
+ case MUTATE_ADD: {
+ const unsigned int offset = rand_range_i(rng, 0, data_len, stride);
+ if (data_len < data_max_len) {
+ data_len += stride;
+ data = (char *)MEM_reallocN((void *)data, data_len);
+ memmove(&data[offset + stride], &data[offset], data_len - (offset + stride));
+ BLI_rng_get_char_n(rng, &data[offset], stride);
+ }
+ break;
+ }
+ case MUTATE_REMOVE: {
+ const unsigned int offset = rand_range_i(rng, 0, data_len, stride);
+ if (data_len > data_min_len) {
+ memmove(&data[offset], &data[offset + stride], data_len - (offset + stride));
+ data_len -= stride;
+ }
+ break;
+ }
+ case MUTATE_ROTATE: {
+ int items = data_len / stride;
+ if (items > 1) {
+ _bli_array_wrap(data, items, stride, (BLI_rng_get_uint(rng) % 2) ? -1 : 1);
+ }
+ break;
+ }
+ case MUTATE_RANDOMIZE: {
+ if (data_len > 0) {
+ const unsigned int offset = rand_range_i(rng, 0, data_len - stride, stride);
+ BLI_rng_get_char_n(rng, &data[offset], stride);
+ }
+ break;
+ }
+ default:
+ BLI_assert(0);
+ }
+ }
+ }
+
+ testbuffer_list_add(lb, (const void *)data, data_len);
+}
+
+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)
+{
+
+ ListBase lb;
+ BLI_listbase_clear(&lb);
+
+ const size_t data_min_len = items_size_min * stride;
+ const size_t data_max_len = items_size_max * stride;
+
+ {
+ RNG *rng = BLI_rng_new(random_seed);
+ for (int i = 0; i < items_total; i++) {
+ testbuffer_list_state_random_data(&lb, stride, data_min_len, data_max_len, mutate, rng);
+ }
+ BLI_rng_free(rng);
+ }
+
+ testbuffer_run_tests_simple(&lb, stride, chunk_count);
+
+ testbuffer_list_free(&lb);
+}
+
+TEST(array_store, TestData_Stride1_Chunk32_Mutate2)
+{
+ random_data_mutate_helper(0, 100, 400, 1, 32, 9779, 2);
+}
+TEST(array_store, TestData_Stride8_Chunk512_Mutate2)
+{
+ random_data_mutate_helper(0, 128, 400, 8, 512, 1001, 2);
+}
+TEST(array_store, TestData_Stride12_Chunk48_Mutate2)
+{
+ random_data_mutate_helper(200, 256, 400, 12, 48, 1331, 2);
+}
+TEST(array_store, TestData_Stride32_Chunk64_Mutate1)
+{
+ random_data_mutate_helper(0, 256, 200, 32, 64, 3112, 1);
+}
+TEST(array_store, TestData_Stride32_Chunk64_Mutate8)
+{
+ random_data_mutate_helper(0, 256, 200, 32, 64, 7117, 8);
+}
+
+/* -------------------------------------------------------------------- */
+/* Randomized Chunks Test */
+
+static void random_chunk_generate(ListBase *lb,
+ const int chunks_per_buffer,
+ const int stride,
+ const int chunk_count,
+ const int random_seed)
+{
+ RNG *rng = BLI_rng_new(random_seed);
+ const size_t chunk_size_bytes = stride * chunk_count;
+ for (int i = 0; i < chunks_per_buffer; i++) {
+ char *data_chunk = (char *)MEM_mallocN(chunk_size_bytes, __func__);
+ BLI_rng_get_char_n(rng, data_chunk, chunk_size_bytes);
+ testchunk_list_add(lb, data_chunk, chunk_size_bytes);
+ }
+ BLI_rng_free(rng);
+}
+
+/**
+ * Add random chunks, then re-order them to ensure chunk de-duplication is working.
+ */
+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)
+{
+ /* generate random chunks */
+
+ ListBase random_chunks;
+ BLI_listbase_clear(&random_chunks);
+ random_chunk_generate(&random_chunks, chunks_per_buffer, stride, chunk_count, random_seed);
+ TestChunk **chunks_array = (TestChunk **)MEM_mallocN(chunks_per_buffer * sizeof(TestChunk *),
+ __func__);
+ {
+ TestChunk *tc = (TestChunk *)random_chunks.first;
+ for (int i = 0; i < chunks_per_buffer; i++, tc = tc->next) {
+ chunks_array[i] = tc;
+ }
+ }
+
+ /* add and re-order each time */
+ ListBase lb;
+ BLI_listbase_clear(&lb);
+
+ {
+ RNG *rng = BLI_rng_new(random_seed);
+ for (int i = 0; i < items_total; i++) {
+ BLI_rng_shuffle_array(rng, chunks_array, sizeof(TestChunk *), chunks_per_buffer);
+ size_t data_len;
+ char *data = testchunk_as_data_array(chunks_array, chunks_per_buffer, &data_len);
+ BLI_assert(data_len == chunks_per_buffer * chunk_count * stride);
+ testbuffer_list_add(&lb, (const void *)data, data_len);
+ }
+ BLI_rng_free(rng);
+ }
+
+ testchunk_list_free(&random_chunks);
+ MEM_freeN(chunks_array);
+
+ BArrayStore *bs = BLI_array_store_create(stride, chunk_count);
+ testbuffer_run_tests_single(bs, &lb);
+
+ size_t expected_size = chunks_per_buffer * chunk_count * stride;
+ EXPECT_EQ(BLI_array_store_calc_size_compacted_get(bs), expected_size);
+
+ BLI_array_store_destroy(bs);
+
+ testbuffer_list_free(&lb);
+}
+
+TEST(array_store, TestChunk_Rand8_Stride1_Chunk64)
+{
+ random_chunk_mutate_helper(8, 100, 1, 64, 9779);
+}
+TEST(array_store, TestChunk_Rand32_Stride1_Chunk64)
+{
+ random_chunk_mutate_helper(32, 100, 1, 64, 1331);
+}
+TEST(array_store, TestChunk_Rand64_Stride8_Chunk32)
+{
+ random_chunk_mutate_helper(64, 100, 8, 32, 2772);
+}
+TEST(array_store, TestChunk_Rand31_Stride11_Chunk21)
+{
+ random_chunk_mutate_helper(31, 100, 11, 21, 7117);
+}
+
+#if 0
+/* -------------------------------------------------------------------- */
+
+/* Test From Files (disabled, keep for local tests.) */
+
+void *file_read_binary_as_mem(const char *filepath, size_t pad_bytes, size_t *r_size)
+{
+ FILE *fp = fopen(filepath, "rb");
+ void *mem = NULL;
+
+ if (fp) {
+ long int filelen_read;
+ fseek(fp, 0L, SEEK_END);
+ const long int filelen = ftell(fp);
+ if (filelen == -1) {
+ goto finally;
+ }
+ fseek(fp, 0L, SEEK_SET);
+
+ mem = MEM_mallocN(filelen + pad_bytes, __func__);
+ if (mem == NULL) {
+ goto finally;
+ }
+
+ filelen_read = fread(mem, 1, filelen, fp);
+ if ((filelen_read != filelen) || ferror(fp)) {
+ MEM_freeN(mem);
+ mem = NULL;
+ goto finally;
+ }
+
+ *r_size = filelen_read;
+
+ finally:
+ fclose(fp);
+ }
+
+ return mem;
+}
+
+TEST(array_store, PlainTextFiles)
+{
+ ListBase lb;
+ BLI_listbase_clear(&lb);
+ BArrayStore *bs = BLI_array_store_create(1, 128);
+
+ for (int i = 0; i < 629; i++) {
+ char str[512];
+ BLI_snprintf(str, sizeof(str), "/src/py_array_cow/test_data/xz_data/%04d.c.xz", i);
+ // BLI_snprintf(str, sizeof(str), "/src/py_array_cow/test_data/c_code/%04d.c", i);
+ // printf("%s\n", str);
+ size_t data_len;
+ void *data;
+ data = file_read_binary_as_mem(str, 0, &data_len);
+
+ testbuffer_list_add(&lb, (const void *)data, data_len);
+ }
+
+ /* forwards */
+ testbuffer_list_store_populate(bs, &lb);
+ EXPECT_TRUE(testbuffer_list_validate(&lb));
+ EXPECT_TRUE(BLI_array_store_is_valid(bs));
+# ifdef DEBUG_PRINT
+ print_mem_saved("source code forward", bs);
+# endif
+
+ testbuffer_list_store_clear(bs, &lb);
+ BLI_listbase_reverse(&lb);
+
+ /* backwards */
+ testbuffer_list_store_populate(bs, &lb);
+ EXPECT_TRUE(testbuffer_list_validate(&lb));
+ EXPECT_TRUE(BLI_array_store_is_valid(bs));
+# ifdef DEBUG_PRINT
+ print_mem_saved("source code backwards", bs);
+# endif
+
+ testbuffer_list_free(&lb);
+ BLI_array_store_destroy(bs);
+}
+#endif
diff --git a/source/blender/blenlib/tests/BLI_array_utils_test.cc b/source/blender/blenlib/tests/BLI_array_utils_test.cc
new file mode 100644
index 00000000000..33b4cd35d52
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_array_utils_test.cc
@@ -0,0 +1,191 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_array_utils.h"
+#include "BLI_utildefines.h"
+#include "BLI_utildefines_stack.h"
+
+/* -------------------------------------------------------------------- */
+/* tests */
+
+/* BLI_array_reverse */
+TEST(array_utils, ReverseStringEmpty)
+{
+ char data[] = "";
+ BLI_array_reverse(data, ARRAY_SIZE(data) - 1);
+ EXPECT_STREQ("", data);
+}
+
+TEST(array_utils, ReverseStringSingle)
+{
+ char data[] = "0";
+ BLI_array_reverse(data, ARRAY_SIZE(data) - 1);
+ EXPECT_STREQ("0", data);
+}
+
+TEST(array_utils, ReverseString4)
+{
+ char data[] = "0123";
+ BLI_array_reverse(data, ARRAY_SIZE(data) - 1);
+ EXPECT_STREQ("3210", data);
+}
+
+TEST(array_utils, ReverseInt4)
+{
+ const int data_cmp[] = {3, 2, 1, 0};
+ int data[] = {0, 1, 2, 3};
+ BLI_array_reverse(data, ARRAY_SIZE(data));
+ EXPECT_EQ_ARRAY(data_cmp, data, ARRAY_SIZE(data));
+}
+
+/* BLI_array_findindex */
+TEST(array_utils, FindIndexStringEmpty)
+{
+ char data[] = "", find = '0';
+ EXPECT_EQ(BLI_array_findindex(data, ARRAY_SIZE(data) - 1, &find), -1);
+ EXPECT_EQ(BLI_array_rfindindex(data, ARRAY_SIZE(data) - 1, &find), -1);
+}
+
+TEST(array_utils, FindIndexStringSingle)
+{
+ char data[] = "0", find = '0';
+ EXPECT_EQ(BLI_array_findindex(data, ARRAY_SIZE(data) - 1, &find), 0);
+ EXPECT_EQ(BLI_array_rfindindex(data, ARRAY_SIZE(data) - 1, &find), 0);
+}
+
+TEST(array_utils, FindIndexStringSingleMissing)
+{
+ char data[] = "1", find = '0';
+ EXPECT_EQ(BLI_array_findindex(data, ARRAY_SIZE(data) - 1, &find), -1);
+ EXPECT_EQ(BLI_array_rfindindex(data, ARRAY_SIZE(data) - 1, &find), -1);
+}
+
+TEST(array_utils, FindIndexString4)
+{
+ char data[] = "0123", find = '3';
+ EXPECT_EQ(BLI_array_findindex(data, ARRAY_SIZE(data) - 1, &find), 3);
+ EXPECT_EQ(BLI_array_rfindindex(data, ARRAY_SIZE(data) - 1, &find), 3);
+}
+
+TEST(array_utils, FindIndexInt4)
+{
+ int data[] = {0, 1, 2, 3}, find = 3;
+ EXPECT_EQ(BLI_array_findindex(data, ARRAY_SIZE(data), &find), 3);
+ EXPECT_EQ(BLI_array_rfindindex(data, ARRAY_SIZE(data), &find), 3);
+}
+
+TEST(array_utils, FindIndexInt4_DupeEnd)
+{
+ int data[] = {0, 1, 2, 0}, find = 0;
+ EXPECT_EQ(BLI_array_findindex(data, ARRAY_SIZE(data), &find), 0);
+ EXPECT_EQ(BLI_array_rfindindex(data, ARRAY_SIZE(data), &find), 3);
+}
+
+TEST(array_utils, FindIndexInt4_DupeMid)
+{
+ int data[] = {1, 0, 0, 3}, find = 0;
+ EXPECT_EQ(BLI_array_findindex(data, ARRAY_SIZE(data), &find), 1);
+ EXPECT_EQ(BLI_array_rfindindex(data, ARRAY_SIZE(data), &find), 2);
+}
+
+TEST(array_utils, FindIndexPointer)
+{
+ const char *data[4] = {NULL};
+ STACK_DECLARE(data);
+
+ STACK_INIT(data, ARRAY_SIZE(data));
+
+ const char *a = "a", *b = "b", *c = "c", *d = "d";
+
+#define STACK_PUSH_AND_CHECK_FORWARD(v, i) \
+ { \
+ STACK_PUSH(data, v); \
+ EXPECT_EQ(BLI_array_findindex(data, STACK_SIZE(data), &(v)), i); \
+ } \
+ ((void)0)
+
+#define STACK_PUSH_AND_CHECK_BACKWARD(v, i) \
+ { \
+ STACK_PUSH(data, v); \
+ EXPECT_EQ(BLI_array_rfindindex(data, STACK_SIZE(data), &(v)), i); \
+ } \
+ ((void)0)
+
+#define STACK_PUSH_AND_CHECK_BOTH(v, i) \
+ { \
+ STACK_PUSH(data, v); \
+ EXPECT_EQ(BLI_array_findindex(data, STACK_SIZE(data), &(v)), i); \
+ EXPECT_EQ(BLI_array_rfindindex(data, STACK_SIZE(data), &(v)), i); \
+ } \
+ ((void)0)
+
+ STACK_PUSH_AND_CHECK_BOTH(a, 0);
+ STACK_PUSH_AND_CHECK_BOTH(b, 1);
+ STACK_PUSH_AND_CHECK_BOTH(c, 2);
+ STACK_PUSH_AND_CHECK_BOTH(d, 3);
+
+ STACK_POP(data);
+ STACK_PUSH_AND_CHECK_BACKWARD(a, 3);
+
+ STACK_POP(data);
+ STACK_PUSH_AND_CHECK_FORWARD(a, 0);
+
+ STACK_POP(data);
+ STACK_POP(data);
+
+ STACK_PUSH_AND_CHECK_BACKWARD(b, 2);
+ STACK_PUSH_AND_CHECK_BACKWARD(a, 3);
+
+#undef STACK_PUSH_AND_CHECK_FORWARD
+#undef STACK_PUSH_AND_CHECK_BACKWARD
+#undef STACK_PUSH_AND_CHECK_BOTH
+}
+
+/* BLI_array_binary_and */
+#define BINARY_AND_TEST(data_cmp, data_a, data_b, data_combine, length) \
+ { \
+ BLI_array_binary_and(data_combine, data_a, data_b, length); \
+ EXPECT_EQ_ARRAY(data_cmp, data_combine, length); \
+ } \
+ ((void)0)
+
+TEST(array_utils, BinaryAndInt4Zero)
+{
+ const int data_cmp[] = {0, 0, 0, 0};
+ int data_a[] = {0, 1, 0, 1}, data_b[] = {1, 0, 1, 0};
+ int data_combine[ARRAY_SIZE(data_cmp)];
+ BINARY_AND_TEST(data_cmp, data_a, data_b, data_combine, ARRAY_SIZE(data_cmp));
+}
+
+TEST(array_utils, BinaryAndInt4Mix)
+{
+ const int data_cmp[] = {1, 0, 1, 0};
+ int data_a[] = {1, 1, 1, 1}, data_b[] = {1, 0, 1, 0};
+ int data_combine[ARRAY_SIZE(data_cmp)];
+ BINARY_AND_TEST(data_cmp, data_a, data_b, data_combine, ARRAY_SIZE(data_cmp));
+}
+#undef BINARY_AND_TEST
+
+/* BLI_array_binary_or */
+#define BINARY_OR_TEST(data_cmp, data_a, data_b, data_combine, length) \
+ { \
+ BLI_array_binary_or(data_combine, data_a, data_b, length); \
+ EXPECT_EQ_ARRAY(data_combine, data_cmp, length); \
+ } \
+ ((void)0)
+
+TEST(array_utils, BinaryOrInt4Alternate)
+{
+ int data_a[] = {0, 1, 0, 1}, data_b[] = {1, 0, 1, 0}, data_cmp[] = {1, 1, 1, 1};
+ int data_combine[ARRAY_SIZE(data_cmp)];
+ BINARY_OR_TEST(data_cmp, data_a, data_b, data_combine, ARRAY_SIZE(data_cmp));
+}
+
+TEST(array_utils, BinaryOrInt4Mix)
+{
+ int data_a[] = {1, 1, 0, 0}, data_b[] = {0, 0, 1, 0}, data_cmp[] = {1, 1, 1, 0};
+ int data_combine[ARRAY_SIZE(data_cmp)];
+ BINARY_OR_TEST(data_cmp, data_a, data_b, data_combine, ARRAY_SIZE(data_cmp));
+}
+#undef BINARY_OR_TEST
diff --git a/source/blender/blenlib/tests/BLI_delaunay_2d_test.cc b/source/blender/blenlib/tests/BLI_delaunay_2d_test.cc
new file mode 100644
index 00000000000..fd2de9864af
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_delaunay_2d_test.cc
@@ -0,0 +1,1762 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_math.h"
+#include "BLI_rand.h"
+#include "PIL_time.h"
+
+#include "BLI_delaunay_2d.h"
+
+#include <fstream>
+#include <iostream>
+#include <sstream>
+
+#define DO_REGULAR_TESTS 1
+#define DO_RANDOM_TESTS 0
+#define DO_FILE_TESTS 0
+
+static void fill_input_verts(CDT_input *r_input, float (*vcos)[2], int nverts)
+{
+ r_input->verts_len = nverts;
+ r_input->edges_len = 0;
+ r_input->faces_len = 0;
+ r_input->vert_coords = vcos;
+ r_input->edges = NULL;
+ r_input->faces = NULL;
+ r_input->faces_start_table = NULL;
+ r_input->faces_len_table = NULL;
+ r_input->epsilon = 1e-5f;
+ r_input->skip_input_modify = false;
+}
+
+static void add_input_edges(CDT_input *r_input, int (*edges)[2], int nedges)
+{
+ r_input->edges_len = nedges;
+ r_input->edges = edges;
+}
+
+static void add_input_faces(
+ CDT_input *r_input, int *faces, int *faces_start_table, int *faces_len_table, int nfaces)
+{
+ r_input->faces_len = nfaces;
+ r_input->faces = faces;
+ r_input->faces_start_table = faces_start_table;
+ r_input->faces_len_table = faces_len_table;
+}
+
+/* The spec should have the form:
+ * #verts #edges #faces
+ * <float> <float> [#verts lines)
+ * <int> <int> [#edges lines]
+ * <int> <int> ... <int> [#faces lines]
+ */
+static void fill_input_from_string(CDT_input *r_input, const char *spec)
+{
+ std::string line;
+ std::vector<std::vector<int>> faces;
+ int i, j;
+ int nverts, nedges, nfaces;
+ float(*p)[2];
+ int(*e)[2];
+ int *farr;
+ int *flen;
+ int *fstart;
+
+ std::istringstream ss(spec);
+ getline(ss, line);
+ std::istringstream hdrss(line);
+ hdrss >> nverts >> nedges >> nfaces;
+ if (nverts == 0) {
+ return;
+ }
+ p = (float(*)[2])MEM_malloc_arrayN(nverts, 2 * sizeof(float), __func__);
+ if (nedges > 0) {
+ e = (int(*)[2])MEM_malloc_arrayN(nedges, 2 * sizeof(int), __func__);
+ }
+ if (nfaces > 0) {
+ flen = (int *)MEM_malloc_arrayN(nfaces, sizeof(int), __func__);
+ fstart = (int *)MEM_malloc_arrayN(nfaces, sizeof(int), __func__);
+ }
+ i = 0;
+ while (i < nverts && getline(ss, line)) {
+ std::istringstream iss(line);
+ iss >> p[i][0] >> p[i][1];
+ i++;
+ }
+ i = 0;
+ while (i < nedges && getline(ss, line)) {
+ std::istringstream ess(line);
+ ess >> e[i][0] >> e[i][1];
+ i++;
+ }
+ i = 0;
+ while (i < nfaces && getline(ss, line)) {
+ std::istringstream fss(line);
+ int v;
+ faces.push_back(std::vector<int>());
+ while (fss >> v) {
+ faces[i].push_back(v);
+ }
+ i++;
+ }
+ fill_input_verts(r_input, p, nverts);
+ if (nedges > 0) {
+ add_input_edges(r_input, e, nedges);
+ }
+ if (nfaces > 0) {
+ for (i = 0; i < nfaces; i++) {
+ flen[i] = (int)faces[i].size();
+ if (i == 0) {
+ fstart[i] = 0;
+ }
+ else {
+ fstart[i] = fstart[i - 1] + flen[i - 1];
+ }
+ }
+ farr = (int *)MEM_malloc_arrayN(fstart[nfaces - 1] + flen[nfaces - 1], sizeof(int), __func__);
+ for (i = 0; i < nfaces; i++) {
+ for (j = 0; j < (int)faces[i].size(); j++) {
+ farr[fstart[i] + j] = faces[i][j];
+ }
+ }
+ add_input_faces(r_input, farr, fstart, flen, nfaces);
+ }
+}
+
+#if DO_FILE_TESTS
+static void fill_input_from_file(CDT_input *in, const char *filename)
+{
+ std::FILE *fp = std::fopen(filename, "rb");
+ if (fp) {
+ std::string contents;
+ std::fseek(fp, 0, SEEK_END);
+ contents.resize(std::ftell(fp));
+ std::rewind(fp);
+ std::fread(&contents[0], 1, contents.size(), fp);
+ std::fclose(fp);
+ fill_input_from_string(in, contents.c_str());
+ }
+ else {
+ printf("couldn't open file %s\n", filename);
+ }
+}
+#endif
+
+static void free_spec_arrays(CDT_input *in)
+{
+ if (in->vert_coords) {
+ MEM_freeN(in->vert_coords);
+ }
+ if (in->edges) {
+ MEM_freeN(in->edges);
+ }
+ if (in->faces_len_table) {
+ MEM_freeN(in->faces_len_table);
+ MEM_freeN(in->faces_start_table);
+ MEM_freeN(in->faces);
+ }
+}
+
+/* which output vert index goes with given input vertex? -1 if not found */
+static int get_output_vert_index(const CDT_result *r, int in_index)
+{
+ int i, j;
+
+ for (i = 0; i < r->verts_len; i++) {
+ for (j = 0; j < r->verts_orig_len_table[i]; j++) {
+ if (r->verts_orig[r->verts_orig_start_table[i] + j] == in_index) {
+ return i;
+ }
+ }
+ }
+ return -1;
+}
+
+/* which output edge index is for given output vert indices? */
+static int get_edge(const CDT_result *r, int out_index_1, int out_index_2)
+{
+ int i;
+
+ for (i = 0; i < r->edges_len; i++) {
+ if ((r->edges[i][0] == out_index_1 && r->edges[i][1] == out_index_2) ||
+ (r->edges[i][0] == out_index_2 && r->edges[i][1] == out_index_1))
+ return i;
+ }
+ return -1;
+}
+
+/* return true if given output edge has given input edge id in its originals list */
+static bool out_edge_has_input_id(const CDT_result *r, int out_edge_index, int in_edge_index)
+{
+ if (r->edges_orig == NULL)
+ return false;
+ if (out_edge_index < 0 || out_edge_index >= r->edges_len)
+ return false;
+ for (int i = 0; i < r->edges_orig_len_table[out_edge_index]; i++) {
+ if (r->edges_orig[r->edges_orig_start_table[out_edge_index] + i] == in_edge_index)
+ return true;
+ }
+ return false;
+}
+
+/* which face is for given output vertex ngon? */
+static int get_face(const CDT_result *r, int *out_indices, int nverts)
+{
+ int f, cycle_start, k, fstart;
+ bool ok;
+
+ if (r->faces_len == 0)
+ return -1;
+ for (f = 0; f < r->faces_len; f++) {
+ if (r->faces_len_table[f] != nverts)
+ continue;
+ fstart = r->faces_start_table[f];
+ for (cycle_start = 0; cycle_start < nverts; cycle_start++) {
+ ok = true;
+ for (k = 0; ok && k < nverts; k++) {
+ if (r->faces[fstart + ((cycle_start + k) % nverts)] != out_indices[k]) {
+ ok = false;
+ }
+ }
+ if (ok) {
+ return f;
+ }
+ }
+ }
+ return -1;
+}
+
+static int get_face_tri(const CDT_result *r, int out_index_1, int out_index_2, int out_index_3)
+{
+ int tri[3];
+
+ tri[0] = out_index_1;
+ tri[1] = out_index_2;
+ tri[2] = out_index_3;
+ return get_face(r, tri, 3);
+}
+
+/* return true if given otuput face has given input face id in its originals list */
+static bool out_face_has_input_id(const CDT_result *r, int out_face_index, int in_face_index)
+{
+ if (r->faces_orig == NULL)
+ return false;
+ if (out_face_index < 0 || out_face_index >= r->faces_len)
+ return false;
+ for (int i = 0; i < r->faces_orig_len_table[out_face_index]; i++) {
+ if (r->faces_orig[r->faces_orig_start_table[out_face_index] + i] == in_face_index)
+ return true;
+ }
+ return false;
+}
+
+#if DO_FILE_TESTS
+/* for debugging */
+static void dump_result(CDT_result *r)
+{
+ int i, j;
+
+ fprintf(stderr, "\nRESULT\n");
+ fprintf(stderr,
+ "verts_len=%d edges_len=%d faces_len=%d\n",
+ r->verts_len,
+ r->edges_len,
+ r->faces_len);
+ fprintf(stderr, "\nvert coords:\n");
+ for (i = 0; i < r->verts_len; i++)
+ fprintf(stderr, "%d: (%f,%f)\n", i, r->vert_coords[i][0], r->vert_coords[i][1]);
+ fprintf(stderr, "vert orig:\n");
+ for (i = 0; i < r->verts_len; i++) {
+ fprintf(stderr, "%d:", i);
+ for (j = 0; j < r->verts_orig_len_table[i]; j++)
+ fprintf(stderr, " %d", r->verts_orig[r->verts_orig_start_table[i] + j]);
+ fprintf(stderr, "\n");
+ }
+ fprintf(stderr, "\nedges:\n");
+ for (i = 0; i < r->edges_len; i++)
+ fprintf(stderr, "%d: (%d,%d)\n", i, r->edges[i][0], r->edges[i][1]);
+ if (r->edges_orig) {
+ fprintf(stderr, "edge orig:\n");
+ for (i = 0; i < r->edges_len; i++) {
+ fprintf(stderr, "%d:", i);
+ for (j = 0; j < r->edges_orig_len_table[i]; j++)
+ fprintf(stderr, " %d", r->edges_orig[r->edges_orig_start_table[i] + j]);
+ fprintf(stderr, "\n");
+ }
+ }
+ fprintf(stderr, "\nfaces:\n");
+ for (i = 0; i < r->faces_len; i++) {
+ fprintf(stderr, "%d: ", i);
+ for (j = 0; j < r->faces_len_table[i]; j++)
+ fprintf(stderr, " %d", r->faces[r->faces_start_table[i] + j]);
+ fprintf(stderr, "\n");
+ }
+ if (r->faces_orig) {
+ fprintf(stderr, "face orig:\n");
+ for (i = 0; i < r->faces_len; i++) {
+ fprintf(stderr, "%d:", i);
+ for (j = 0; j < r->faces_orig_len_table[i]; j++)
+ fprintf(stderr, " %d", r->faces_orig[r->faces_orig_start_table[i] + j]);
+ fprintf(stderr, "\n");
+ }
+ }
+}
+#endif
+
+#if DO_REGULAR_TESTS
+TEST(delaunay, Empty)
+{
+ CDT_input in;
+ CDT_result *out;
+
+ fill_input_verts(&in, NULL, 0);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_NE((CDT_result *)NULL, out);
+ EXPECT_EQ(out->verts_len, 0);
+ EXPECT_EQ(out->edges_len, 0);
+ EXPECT_EQ(out->faces_len, 0);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, OnePt)
+{
+ CDT_input in;
+ CDT_result *out;
+ const char *spec = R"(1 0 0
+ 0.0 0.0
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 1);
+ EXPECT_EQ(out->edges_len, 0);
+ EXPECT_EQ(out->faces_len, 0);
+ if (out->verts_len >= 1) {
+ EXPECT_EQ(out->vert_coords[0][0], 0.0f);
+ EXPECT_EQ(out->vert_coords[0][1], 0.0f);
+ }
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, TwoPt)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v0_out, v1_out, e0_out;
+ const char *spec = R"(2 0 0
+ 0.0 -0.75
+ 0.0 0.75
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 2);
+ EXPECT_EQ(out->edges_len, 1);
+ EXPECT_EQ(out->faces_len, 0);
+ v0_out = get_output_vert_index(out, 0);
+ v1_out = get_output_vert_index(out, 1);
+ EXPECT_NE(v0_out, -1);
+ EXPECT_NE(v1_out, -1);
+ EXPECT_NE(v0_out, v1_out);
+ if (out->verts_len >= 2) {
+ EXPECT_NEAR(out->vert_coords[v0_out][0], 0.0, in.epsilon);
+ EXPECT_NEAR(out->vert_coords[v0_out][1], -0.75, in.epsilon);
+ EXPECT_NEAR(out->vert_coords[v1_out][0], 0.0, in.epsilon);
+ EXPECT_NEAR(out->vert_coords[v1_out][1], 0.75, in.epsilon);
+ }
+ e0_out = get_edge(out, v0_out, v1_out);
+ EXPECT_EQ(e0_out, 0);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, ThreePt)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v0_out, v1_out, v2_out;
+ int e0_out, e1_out, e2_out;
+ int f0_out;
+ const char *spec = R"(3 0 0
+ -0.1 -0.75
+ 0.1 0.75
+ 0.5 0.5
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 3);
+ EXPECT_EQ(out->edges_len, 3);
+ EXPECT_EQ(out->faces_len, 1);
+ v0_out = get_output_vert_index(out, 0);
+ v1_out = get_output_vert_index(out, 1);
+ v2_out = get_output_vert_index(out, 2);
+ EXPECT_TRUE(v0_out != -1 && v1_out != -1 && v2_out != -1);
+ EXPECT_TRUE(v0_out != v1_out && v0_out != v2_out && v1_out != v2_out);
+ e0_out = get_edge(out, v0_out, v1_out);
+ e1_out = get_edge(out, v1_out, v2_out);
+ e2_out = get_edge(out, v2_out, v0_out);
+ EXPECT_TRUE(e0_out != -1 && e1_out != -1 && e2_out != -1);
+ EXPECT_TRUE(e0_out != e1_out && e0_out != e2_out && e1_out != e2_out);
+ f0_out = get_face_tri(out, v0_out, v2_out, v1_out);
+ EXPECT_EQ(f0_out, 0);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, ThreePtsMerge)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v0_out, v1_out, v2_out;
+ const char *spec = R"(3 0 0
+ -0.05 -0.05
+ 0.05 -0.05
+ 0.0 0.03660254
+ )";
+
+ /* First with epsilon such that points are within that distance of each other */
+ fill_input_from_string(&in, spec);
+ in.epsilon = 0.21f;
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 1);
+ EXPECT_EQ(out->edges_len, 0);
+ EXPECT_EQ(out->faces_len, 0);
+ v0_out = get_output_vert_index(out, 0);
+ v1_out = get_output_vert_index(out, 1);
+ v2_out = get_output_vert_index(out, 2);
+ EXPECT_EQ(v0_out, 0);
+ EXPECT_EQ(v1_out, 0);
+ EXPECT_EQ(v2_out, 0);
+ BLI_delaunay_2d_cdt_free(out);
+ /* Now with epsilon such that points are farther away than that.
+ * Note that the points won't merge with each other if distance is
+ * less than .01, but that they may merge with points on the Delaunay
+ * triangulation lines, so make epsilon even smaller to avoid that for
+ * this test.
+ */
+ in.epsilon = 0.05f;
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 3);
+ EXPECT_EQ(out->edges_len, 3);
+ EXPECT_EQ(out->faces_len, 1);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, MixedPts)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v0_out, v1_out, v2_out, v3_out;
+ int e0_out, e1_out, e2_out;
+ const char *spec = R"(4 3 0
+ 0.0 0.0
+ -0.5 -0.5
+ -0.4 -0.25
+ -0.3 0.8
+ 0 1
+ 1 2
+ 2 3
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 4);
+ EXPECT_EQ(out->edges_len, 6);
+ v0_out = get_output_vert_index(out, 0);
+ v1_out = get_output_vert_index(out, 1);
+ v2_out = get_output_vert_index(out, 2);
+ v3_out = get_output_vert_index(out, 3);
+ EXPECT_TRUE(v0_out != -1 && v1_out != -1 && v2_out != -1 && v3_out != -1);
+ e0_out = get_edge(out, v0_out, v1_out);
+ e1_out = get_edge(out, v1_out, v2_out);
+ e2_out = get_edge(out, v2_out, v3_out);
+ EXPECT_TRUE(e0_out != -1 && e1_out != -1 && e2_out != -1);
+ EXPECT_TRUE(out_edge_has_input_id(out, e0_out, 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e1_out, 1));
+ EXPECT_TRUE(out_edge_has_input_id(out, e2_out, 2));
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, Quad0)
+{
+ CDT_input in;
+ CDT_result *out;
+ int e_diag_out;
+ const char *spec = R"(4 0 0
+ 0.0 1.0
+ 1.0 0.0
+ 2.0 0.1
+ 2.25 0.5
+ )";
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 4);
+ EXPECT_EQ(out->edges_len, 5);
+ e_diag_out = get_edge(out, 1, 3);
+ EXPECT_NE(e_diag_out, -1);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, Quad1)
+{
+ CDT_input in;
+ CDT_result *out;
+ int e_diag_out;
+ const char *spec = R"(4 0 0
+ 0.0 0.0
+ 0.9 -1.0
+ 2.0 0.0
+ 0.9 3.0
+ )";
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 4);
+ EXPECT_EQ(out->edges_len, 5);
+ e_diag_out = get_edge(out, 0, 2);
+ EXPECT_NE(e_diag_out, -1);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, Quad2)
+{
+ CDT_input in;
+ CDT_result *out;
+ int e_diag_out;
+ const char *spec = R"(4 0 0
+ 0.5 0.0
+ 0.15 0.2
+ 0.3 0.4
+ .45 0.35
+ )";
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 4);
+ EXPECT_EQ(out->edges_len, 5);
+ e_diag_out = get_edge(out, 1, 3);
+ EXPECT_NE(e_diag_out, -1);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, Quad3)
+{
+ CDT_input in;
+ CDT_result *out;
+ int e_diag_out;
+ const char *spec = R"(4 0 0
+ 0.5 0.0
+ 0.0 0.0
+ 0.3 0.4
+ .45 0.35
+ )";
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 4);
+ EXPECT_EQ(out->edges_len, 5);
+ e_diag_out = get_edge(out, 0, 2);
+ EXPECT_NE(e_diag_out, -1);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, Quad4)
+{
+ CDT_input in;
+ CDT_result *out;
+ int e_diag_out;
+ const char *spec = R"(4 0 0
+ 1.0 1.0
+ 0.0 0.0
+ 1.0 -3.0
+ 0.0 1.0
+ )";
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 4);
+ EXPECT_EQ(out->edges_len, 5);
+ e_diag_out = get_edge(out, 0, 1);
+ EXPECT_NE(e_diag_out, -1);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, LineInSquare)
+{
+ CDT_input in;
+ CDT_result *out;
+ const char *spec = R"(6 1 1
+ -0.5 -0.5
+ 0.5 -0.5
+ -0.5 0.5
+ 0.5 0.5
+ -0.25 0.0
+ 0.25 0.0
+ 4 5
+ 0 1 3 2
+ )";
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 6);
+ EXPECT_EQ(out->faces_len, 1);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, CrossSegs)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v0_out, v1_out, v2_out, v3_out, v_intersect;
+ int i;
+ const char *spec = R"(4 2 0
+ -0.5 0.0
+ 0.5 0.0
+ -0.4 -0.5
+ 0.4 0.5
+ 0 1
+ 2 3
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 5);
+ EXPECT_EQ(out->edges_len, 8);
+ EXPECT_EQ(out->faces_len, 4);
+ v0_out = get_output_vert_index(out, 0);
+ v1_out = get_output_vert_index(out, 1);
+ v2_out = get_output_vert_index(out, 2);
+ v3_out = get_output_vert_index(out, 3);
+ EXPECT_TRUE(v0_out != -1 && v1_out != -1 && v2_out != -1 && v3_out != -1);
+ v_intersect = -1;
+ for (i = 0; i < out->verts_len; i++) {
+ if (i != v0_out && i != v1_out && i != v2_out && i != v3_out) {
+ EXPECT_EQ(v_intersect, -1);
+ v_intersect = i;
+ }
+ }
+ EXPECT_NE(v_intersect, -1);
+ if (v_intersect != -1) {
+ EXPECT_NEAR(out->vert_coords[v_intersect][0], 0.0f, in.epsilon);
+ EXPECT_NEAR(out->vert_coords[v_intersect][1], 0.0f, in.epsilon);
+ }
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, DiamondCross)
+{
+ CDT_input in;
+ CDT_result *out;
+ const char *spec = R"(7 5 0
+ 0.0 0.0
+ 1.0 3.0
+ 2.0 0.0
+ 1.0 -3.0
+ 0.0 0.0
+ 1.0 -3.0
+ 1.0 3.0
+ 0 1
+ 1 2
+ 2 3
+ 3 4
+ 5 6
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 4);
+ EXPECT_EQ(out->edges_len, 5);
+ EXPECT_EQ(out->faces_len, 2);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, TwoDiamondsCrossed)
+{
+ CDT_input in;
+ CDT_result *out;
+ /* Input has some repetition of vertices, on purpose */
+ int e[][2] = {{0, 1}, {1, 2}, {2, 3}, {3, 4}, {5, 6}, {6, 7}, {7, 8}, {8, 9}, {10, 11}};
+ int v_out[12];
+ int e_out[9], e_cross_1, e_cross_2, e_cross_3;
+ int i;
+ const char *spec = R"(12 9 0
+ 0.0 0.0
+ 1.0 2.0
+ 2.0 0.0
+ 1.0 -2.0
+ 0.0 0.0
+ 3.0 0.0
+ 4.0 2.0
+ 5.0 0.0
+ 4.0 -2.0
+ 3.0 0.0
+ 0.0 0.0
+ 5.0 0.0
+ 0 1
+ 1 2
+ 2 3
+ 3 4
+ 5 6
+ 6 7
+ 7 8
+ 8 9
+ 10 11
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 8);
+ EXPECT_EQ(out->edges_len, 15);
+ EXPECT_EQ(out->faces_len, 8);
+ for (i = 0; i < 12; i++) {
+ v_out[i] = get_output_vert_index(out, i);
+ EXPECT_NE(v_out[i], -1);
+ }
+ EXPECT_EQ(v_out[0], v_out[4]);
+ EXPECT_EQ(v_out[0], v_out[10]);
+ EXPECT_EQ(v_out[5], v_out[9]);
+ EXPECT_EQ(v_out[7], v_out[11]);
+ for (i = 0; i < 8; i++) {
+ e_out[i] = get_edge(out, v_out[e[i][0]], v_out[e[i][1]]);
+ EXPECT_NE(e_out[i], -1);
+ }
+ /* there won't be a single edge for the input cross edge, but rather 3 */
+ EXPECT_EQ(get_edge(out, v_out[10], v_out[11]), -1);
+ e_cross_1 = get_edge(out, v_out[0], v_out[2]);
+ e_cross_2 = get_edge(out, v_out[2], v_out[5]);
+ e_cross_3 = get_edge(out, v_out[5], v_out[7]);
+ EXPECT_TRUE(e_cross_1 != -1 && e_cross_2 != -1 && e_cross_3 != -1);
+ EXPECT_TRUE(out_edge_has_input_id(out, e_cross_1, 8));
+ EXPECT_TRUE(out_edge_has_input_id(out, e_cross_2, 8));
+ EXPECT_TRUE(out_edge_has_input_id(out, e_cross_3, 8));
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, ManyCross)
+{
+ CDT_input in;
+ CDT_result *out;
+ /* Input has some repetition of vertices, on purpose */
+ const char *spec = R"(27 21 0
+ 0.0 0.0
+ 6.0 9.0
+ 15.0 18.0
+ 35.0 13.0
+ 43.0 18.0
+ 57.0 12.0
+ 69.0 10.0
+ 78.0 0.0
+ 91.0 0.0
+ 107.0 22.0
+ 123.0 0.0
+ 0.0 0.0
+ 10.0 -14.0
+ 35.0 -8.0
+ 43.0 -12.0
+ 64.0 -13.0
+ 78.0 0.0
+ 91.0 0.0
+ 102.0 -9.0
+ 116.0 -9.0
+ 123.0 0.0
+ 43.0 18.0
+ 43.0 -12.0
+ 107.0 22.0
+ 102.0 -9.0
+ 0.0 0.0
+ 123.0 0.0
+ 0 1
+ 1 2
+ 2 3
+ 3 4
+ 4 5
+ 5 6
+ 6 7
+ 7 8
+ 8 9
+ 9 10
+ 11 12
+ 12 13
+ 13 14
+ 14 15
+ 15 16
+ 17 18
+ 18 19
+ 19 20
+ 21 22
+ 23 24
+ 25 26
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 19);
+ EXPECT_EQ(out->edges_len, 46);
+ EXPECT_EQ(out->faces_len, 28);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, TwoFace)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v_out[6], f0_out, f1_out, e0_out, e1_out, e2_out;
+ int i;
+ const char *spec = R"(6 0 2
+ 0.0 0.0
+ 1.0 0.0
+ 0.5 1.0
+ 1.1 1.0
+ 1.1 0.0
+ 1.6 1.0
+ 0 1 2
+ 3 4 5
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 6);
+ EXPECT_EQ(out->edges_len, 9);
+ EXPECT_EQ(out->faces_len, 4);
+ for (i = 0; i < 6; i++) {
+ v_out[i] = get_output_vert_index(out, i);
+ EXPECT_NE(v_out[i], -1);
+ }
+ f0_out = get_face(out, &v_out[0], 3);
+ f1_out = get_face(out, &v_out[3], 3);
+ EXPECT_NE(f0_out, -1);
+ EXPECT_NE(f1_out, -1);
+ e0_out = get_edge(out, v_out[0], v_out[1]);
+ e1_out = get_edge(out, v_out[1], v_out[2]);
+ e2_out = get_edge(out, v_out[2], v_out[0]);
+ EXPECT_NE(e0_out, -1);
+ EXPECT_NE(e1_out, -1);
+ EXPECT_NE(e2_out, -1);
+ EXPECT_TRUE(out_edge_has_input_id(out, e0_out, out->face_edge_offset + 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e1_out, out->face_edge_offset + 1));
+ EXPECT_TRUE(out_edge_has_input_id(out, e2_out, out->face_edge_offset + 2));
+ EXPECT_TRUE(out_face_has_input_id(out, f0_out, 0));
+ EXPECT_TRUE(out_face_has_input_id(out, f1_out, 1));
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, OverlapFaces)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v_out[12], v_int1, v_int2, f0_out, f1_out, f2_out;
+ int i;
+ const char *spec = R"(12 0 3
+ 0.0 0.0
+ 1.0 0.0
+ 1.0 1.0
+ 0.0 1.0
+ 0.5 0.5
+ 1.5 0.5
+ 1.5 1.3
+ 0.5 1.3
+ 0.1 0.1
+ 0.3 0.1
+ 0.3 0.3
+ 0.1 0.3
+ 0 1 2 3
+ 4 5 6 7
+ 8 9 10 11
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ EXPECT_EQ(out->verts_len, 14);
+ EXPECT_EQ(out->edges_len, 33);
+ EXPECT_EQ(out->faces_len, 20);
+ for (i = 0; i < 12; i++) {
+ v_out[i] = get_output_vert_index(out, i);
+ EXPECT_NE(v_out[i], -1);
+ }
+ v_int1 = 12;
+ v_int2 = 13;
+ if (out->verts_len > 13) {
+ if (fabsf(out->vert_coords[v_int1][0] - 1.0f) > in.epsilon) {
+ v_int1 = 13;
+ v_int2 = 12;
+ }
+ EXPECT_NEAR(out->vert_coords[v_int1][0], 1.0, in.epsilon);
+ EXPECT_NEAR(out->vert_coords[v_int1][1], 0.5, in.epsilon);
+ EXPECT_NEAR(out->vert_coords[v_int2][0], 0.5, in.epsilon);
+ EXPECT_NEAR(out->vert_coords[v_int2][1], 1.0, in.epsilon);
+ EXPECT_EQ(out->verts_orig_len_table[v_int1], 0);
+ EXPECT_EQ(out->verts_orig_len_table[v_int2], 0);
+ }
+ f0_out = get_face_tri(out, v_out[1], v_int1, v_out[4]);
+ EXPECT_NE(f0_out, -1);
+ EXPECT_TRUE(out_face_has_input_id(out, f0_out, 0));
+ f1_out = get_face_tri(out, v_out[4], v_int1, v_out[2]);
+ EXPECT_NE(f1_out, -1);
+ EXPECT_TRUE(out_face_has_input_id(out, f1_out, 0));
+ EXPECT_TRUE(out_face_has_input_id(out, f1_out, 1));
+ f2_out = get_face_tri(out, v_out[8], v_out[9], v_out[10]);
+ if (f2_out == -1)
+ f2_out = get_face_tri(out, v_out[8], v_out[9], v_out[11]);
+ EXPECT_NE(f2_out, -1);
+ EXPECT_TRUE(out_face_has_input_id(out, f2_out, 0));
+ EXPECT_TRUE(out_face_has_input_id(out, f2_out, 2));
+ BLI_delaunay_2d_cdt_free(out);
+
+ /* Different output types */
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_INSIDE);
+ EXPECT_EQ(out->faces_len, 18);
+ BLI_delaunay_2d_cdt_free(out);
+
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->faces_len, 4);
+ BLI_delaunay_2d_cdt_free(out);
+
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS_VALID_BMESH);
+ EXPECT_EQ(out->faces_len, 5);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, TwoSquaresOverlap)
+{
+ CDT_input in;
+ CDT_result *out;
+ const char *spec = R"(8 0 2
+ 1.0 -1.0
+ -1.0 -1.0
+ -1.0 1.0
+ 1.0 1.0
+ -1.5 1.5
+ 0.5 1.5
+ 0.5 -0.5
+ -1.5 -0.5
+ 7 6 5 4
+ 3 2 1 0
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS_VALID_BMESH);
+ EXPECT_EQ(out->verts_len, 10);
+ EXPECT_EQ(out->edges_len, 12);
+ EXPECT_EQ(out->faces_len, 3);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, TwoFaceEdgeOverlap)
+{
+ CDT_input in;
+ CDT_result *out;
+ int i, v_out[6], v_int;
+ int e01, e1i, ei2, e20, e24, e4i, ei0;
+ int f02i, f24i, f10i;
+ const char *spec = R"(6 0 2
+ 5.657 0.0
+ -1.414 -5.831
+ 0.0 0.0
+ 5.657 0.0
+ -2.121 -2.915
+ 0.0 0.0
+ 2 1 0
+ 5 4 3
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 5);
+ EXPECT_EQ(out->edges_len, 7);
+ EXPECT_EQ(out->faces_len, 3);
+ if (out->verts_len == 5 && out->edges_len == 7 && out->faces_len == 3) {
+ v_int = 4;
+ for (i = 0; i < 6; i++) {
+ v_out[i] = get_output_vert_index(out, i);
+ EXPECT_NE(v_out[i], -1);
+ EXPECT_NE(v_out[i], v_int);
+ }
+ EXPECT_EQ(v_out[0], v_out[3]);
+ EXPECT_EQ(v_out[2], v_out[5]);
+ e01 = get_edge(out, v_out[0], v_out[1]);
+ EXPECT_TRUE(out_edge_has_input_id(out, e01, 1));
+ e1i = get_edge(out, v_out[1], v_int);
+ EXPECT_TRUE(out_edge_has_input_id(out, e1i, 0));
+ ei2 = get_edge(out, v_int, v_out[2]);
+ EXPECT_TRUE(out_edge_has_input_id(out, ei2, 0));
+ e20 = get_edge(out, v_out[2], v_out[0]);
+ EXPECT_TRUE(out_edge_has_input_id(out, e20, 2));
+ EXPECT_TRUE(out_edge_has_input_id(out, e20, 5));
+ e24 = get_edge(out, v_out[2], v_out[4]);
+ EXPECT_TRUE(out_edge_has_input_id(out, e24, 3));
+ e4i = get_edge(out, v_out[4], v_int);
+ EXPECT_TRUE(out_edge_has_input_id(out, e4i, 4));
+ ei0 = get_edge(out, v_int, v_out[0]);
+ EXPECT_TRUE(out_edge_has_input_id(out, ei0, 4));
+ f02i = get_face_tri(out, v_out[0], v_out[2], v_int);
+ EXPECT_NE(f02i, -1);
+ EXPECT_TRUE(out_face_has_input_id(out, f02i, 0));
+ EXPECT_TRUE(out_face_has_input_id(out, f02i, 1));
+ f24i = get_face_tri(out, v_out[2], v_out[4], v_int);
+ EXPECT_NE(f24i, -1);
+ EXPECT_TRUE(out_face_has_input_id(out, f24i, 1));
+ EXPECT_FALSE(out_face_has_input_id(out, f24i, 0));
+ f10i = get_face_tri(out, v_out[1], v_out[0], v_int);
+ EXPECT_NE(f10i, -1);
+ EXPECT_TRUE(out_face_has_input_id(out, f10i, 0));
+ EXPECT_FALSE(out_face_has_input_id(out, f10i, 1));
+ }
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, TriInTri)
+{
+ CDT_input in;
+ CDT_result *out;
+ const char *spec = R"(6 0 2
+ -5.65685 0.0
+ 1.41421 -5.83095
+ 0.0 0.0
+ -2.47487 -1.45774
+ -0.707107 -2.91548
+ -1.06066 -1.45774
+ 0 1 2
+ 3 4 5
+ )";
+
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS_VALID_BMESH);
+ EXPECT_EQ(out->verts_len, 6);
+ EXPECT_EQ(out->edges_len, 8);
+ EXPECT_EQ(out->faces_len, 3);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, DiamondInSquare)
+{
+ CDT_input in;
+ CDT_result *out;
+ const char *spec = R"(8 0 2
+ 0.0 0.0
+ 1.0 0.0
+ 1.0 1.0
+ 0.0 1.0
+ 0.14644660940672627 0.5
+ 0.5 0.14644660940672627
+ 0.8535533905932737 0.5
+ 0.5 0.8535533905932737
+ 0 1 2 3
+ 4 5 6 7
+ )";
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS_VALID_BMESH);
+ EXPECT_EQ(out->verts_len, 8);
+ EXPECT_EQ(out->edges_len, 10);
+ EXPECT_EQ(out->faces_len, 3);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, DiamondInSquareWire)
+{
+ CDT_input in;
+ CDT_result *out;
+ const char *spec = R"(8 8 0
+ 0.0 0.0
+ 1.0 0.0
+ 1.0 1.0
+ 0.0 1.0
+ 0.14644660940672627 0.5
+ 0.5 0.14644660940672627
+ 0.8535533905932737 0.5
+ 0.5 0.8535533905932737
+ 0 1
+ 1 2
+ 2 3
+ 3 0
+ 4 5
+ 5 6
+ 6 7
+ 7 4
+ )";
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 8);
+ EXPECT_EQ(out->edges_len, 8);
+ EXPECT_EQ(out->faces_len, 2);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, TinyEdge)
+{
+ CDT_input in;
+ CDT_result *out;
+ /* An intersect with triangle would be at (0.8, 0.2). */
+ const char *spec = R"(4 1 1
+ 0.0 0.0
+ 1.0 0.0
+ 0.5 0.5
+ 0.84 0.21
+ 0 3
+ 0 1 2
+ )";
+ fill_input_from_string(&in, spec);
+ in.epsilon = 0.1;
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 4);
+ EXPECT_EQ(out->edges_len, 5);
+ EXPECT_EQ(out->faces_len, 2);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, TinyEdge2)
+{
+ CDT_input in;
+ CDT_result *out;
+ /* An intersect with triangle would be at (0.8, 0.2). */
+ const char *spec = R"(6 1 1
+ 0.0 0.0
+ 0.2 -0.2
+ 1.0 0.0
+ 0.5 0.5
+ 0.2 0.4
+ 0.84 0.21
+ 0 5
+ 0 1 2 3 4
+ )";
+ fill_input_from_string(&in, spec);
+ in.epsilon = 0.1;
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 6);
+ EXPECT_EQ(out->edges_len, 7);
+ EXPECT_EQ(out->faces_len, 2);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, repeatededge)
+{
+ CDT_input in;
+ CDT_result *out;
+ const char *spec = R"(5 3 0
+ 0.0 0.0
+ 0.0 1.0
+ 1.0 1.1
+ 0.5 -0.5
+ 0.5 2.5
+ 0 1
+ 2 3
+ 2 3
+ )";
+ fill_input_from_string(&in, spec);
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->edges_len, 2);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, NearSeg)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v[4], e0, e1, e2, i;
+ const char *spec = R"(4 2 0
+ 0.0 0.0
+ 1.0 0.0
+ 0.25 0.09
+ 0.25 1.0
+ 0 1
+ 2 3
+ )";
+
+ fill_input_from_string(&in, spec);
+ in.epsilon = 0.1;
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 4);
+ EXPECT_EQ(out->edges_len, 3);
+ EXPECT_EQ(out->faces_len, 0);
+ if (out->edges_len == 3) {
+ for (i = 0; i < 4; i++) {
+ v[i] = get_output_vert_index(out, i);
+ EXPECT_NE(v[i], -1);
+ }
+ e0 = get_edge(out, v[0], v[2]);
+ e1 = get_edge(out, v[2], v[1]);
+ e2 = get_edge(out, v[2], v[3]);
+ EXPECT_TRUE(out_edge_has_input_id(out, e0, 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e1, 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e2, 1));
+ }
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, OverlapSegs)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v[4], e0, e1, e2, i;
+ const char *spec = R"(4 2 0
+ 0.0 0.0
+ 1.0 0.0
+ 0.4 0.09
+ 1.4 0.09
+ 0 1
+ 2 3
+ )";
+
+ fill_input_from_string(&in, spec);
+ in.epsilon = 0.1;
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 4);
+ EXPECT_EQ(out->edges_len, 3);
+ EXPECT_EQ(out->faces_len, 0);
+ if (out->edges_len == 3) {
+ for (i = 0; i < 4; i++) {
+ v[i] = get_output_vert_index(out, i);
+ EXPECT_NE(v[i], -1);
+ }
+ e0 = get_edge(out, v[0], v[2]);
+ e1 = get_edge(out, v[2], v[1]);
+ e2 = get_edge(out, v[1], v[3]);
+ EXPECT_TRUE(out_edge_has_input_id(out, e0, 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e1, 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e1, 1));
+ EXPECT_TRUE(out_edge_has_input_id(out, e2, 1));
+ }
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, NearSegWithDup)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v[5], e0, e1, e2, e3, i;
+ const char *spec = R"(5 3 0
+ 0.0 0.0
+ 1.0 0.0
+ 0.25 0.09
+ 0.25 1.0
+ 0.75 0.09
+ 0 1
+ 2 3
+ 2 4
+ )";
+
+ fill_input_from_string(&in, spec);
+ in.epsilon = 0.1;
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 5);
+ EXPECT_EQ(out->edges_len, 4);
+ EXPECT_EQ(out->faces_len, 0);
+ if (out->edges_len == 5) {
+ for (i = 0; i < 5; i++) {
+ v[i] = get_output_vert_index(out, i);
+ EXPECT_NE(v[i], -1);
+ }
+ e0 = get_edge(out, v[0], v[2]);
+ e1 = get_edge(out, v[2], v[4]);
+ e2 = get_edge(out, v[4], v[1]);
+ e3 = get_edge(out, v[3], v[2]);
+ EXPECT_TRUE(out_edge_has_input_id(out, e0, 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e1, 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e1, 2));
+ EXPECT_TRUE(out_edge_has_input_id(out, e2, 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e3, 1));
+ }
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, TwoNearSeg)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v[5], e0, e1, e2, e3, e4, i;
+ const char *spec = R"(5 3 0
+ 0.0 0.0
+ 1.0 0.0
+ 0.25 0.09
+ 0.25 1.0
+ 0.75 0.09
+ 0 1
+ 3 2
+ 3 4
+ )";
+
+ fill_input_from_string(&in, spec);
+ in.epsilon = 0.1;
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 5);
+ EXPECT_EQ(out->edges_len, 5);
+ EXPECT_EQ(out->faces_len, 1);
+ if (out->edges_len == 5) {
+ for (i = 0; i < 5; i++) {
+ v[i] = get_output_vert_index(out, i);
+ EXPECT_NE(v[i], -1);
+ }
+ e0 = get_edge(out, v[0], v[2]);
+ e1 = get_edge(out, v[2], v[4]);
+ e2 = get_edge(out, v[4], v[1]);
+ e3 = get_edge(out, v[3], v[2]);
+ e4 = get_edge(out, v[3], v[4]);
+ EXPECT_TRUE(out_edge_has_input_id(out, e0, 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e1, 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e2, 0));
+ EXPECT_TRUE(out_edge_has_input_id(out, e3, 1));
+ EXPECT_TRUE(out_edge_has_input_id(out, e4, 2));
+ }
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, FaceNearSegs)
+{
+ CDT_input in;
+ CDT_result *out;
+ int v[9], e0, e1, e2, e3, i;
+ const char *spec = R"(8 1 2
+ 0.0 0.0
+ 2.0 0.0
+ 1.0 1.0
+ 0.21 0.2
+ 1.79 0.2
+ 0.51 0.5
+ 1.49 0.5
+ 1.0 0.19
+ 2 7
+ 0 1 2
+ 3 4 6 5
+ )";
+
+ fill_input_from_string(&in, spec);
+ in.epsilon = 0.05;
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 9);
+ EXPECT_EQ(out->edges_len, 13);
+ EXPECT_EQ(out->faces_len, 5);
+ if (out->verts_len == 9 && out->edges_len == 13) {
+ for (i = 0; i < 8; i++) {
+ v[i] = get_output_vert_index(out, i);
+ EXPECT_NE(v[i], -1);
+ }
+ v[8] = 8;
+ e0 = get_edge(out, v[0], v[1]);
+ e1 = get_edge(out, v[4], v[6]);
+ e2 = get_edge(out, v[3], v[0]);
+ e3 = get_edge(out, v[2], v[8]);
+
+ EXPECT_TRUE(out_edge_has_input_id(out, e0, 1));
+ EXPECT_TRUE(out_edge_has_input_id(out, e1, 2));
+ EXPECT_TRUE(out_edge_has_input_id(out, e1, 5));
+ EXPECT_TRUE(out_edge_has_input_id(out, e2, 3));
+ EXPECT_TRUE(out_edge_has_input_id(out, e3, 0));
+ }
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+
+TEST(delaunay, ChainNearIntersects)
+{
+ CDT_input in;
+ CDT_result *out;
+ const char *spec = R"(6 10 0
+ 0.8 1.25
+ 1.25 0.75
+ 3.25 1.25
+ 5.0 1.9
+ 2.5 4.0
+ 1.0 2.25
+ 0 1
+ 1 2
+ 2 3
+ 3 4
+ 4 5
+ 5 0
+ 0 2
+ 5 2
+ 4 2
+ 1 3
+ )";
+
+ fill_input_from_string(&in, spec);
+ in.epsilon = 0.05;
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 9);
+ EXPECT_EQ(out->edges_len, 16);
+ BLI_delaunay_2d_cdt_free(out);
+ in.epsilon = 0.11;
+ /* The chaining we want to test happens prematurely if modify input. */
+ in.skip_input_modify = true;
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ EXPECT_EQ(out->verts_len, 6);
+ EXPECT_EQ(out->edges_len, 9);
+ free_spec_arrays(&in);
+ BLI_delaunay_2d_cdt_free(out);
+}
+#endif
+
+#if DO_RANDOM_TESTS
+enum {
+ RANDOM_PTS,
+ RANDOM_SEGS,
+ RANDOM_POLY,
+ RANDOM_TILTED_GRID,
+ RANDOM_CIRCLE,
+ RANDOM_TRI_BETWEEN_CIRCLES,
+};
+
+# define DO_TIMING
+static void rand_delaunay_test(int test_kind,
+ int start_lg_size,
+ int max_lg_size,
+ int reps_per_size,
+ double param,
+ CDT_output_type otype)
+{
+ CDT_input in;
+ CDT_result *out;
+ int lg_size, size, rep, i, j, size_max, npts_max, nedges_max, nfaces_max, npts, nedges, nfaces;
+ int ia, ib, ic;
+ float(*p)[2];
+ int(*e)[2];
+ int *faces, *faces_start_table, *faces_len_table;
+ double start_angle, angle_delta, angle1, angle2, angle3;
+ float orient;
+ double tstart;
+ double *times;
+ RNG *rng;
+
+ rng = BLI_rng_new(0);
+ e = NULL;
+ faces = NULL;
+ faces_start_table = NULL;
+ faces_len_table = NULL;
+ nedges_max = 0;
+ nfaces_max = 0;
+
+ /* Set up npts, nedges, nfaces, and allocate needed arrays at max length needed. */
+ size_max = 1 << max_lg_size;
+ switch (test_kind) {
+ case RANDOM_PTS:
+ case RANDOM_SEGS:
+ case RANDOM_POLY:
+ npts_max = size_max;
+ if (test_kind == RANDOM_SEGS) {
+ nedges_max = npts_max - 1;
+ }
+ else if (test_kind == RANDOM_POLY) {
+ nedges_max = npts_max;
+ }
+ break;
+
+ case RANDOM_TILTED_GRID:
+ /* A 'size' x 'size' grid of points, tilted by angle 'param'.
+ * Edges will go from left ends to right ends and tops to bottoms, so 2 x size of them.
+ * Depending on epsilon, the vertical-ish edges may or may not go through the intermediate
+ * vertices, but the horizontal ones always should.
+ */
+ npts_max = size_max * size_max;
+ nedges_max = 2 * size_max;
+ break;
+
+ case RANDOM_CIRCLE:
+ /* A circle with 'size' points, a random start angle, and equal spacing thereafter.
+ * Will be input as one face.
+ */
+ npts_max = size_max;
+ nfaces_max = 1;
+ break;
+
+ case RANDOM_TRI_BETWEEN_CIRCLES:
+ /* A set of 'size' triangles, each has two random points on the unit circle,
+ * and the third point is a random point on the circle with radius 'param'.
+ * Each triangle will be input as a face.
+ */
+ npts_max = 3 * size_max;
+ nfaces_max = size_max;
+ break;
+
+ default:
+ fprintf(stderr, "unknown random delaunay test kind\n");
+ return;
+ }
+ p = (float(*)[2])MEM_malloc_arrayN(npts_max, 2 * sizeof(float), __func__);
+ if (nedges_max > 0) {
+ e = (int(*)[2])MEM_malloc_arrayN(nedges_max, 2 * sizeof(int), __func__);
+ }
+ if (nfaces_max > 0) {
+ faces_start_table = (int *)MEM_malloc_arrayN(nfaces_max, sizeof(int), __func__);
+ faces_len_table = (int *)MEM_malloc_arrayN(nfaces_max, sizeof(int), __func__);
+ faces = (int *)MEM_malloc_arrayN(npts_max, sizeof(int), __func__);
+ }
+
+ times = (double *)MEM_malloc_arrayN(max_lg_size + 1, sizeof(double), __func__);
+
+ /* For powers of 2 sizes up to max_lg_size power of 2. */
+ for (lg_size = start_lg_size; lg_size <= max_lg_size; lg_size++) {
+ size = 1 << lg_size;
+ nedges = 0;
+ nfaces = 0;
+ times[lg_size] = 0.0;
+ if (size == 1 && test_kind != RANDOM_PTS) {
+ continue;
+ }
+ /* Do 'rep' repetitions. */
+ for (rep = 0; rep < reps_per_size; rep++) {
+ /* Make vertices and edges or faces. */
+ switch (test_kind) {
+ case RANDOM_PTS:
+ case RANDOM_SEGS:
+ case RANDOM_POLY:
+ npts = size;
+ if (test_kind == RANDOM_SEGS) {
+ nedges = npts - 1;
+ }
+ else if (test_kind == RANDOM_POLY) {
+ nedges = npts;
+ }
+ for (i = 0; i < size; i++) {
+ p[i][0] = (float)BLI_rng_get_double(rng); /* will be in range in [0,1) */
+ p[i][1] = (float)BLI_rng_get_double(rng);
+ if (test_kind != RANDOM_PTS) {
+ if (i > 0) {
+ e[i - 1][0] = i - 1;
+ e[i - 1][1] = i;
+ }
+ }
+ }
+ if (test_kind == RANDOM_POLY) {
+ e[size - 1][0] = size - 1;
+ e[size - 1][1] = 0;
+ }
+ break;
+
+ case RANDOM_TILTED_GRID:
+ /* 'param' is slope of tilt of vertical lines. */
+ npts = size * size;
+ nedges = 2 * size;
+ for (i = 0; i < size; i++) {
+ for (j = 0; j < size; j++) {
+ p[i * size + j][0] = i * param + j;
+ p[i * size + j][1] = i;
+ }
+ }
+ for (i = 0; i < size; i++) {
+ /* Horizontal edges: connect p(i,0) to p(i,size-1). */
+ e[i][0] = i * size;
+ e[i][1] = i * size + size - 1;
+ /* Vertical edges: conntect p(0,i) to p(size-1,i). */
+ e[size + i][0] = i;
+ e[size + i][1] = (size - 1) * size + i;
+ }
+ break;
+
+ case RANDOM_CIRCLE:
+ npts = size;
+ nfaces = 1;
+ faces_start_table[0] = 0;
+ faces_len_table[0] = npts;
+ start_angle = BLI_rng_get_double(rng) * 2.0 * M_PI;
+ angle_delta = 2.0 * M_PI / size;
+ for (i = 0; i < size; i++) {
+ p[i][0] = (float)cos(start_angle + i * angle_delta);
+ p[i][1] = (float)sin(start_angle + i * angle_delta);
+ faces[i] = i;
+ }
+ break;
+
+ case RANDOM_TRI_BETWEEN_CIRCLES:
+ npts = 3 * size;
+ nfaces = size;
+ for (i = 0; i < size; i++) {
+ /* Get three random angles in [0, 2pi). */
+ angle1 = BLI_rng_get_double(rng) * 2.0 * M_PI;
+ angle2 = BLI_rng_get_double(rng) * 2.0 * M_PI;
+ angle3 = BLI_rng_get_double(rng) * 2.0 * M_PI;
+ ia = 3 * i;
+ ib = 3 * i + 1;
+ ic = 3 * i + 2;
+ p[ia][0] = (float)cos(angle1);
+ p[ia][1] = (float)sin(angle1);
+ p[ib][0] = (float)cos(angle2);
+ p[ib][1] = (float)sin(angle2);
+ p[ic][0] = (float)(param * cos(angle3));
+ p[ic][1] = (float)(param * sin(angle3));
+ faces_start_table[i] = 3 * i;
+ faces_len_table[i] = 3;
+ /* Put the coordinates in ccw order. */
+ faces[ia] = ia;
+ orient = (p[ia][0] - p[ic][0]) * (p[ib][1] - p[ic][1]) -
+ (p[ib][0] - p[ic][0]) * (p[ia][1] - p[ic][1]);
+ if (orient >= 0.0f) {
+ faces[ib] = ib;
+ faces[ic] = ic;
+ }
+ else {
+ faces[ib] = ic;
+ faces[ic] = ib;
+ }
+ }
+ break;
+ }
+ fill_input_verts(&in, p, npts);
+ if (nedges > 0) {
+ add_input_edges(&in, e, nedges);
+ }
+ if (nfaces > 0) {
+ add_input_faces(&in, faces, faces_start_table, faces_len_table, nfaces);
+ }
+
+ /* Run the test. */
+ tstart = PIL_check_seconds_timer();
+ out = BLI_delaunay_2d_cdt_calc(&in, otype);
+ EXPECT_NE(out->verts_len, 0);
+ BLI_delaunay_2d_cdt_free(out);
+ times[lg_size] += PIL_check_seconds_timer() - tstart;
+ }
+ }
+# ifdef DO_TIMING
+ fprintf(stderr, "size,time\n");
+ for (lg_size = 0; lg_size <= max_lg_size; lg_size++) {
+ fprintf(stderr, "%d,%f\n", 1 << lg_size, times[lg_size] / reps_per_size);
+ }
+# endif
+ MEM_freeN(p);
+ if (e) {
+ MEM_freeN(e);
+ }
+ if (faces) {
+ MEM_freeN(faces);
+ MEM_freeN(faces_start_table);
+ MEM_freeN(faces_len_table);
+ }
+ MEM_freeN(times);
+ BLI_rng_free(rng);
+}
+
+TEST(delaunay, randompts)
+{
+ rand_delaunay_test(RANDOM_PTS, 0, 7, 1, 0.0, CDT_FULL);
+}
+
+TEST(delaunay, randomsegs)
+{
+ rand_delaunay_test(RANDOM_SEGS, 1, 7, 1, 0.0, CDT_FULL);
+}
+
+TEST(delaunay, randompoly)
+{
+ rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_FULL);
+}
+
+TEST(delaunay, randompoly_inside)
+{
+ rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_INSIDE);
+}
+
+TEST(delaunay, randompoly_constraints)
+{
+ rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS);
+}
+
+TEST(delaunay, randompoly_validbmesh)
+{
+ rand_delaunay_test(RANDOM_POLY, 1, 7, 1, 0.0, CDT_CONSTRAINTS_VALID_BMESH);
+}
+
+TEST(delaunay, grid)
+{
+ rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 0.0, CDT_FULL);
+}
+
+TEST(delaunay, tilted_grid_a)
+{
+ rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 1.0, CDT_FULL);
+}
+
+TEST(delaunay, tilted_grid_b)
+{
+ rand_delaunay_test(RANDOM_TILTED_GRID, 1, 6, 1, 0.01, CDT_FULL);
+}
+
+TEST(delaunay, randomcircle)
+{
+ rand_delaunay_test(RANDOM_CIRCLE, 1, 7, 1, 0.0, CDT_FULL);
+}
+
+TEST(delaunay, random_tris_circle)
+{
+ rand_delaunay_test(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 0.25, CDT_FULL);
+}
+
+TEST(delaunay, random_tris_circle_b)
+{
+ rand_delaunay_test(RANDOM_TRI_BETWEEN_CIRCLES, 1, 6, 1, 1e-4, CDT_FULL);
+}
+#endif
+
+#if DO_FILE_TESTS
+/* For manually testing performance by timing a large number of points from a
+ * file. See fill_input_from_file for file format.
+ */
+static void points_from_file_test(const char *filename)
+{
+ CDT_input in;
+ CDT_result *out;
+ double tstart;
+
+ fill_input_from_file(&in, filename);
+ tstart = PIL_check_seconds_timer();
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_FULL);
+ fprintf(stderr, "time to triangulate=%f seconds\n", PIL_check_seconds_timer() - tstart);
+ BLI_delaunay_2d_cdt_free(out);
+ free_spec_arrays(&in);
+}
+
+# if 0
+TEST(delaunay, debug)
+{
+ CDT_input in;
+ CDT_result *out;
+ fill_input_from_file(&in, "/tmp/cdtinput.txt");
+ out = BLI_delaunay_2d_cdt_calc(&in, CDT_CONSTRAINTS);
+ BLI_delaunay_2d_cdt_free(out);
+ free_spec_arrays(&in);
+}
+# endif
+
+# if 1
+# define POINTFILEROOT "/tmp/"
+
+TEST(delaunay, terrain1)
+{
+ points_from_file_test(POINTFILEROOT "points1.txt");
+}
+
+TEST(delaunay, terrain2)
+{
+ points_from_file_test(POINTFILEROOT "points2.txt");
+}
+
+TEST(delaunay, terrain3)
+{
+ points_from_file_test(POINTFILEROOT "points3.txt");
+}
+# endif
+#endif
diff --git a/source/blender/blenlib/tests/BLI_expr_pylike_eval_test.cc b/source/blender/blenlib/tests/BLI_expr_pylike_eval_test.cc
new file mode 100644
index 00000000000..aad21ae4ad4
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_expr_pylike_eval_test.cc
@@ -0,0 +1,363 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include <string.h>
+
+#include "BLI_expr_pylike_eval.h"
+#include "BLI_math.h"
+
+#define TRUE_VAL 1.0
+#define FALSE_VAL 0.0
+
+static void expr_pylike_parse_fail_test(const char *str)
+{
+ ExprPyLike_Parsed *expr = BLI_expr_pylike_parse(str, NULL, 0);
+
+ EXPECT_FALSE(BLI_expr_pylike_is_valid(expr));
+
+ BLI_expr_pylike_free(expr);
+}
+
+static void expr_pylike_const_test(const char *str, double value, bool force_const)
+{
+ ExprPyLike_Parsed *expr = BLI_expr_pylike_parse(str, NULL, 0);
+
+ if (force_const) {
+ EXPECT_TRUE(BLI_expr_pylike_is_constant(expr));
+ }
+ else {
+ EXPECT_TRUE(BLI_expr_pylike_is_valid(expr));
+ EXPECT_FALSE(BLI_expr_pylike_is_constant(expr));
+ }
+
+ double result;
+ eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, NULL, 0, &result);
+
+ EXPECT_EQ(status, EXPR_PYLIKE_SUCCESS);
+ EXPECT_EQ(result, value);
+
+ BLI_expr_pylike_free(expr);
+}
+
+static ExprPyLike_Parsed *parse_for_eval(const char *str, bool nonconst)
+{
+ const char *names[1] = {"x"};
+ ExprPyLike_Parsed *expr = BLI_expr_pylike_parse(str, names, ARRAY_SIZE(names));
+
+ EXPECT_TRUE(BLI_expr_pylike_is_valid(expr));
+
+ if (nonconst) {
+ EXPECT_FALSE(BLI_expr_pylike_is_constant(expr));
+ }
+
+ return expr;
+}
+
+static void verify_eval_result(ExprPyLike_Parsed *expr, double x, double value)
+{
+ double result;
+ eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, &x, 1, &result);
+
+ EXPECT_EQ(status, EXPR_PYLIKE_SUCCESS);
+ EXPECT_EQ(result, value);
+}
+
+static void expr_pylike_eval_test(const char *str, double x, double value)
+{
+ ExprPyLike_Parsed *expr = parse_for_eval(str, true);
+ verify_eval_result(expr, x, value);
+ BLI_expr_pylike_free(expr);
+}
+
+static void expr_pylike_error_test(const char *str, double x, eExprPyLike_EvalStatus error)
+{
+ ExprPyLike_Parsed *expr = parse_for_eval(str, false);
+
+ double result;
+ eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, &x, 1, &result);
+
+ EXPECT_EQ(status, error);
+
+ BLI_expr_pylike_free(expr);
+}
+
+#define TEST_PARSE_FAIL(name, str) \
+ TEST(expr_pylike, ParseFail_##name) \
+ { \
+ expr_pylike_parse_fail_test(str); \
+ }
+
+TEST_PARSE_FAIL(Empty, "")
+TEST_PARSE_FAIL(ConstHex, "0x0")
+TEST_PARSE_FAIL(ConstOctal, "01")
+TEST_PARSE_FAIL(Tail, "0 0")
+TEST_PARSE_FAIL(ConstFloatExp, "0.5e+")
+TEST_PARSE_FAIL(BadId, "Pi")
+TEST_PARSE_FAIL(BadArgCount0, "sqrt")
+TEST_PARSE_FAIL(BadArgCount1, "sqrt()")
+TEST_PARSE_FAIL(BadArgCount2, "sqrt(1,2)")
+TEST_PARSE_FAIL(BadArgCount3, "pi()")
+TEST_PARSE_FAIL(BadArgCount4, "max()")
+TEST_PARSE_FAIL(BadArgCount5, "min()")
+
+TEST_PARSE_FAIL(Truncated1, "(1+2")
+TEST_PARSE_FAIL(Truncated2, "1 if 2")
+TEST_PARSE_FAIL(Truncated3, "1 if 2 else")
+TEST_PARSE_FAIL(Truncated4, "1 < 2 <")
+TEST_PARSE_FAIL(Truncated5, "1 +")
+TEST_PARSE_FAIL(Truncated6, "1 *")
+TEST_PARSE_FAIL(Truncated7, "1 and")
+TEST_PARSE_FAIL(Truncated8, "1 or")
+TEST_PARSE_FAIL(Truncated9, "sqrt(1")
+TEST_PARSE_FAIL(Truncated10, "fmod(1,")
+
+/* Constant expression with working constant folding */
+#define TEST_CONST(name, str, value) \
+ TEST(expr_pylike, Const_##name) \
+ { \
+ expr_pylike_const_test(str, value, true); \
+ }
+
+/* Constant expression but constant folding is not supported */
+#define TEST_RESULT(name, str, value) \
+ TEST(expr_pylike, Result_##name) \
+ { \
+ expr_pylike_const_test(str, value, false); \
+ }
+
+/* Expression with an argument */
+#define TEST_EVAL(name, str, x, value) \
+ TEST(expr_pylike, Eval_##name) \
+ { \
+ expr_pylike_eval_test(str, x, value); \
+ }
+
+TEST_CONST(Zero, "0", 0.0)
+TEST_CONST(Zero2, "00", 0.0)
+TEST_CONST(One, "1", 1.0)
+TEST_CONST(OneF, "1.0", 1.0)
+TEST_CONST(OneF2, "1.", 1.0)
+TEST_CONST(OneE, "1e0", 1.0)
+TEST_CONST(TenE, "1.e+1", 10.0)
+TEST_CONST(Half, ".5", 0.5)
+
+TEST_CONST(Pi, "pi", M_PI)
+TEST_CONST(True, "True", TRUE_VAL)
+TEST_CONST(False, "False", FALSE_VAL)
+
+TEST_CONST(Sqrt, "sqrt(4)", 2.0)
+TEST_EVAL(Sqrt, "sqrt(x)", 4.0, 2.0)
+
+TEST_CONST(FMod, "fmod(3.5, 2)", 1.5)
+TEST_EVAL(FMod, "fmod(x, 2)", 3.5, 1.5)
+
+TEST_CONST(Pow, "pow(4, 0.5)", 2.0)
+TEST_EVAL(Pow, "pow(4, x)", 0.5, 2.0)
+
+TEST_CONST(Log2_1, "log(4, 2)", 2.0)
+
+TEST_CONST(Round1, "round(-0.5)", -1.0)
+TEST_CONST(Round2, "round(-0.4)", 0.0)
+TEST_CONST(Round3, "round(0.4)", 0.0)
+TEST_CONST(Round4, "round(0.5)", 1.0)
+
+TEST_CONST(Clamp1, "clamp(-0.1)", 0.0)
+TEST_CONST(Clamp2, "clamp(0.5)", 0.5)
+TEST_CONST(Clamp3, "clamp(1.5)", 1.0)
+TEST_CONST(Clamp4, "clamp(0.5, 0.2, 0.3)", 0.3)
+TEST_CONST(Clamp5, "clamp(0.0, 0.2, 0.3)", 0.2)
+
+TEST_CONST(Lerp1, "lerp(-10,10,-1)", -30.0)
+TEST_CONST(Lerp2, "lerp(-10,10,0.25)", -5.0)
+TEST_CONST(Lerp3, "lerp(-10,10,1)", 10.0)
+TEST_EVAL(Lerp1, "lerp(-10,10,x)", 0, -10.0)
+TEST_EVAL(Lerp2, "lerp(-10,10,x)", 0.75, 5.0)
+
+TEST_CONST(Smoothstep1, "smoothstep(-10,10,-20)", 0.0)
+TEST_CONST(Smoothstep2, "smoothstep(-10,10,-10)", 0.0)
+TEST_CONST(Smoothstep3, "smoothstep(-10,10,10)", 1.0)
+TEST_CONST(Smoothstep4, "smoothstep(-10,10,20)", 1.0)
+TEST_CONST(Smoothstep5, "smoothstep(-10,10,-5)", 0.15625)
+TEST_EVAL(Smoothstep1, "smoothstep(-10,10,x)", 5, 0.84375)
+
+TEST_RESULT(Min1, "min(3,1,2)", 1.0)
+TEST_RESULT(Max1, "max(3,1,2)", 3.0)
+TEST_RESULT(Min2, "min(1,2,3)", 1.0)
+TEST_RESULT(Max2, "max(1,2,3)", 3.0)
+TEST_RESULT(Min3, "min(2,3,1)", 1.0)
+TEST_RESULT(Max3, "max(2,3,1)", 3.0)
+
+TEST_CONST(UnaryPlus, "+1", 1.0)
+
+TEST_CONST(UnaryMinus, "-1", -1.0)
+TEST_EVAL(UnaryMinus, "-x", 1.0, -1.0)
+
+TEST_CONST(BinaryPlus, "1+2", 3.0)
+TEST_EVAL(BinaryPlus, "x+2", 1, 3.0)
+
+TEST_CONST(BinaryMinus, "1-2", -1.0)
+TEST_EVAL(BinaryMinus, "1-x", 2, -1.0)
+
+TEST_CONST(BinaryMul, "2*3", 6.0)
+TEST_EVAL(BinaryMul, "x*3", 2, 6.0)
+
+TEST_CONST(BinaryDiv, "3/2", 1.5)
+TEST_EVAL(BinaryDiv, "3/x", 2, 1.5)
+
+TEST_CONST(Arith1, "1 + -2 * 3", -5.0)
+TEST_CONST(Arith2, "(1 + -2) * 3", -3.0)
+TEST_CONST(Arith3, "-1 + 2 * 3", 5.0)
+TEST_CONST(Arith4, "3 * (-2 + 1)", -3.0)
+
+TEST_EVAL(Arith1, "1 + -x * 3", 2, -5.0)
+
+TEST_CONST(Eq1, "1 == 1.0", TRUE_VAL)
+TEST_CONST(Eq2, "1 == 2.0", FALSE_VAL)
+TEST_CONST(Eq3, "True == 1", TRUE_VAL)
+TEST_CONST(Eq4, "False == 0", TRUE_VAL)
+
+TEST_EVAL(Eq1, "1 == x", 1.0, TRUE_VAL)
+TEST_EVAL(Eq2, "1 == x", 2.0, FALSE_VAL)
+
+TEST_CONST(NEq1, "1 != 1.0", FALSE_VAL)
+TEST_CONST(NEq2, "1 != 2.0", TRUE_VAL)
+
+TEST_EVAL(NEq1, "1 != x", 1.0, FALSE_VAL)
+TEST_EVAL(NEq2, "1 != x", 2.0, TRUE_VAL)
+
+TEST_CONST(Lt1, "1 < 1", FALSE_VAL)
+TEST_CONST(Lt2, "1 < 2", TRUE_VAL)
+TEST_CONST(Lt3, "2 < 1", FALSE_VAL)
+
+TEST_CONST(Le1, "1 <= 1", TRUE_VAL)
+TEST_CONST(Le2, "1 <= 2", TRUE_VAL)
+TEST_CONST(Le3, "2 <= 1", FALSE_VAL)
+
+TEST_CONST(Gt1, "1 > 1", FALSE_VAL)
+TEST_CONST(Gt2, "1 > 2", FALSE_VAL)
+TEST_CONST(Gt3, "2 > 1", TRUE_VAL)
+
+TEST_CONST(Ge1, "1 >= 1", TRUE_VAL)
+TEST_CONST(Ge2, "1 >= 2", FALSE_VAL)
+TEST_CONST(Ge3, "2 >= 1", TRUE_VAL)
+
+TEST_CONST(Cmp1, "3 == 1 + 2", TRUE_VAL)
+
+TEST_EVAL(Cmp1, "3 == x + 2", 1, TRUE_VAL)
+TEST_EVAL(Cmp1b, "3 == x + 2", 1.5, FALSE_VAL)
+
+TEST_RESULT(CmpChain1, "1 < 2 < 3", TRUE_VAL)
+TEST_RESULT(CmpChain2, "1 < 2 == 2", TRUE_VAL)
+TEST_RESULT(CmpChain3, "1 < 2 > -1", TRUE_VAL)
+TEST_RESULT(CmpChain4, "1 < 2 < 2 < 3", FALSE_VAL)
+TEST_RESULT(CmpChain5, "1 < 2 <= 2 < 3", TRUE_VAL)
+
+TEST_EVAL(CmpChain1a, "1 < x < 3", 2, TRUE_VAL)
+TEST_EVAL(CmpChain1b, "1 < x < 3", 1, FALSE_VAL)
+TEST_EVAL(CmpChain1c, "1 < x < 3", 3, FALSE_VAL)
+
+TEST_CONST(Not1, "not 2", FALSE_VAL)
+TEST_CONST(Not2, "not 0", TRUE_VAL)
+TEST_CONST(Not3, "not not 2", TRUE_VAL)
+
+TEST_EVAL(Not1, "not x", 2, FALSE_VAL)
+TEST_EVAL(Not2, "not x", 0, TRUE_VAL)
+
+TEST_RESULT(And1, "2 and 3", 3.0)
+TEST_RESULT(And2, "0 and 3", 0.0)
+
+TEST_RESULT(Or1, "2 or 3", 2.0)
+TEST_RESULT(Or2, "0 or 3", 3.0)
+
+TEST_RESULT(Bool1, "2 or 3 and 4", 2.0)
+TEST_RESULT(Bool2, "not 2 or 3 and 4", 4.0)
+
+TEST(expr_pylike, Eval_Ternary1)
+{
+ ExprPyLike_Parsed *expr = parse_for_eval("x / 2 if x < 4 else x - 2 if x < 8 else x*2 - 12",
+ true);
+
+ for (int i = 0; i <= 10; i++) {
+ double x = i;
+ double v = (x < 4) ? (x / 2) : (x < 8) ? (x - 2) : (x * 2 - 12);
+
+ verify_eval_result(expr, x, v);
+ }
+
+ BLI_expr_pylike_free(expr);
+}
+
+TEST(expr_pylike, MultipleArgs)
+{
+ const char *names[3] = {"x", "y", "x"};
+ double values[3] = {1.0, 2.0, 3.0};
+
+ ExprPyLike_Parsed *expr = BLI_expr_pylike_parse("x*10 + y", names, ARRAY_SIZE(names));
+
+ EXPECT_TRUE(BLI_expr_pylike_is_valid(expr));
+
+ double result;
+ eExprPyLike_EvalStatus status = BLI_expr_pylike_eval(expr, values, 3, &result);
+
+ EXPECT_EQ(status, EXPR_PYLIKE_SUCCESS);
+ EXPECT_EQ(result, 32.0);
+
+ BLI_expr_pylike_free(expr);
+}
+
+TEST(expr_pylike, UsingParam)
+{
+ const char *names[3] = {"x", "y", "z"};
+
+ ExprPyLike_Parsed *expr = BLI_expr_pylike_parse("x + z", names, ARRAY_SIZE(names));
+
+ EXPECT_TRUE(BLI_expr_pylike_is_using_param(expr, 0));
+ EXPECT_FALSE(BLI_expr_pylike_is_using_param(expr, 1));
+ EXPECT_TRUE(BLI_expr_pylike_is_using_param(expr, 2));
+
+ BLI_expr_pylike_free(expr);
+}
+
+#define TEST_ERROR(name, str, x, code) \
+ TEST(expr_pylike, Error_##name) \
+ { \
+ expr_pylike_error_test(str, x, code); \
+ }
+
+TEST_ERROR(DivZero1, "0 / 0", 0.0, EXPR_PYLIKE_MATH_ERROR)
+TEST_ERROR(DivZero2, "1 / 0", 0.0, EXPR_PYLIKE_DIV_BY_ZERO)
+TEST_ERROR(DivZero3, "1 / x", 0.0, EXPR_PYLIKE_DIV_BY_ZERO)
+TEST_ERROR(DivZero4, "1 / x", 1.0, EXPR_PYLIKE_SUCCESS)
+
+TEST_ERROR(SqrtDomain1, "sqrt(-1)", 0.0, EXPR_PYLIKE_MATH_ERROR)
+TEST_ERROR(SqrtDomain2, "sqrt(x)", -1.0, EXPR_PYLIKE_MATH_ERROR)
+TEST_ERROR(SqrtDomain3, "sqrt(x)", 0.0, EXPR_PYLIKE_SUCCESS)
+
+TEST_ERROR(PowDomain1, "pow(-1, 0.5)", 0.0, EXPR_PYLIKE_MATH_ERROR)
+TEST_ERROR(PowDomain2, "pow(-1, x)", 0.5, EXPR_PYLIKE_MATH_ERROR)
+TEST_ERROR(PowDomain3, "pow(-1, x)", 2.0, EXPR_PYLIKE_SUCCESS)
+
+TEST_ERROR(Mixed1, "sqrt(x) + 1 / max(0, x)", -1.0, EXPR_PYLIKE_MATH_ERROR)
+TEST_ERROR(Mixed2, "sqrt(x) + 1 / max(0, x)", 0.0, EXPR_PYLIKE_DIV_BY_ZERO)
+TEST_ERROR(Mixed3, "sqrt(x) + 1 / max(0, x)", 1.0, EXPR_PYLIKE_SUCCESS)
+
+TEST(expr_pylike, Error_Invalid)
+{
+ ExprPyLike_Parsed *expr = BLI_expr_pylike_parse("", NULL, 0);
+ double result;
+
+ EXPECT_EQ(BLI_expr_pylike_eval(expr, NULL, 0, &result), EXPR_PYLIKE_INVALID);
+
+ BLI_expr_pylike_free(expr);
+}
+
+TEST(expr_pylike, Error_ArgumentCount)
+{
+ ExprPyLike_Parsed *expr = parse_for_eval("x", false);
+ double result;
+
+ EXPECT_EQ(BLI_expr_pylike_eval(expr, NULL, 0, &result), EXPR_PYLIKE_FATAL_ERROR);
+
+ BLI_expr_pylike_free(expr);
+}
diff --git a/source/blender/blenlib/tests/BLI_ghash_test.cc b/source/blender/blenlib/tests/BLI_ghash_test.cc
new file mode 100644
index 00000000000..fcc0512cb9e
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_ghash_test.cc
@@ -0,0 +1,209 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#define GHASH_INTERNAL_API
+
+#include "BLI_ghash.h"
+#include "BLI_rand.h"
+#include "BLI_utildefines.h"
+
+#define TESTCASE_SIZE 10000
+
+/* Only keeping this in case here, for now. */
+#define PRINTF_GHASH_STATS(_gh) \
+ { \
+ double q, lf, var, pempty, poverloaded; \
+ int bigb; \
+ q = BLI_ghash_calc_quality_ex((_gh), &lf, &var, &pempty, &poverloaded, &bigb); \
+ printf( \
+ "GHash stats (%d entries):\n\t" \
+ "Quality (the lower the better): %f\n\tVariance (the lower the better): %f\n\tLoad: " \
+ "%f\n\t" \
+ "Empty buckets: %.2f%%\n\tOverloaded buckets: %.2f%% (biggest bucket: %d)\n", \
+ BLI_ghash_len(_gh), \
+ q, \
+ var, \
+ lf, \
+ pempty * 100.0, \
+ poverloaded * 100.0, \
+ bigb); \
+ } \
+ void(0)
+
+/* Note: for pure-ghash testing, nature of the keys and data have absolutely no importance! So here
+ * we just use mere random integers stored in pointers. */
+
+static void init_keys(unsigned int keys[TESTCASE_SIZE], const int seed)
+{
+ RNG *rng = BLI_rng_new(seed);
+ unsigned int *k;
+ int i;
+
+ for (i = 0, k = keys; i < TESTCASE_SIZE;) {
+ /* Risks of collision are low, but they do exist.
+ * And we cannot use a GSet, since we test that here! */
+ int j, t = BLI_rng_get_uint(rng);
+ for (j = i; j--;) {
+ if (keys[j] == t) {
+ continue;
+ }
+ }
+ *k = t;
+ i++;
+ k++;
+ }
+ BLI_rng_free(rng);
+}
+
+/* Here we simply insert and then lookup all keys, ensuring we do get back the expected stored
+ * 'data'. */
+TEST(ghash, InsertLookup)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__);
+ unsigned int keys[TESTCASE_SIZE], *k;
+ int i;
+
+ init_keys(keys, 0);
+
+ for (i = TESTCASE_SIZE, k = keys; i--; k++) {
+ BLI_ghash_insert(ghash, POINTER_FROM_UINT(*k), POINTER_FROM_UINT(*k));
+ }
+
+ EXPECT_EQ(BLI_ghash_len(ghash), TESTCASE_SIZE);
+
+ for (i = TESTCASE_SIZE, k = keys; i--; k++) {
+ void *v = BLI_ghash_lookup(ghash, POINTER_FROM_UINT(*k));
+ EXPECT_EQ(POINTER_AS_UINT(v), *k);
+ }
+
+ BLI_ghash_free(ghash, NULL, NULL);
+}
+
+/* Here we simply insert and then remove all keys, ensuring we do get an empty, unshrinked ghash.
+ */
+TEST(ghash, InsertRemove)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__);
+ unsigned int keys[TESTCASE_SIZE], *k;
+ int i, bkt_size;
+
+ init_keys(keys, 10);
+
+ for (i = TESTCASE_SIZE, k = keys; i--; k++) {
+ BLI_ghash_insert(ghash, POINTER_FROM_UINT(*k), POINTER_FROM_UINT(*k));
+ }
+
+ EXPECT_EQ(BLI_ghash_len(ghash), TESTCASE_SIZE);
+ bkt_size = BLI_ghash_buckets_len(ghash);
+
+ for (i = TESTCASE_SIZE, k = keys; i--; k++) {
+ void *v = BLI_ghash_popkey(ghash, POINTER_FROM_UINT(*k), NULL);
+ EXPECT_EQ(POINTER_AS_UINT(v), *k);
+ }
+
+ EXPECT_EQ(BLI_ghash_len(ghash), 0);
+ EXPECT_EQ(BLI_ghash_buckets_len(ghash), bkt_size);
+
+ BLI_ghash_free(ghash, NULL, NULL);
+}
+
+/* Same as above, but this time we allow ghash to shrink. */
+TEST(ghash, InsertRemoveShrink)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__);
+ unsigned int keys[TESTCASE_SIZE], *k;
+ int i, bkt_size;
+
+ BLI_ghash_flag_set(ghash, GHASH_FLAG_ALLOW_SHRINK);
+ init_keys(keys, 20);
+
+ for (i = TESTCASE_SIZE, k = keys; i--; k++) {
+ BLI_ghash_insert(ghash, POINTER_FROM_UINT(*k), POINTER_FROM_UINT(*k));
+ }
+
+ EXPECT_EQ(BLI_ghash_len(ghash), TESTCASE_SIZE);
+ bkt_size = BLI_ghash_buckets_len(ghash);
+
+ for (i = TESTCASE_SIZE, k = keys; i--; k++) {
+ void *v = BLI_ghash_popkey(ghash, POINTER_FROM_UINT(*k), NULL);
+ EXPECT_EQ(POINTER_AS_UINT(v), *k);
+ }
+
+ EXPECT_EQ(BLI_ghash_len(ghash), 0);
+ EXPECT_LT(BLI_ghash_buckets_len(ghash), bkt_size);
+
+ BLI_ghash_free(ghash, NULL, NULL);
+}
+
+/* Check copy. */
+TEST(ghash, Copy)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__);
+ GHash *ghash_copy;
+ unsigned int keys[TESTCASE_SIZE], *k;
+ int i;
+
+ init_keys(keys, 30);
+
+ for (i = TESTCASE_SIZE, k = keys; i--; k++) {
+ BLI_ghash_insert(ghash, POINTER_FROM_UINT(*k), POINTER_FROM_UINT(*k));
+ }
+
+ EXPECT_EQ(BLI_ghash_len(ghash), TESTCASE_SIZE);
+
+ ghash_copy = BLI_ghash_copy(ghash, NULL, NULL);
+
+ EXPECT_EQ(BLI_ghash_len(ghash_copy), TESTCASE_SIZE);
+ EXPECT_EQ(BLI_ghash_buckets_len(ghash_copy), BLI_ghash_buckets_len(ghash));
+
+ for (i = TESTCASE_SIZE, k = keys; i--; k++) {
+ void *v = BLI_ghash_lookup(ghash_copy, POINTER_FROM_UINT(*k));
+ EXPECT_EQ(POINTER_AS_UINT(v), *k);
+ }
+
+ BLI_ghash_free(ghash, NULL, NULL);
+ BLI_ghash_free(ghash_copy, NULL, NULL);
+}
+
+/* Check pop. */
+TEST(ghash, Pop)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__);
+ unsigned int keys[TESTCASE_SIZE], *k;
+ int i;
+
+ BLI_ghash_flag_set(ghash, GHASH_FLAG_ALLOW_SHRINK);
+ init_keys(keys, 30);
+
+ for (i = TESTCASE_SIZE, k = keys; i--; k++) {
+ BLI_ghash_insert(ghash, POINTER_FROM_UINT(*k), POINTER_FROM_UINT(*k));
+ }
+
+ EXPECT_EQ(BLI_ghash_len(ghash), TESTCASE_SIZE);
+
+ GHashIterState pop_state = {0};
+
+ for (i = TESTCASE_SIZE / 2; i--;) {
+ void *k, *v;
+ bool success = BLI_ghash_pop(ghash, &pop_state, &k, &v);
+ EXPECT_EQ(k, v);
+ EXPECT_TRUE(success);
+
+ if (i % 2) {
+ BLI_ghash_insert(ghash, POINTER_FROM_UINT(i * 4), POINTER_FROM_UINT(i * 4));
+ }
+ }
+
+ EXPECT_EQ(BLI_ghash_len(ghash), (TESTCASE_SIZE - TESTCASE_SIZE / 2 + TESTCASE_SIZE / 4));
+
+ {
+ void *k, *v;
+ while (BLI_ghash_pop(ghash, &pop_state, &k, &v)) {
+ EXPECT_EQ(k, v);
+ }
+ }
+ EXPECT_EQ(BLI_ghash_len(ghash), 0);
+
+ BLI_ghash_free(ghash, NULL, NULL);
+}
diff --git a/source/blender/blenlib/tests/BLI_hash_mm2a_test.cc b/source/blender/blenlib/tests/BLI_hash_mm2a_test.cc
new file mode 100644
index 00000000000..c7bea8e15de
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_hash_mm2a_test.cc
@@ -0,0 +1,74 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_hash_mm2a.h"
+
+/* Note: Reference results are taken from reference implementation
+ * (cpp code, CMurmurHash2A variant):
+ * https://smhasher.googlecode.com/svn-history/r130/trunk/MurmurHash2.cpp
+ */
+
+TEST(hash_mm2a, MM2ABasic)
+{
+ BLI_HashMurmur2A mm2;
+
+ const char *data = "Blender";
+
+ BLI_hash_mm2a_init(&mm2, 0);
+ BLI_hash_mm2a_add(&mm2, (const unsigned char *)data, strlen(data));
+#ifdef __LITTLE_ENDIAN__
+ EXPECT_EQ(BLI_hash_mm2a_end(&mm2), 1633988145);
+#else
+ EXPECT_EQ(BLI_hash_mm2a_end(&mm2), 959283772);
+#endif
+}
+
+TEST(hash_mm2a, MM2AConcatenateStrings)
+{
+ BLI_HashMurmur2A mm2;
+ uint32_t hash;
+
+ const char *data1 = "Blender";
+ const char *data2 = " is ";
+ const char *data3 = "FaNtAsTiC";
+ const char *data123 = "Blender is FaNtAsTiC";
+
+ BLI_hash_mm2a_init(&mm2, 0);
+ BLI_hash_mm2a_add(&mm2, (const unsigned char *)data1, strlen(data1));
+ BLI_hash_mm2a_add(&mm2, (const unsigned char *)data2, strlen(data2));
+ BLI_hash_mm2a_add(&mm2, (const unsigned char *)data3, strlen(data3));
+ hash = BLI_hash_mm2a_end(&mm2);
+ BLI_hash_mm2a_init(&mm2, 0);
+ BLI_hash_mm2a_add(&mm2, (const unsigned char *)data123, strlen(data123));
+#ifdef __LITTLE_ENDIAN__
+ EXPECT_EQ(hash, 1545105348);
+#else
+ EXPECT_EQ(hash, 2604964730);
+#endif
+ EXPECT_EQ(BLI_hash_mm2a_end(&mm2), hash);
+}
+
+TEST(hash_mm2a, MM2AIntegers)
+{
+ BLI_HashMurmur2A mm2;
+ uint32_t hash;
+
+ const int ints[4] = {1, 2, 3, 4};
+
+ BLI_hash_mm2a_init(&mm2, 0);
+ BLI_hash_mm2a_add_int(&mm2, ints[0]);
+ BLI_hash_mm2a_add_int(&mm2, ints[1]);
+ BLI_hash_mm2a_add_int(&mm2, ints[2]);
+ BLI_hash_mm2a_add_int(&mm2, ints[3]);
+ hash = BLI_hash_mm2a_end(&mm2);
+ BLI_hash_mm2a_init(&mm2, 0);
+ BLI_hash_mm2a_add(&mm2, (const unsigned char *)ints, sizeof(ints));
+ /* Yes, same hash here on little and big endian. */
+#ifdef __LITTLE_ENDIAN__
+ EXPECT_EQ(hash, 405493096);
+#else
+ EXPECT_EQ(hash, 405493096);
+#endif
+ EXPECT_EQ(BLI_hash_mm2a_end(&mm2), hash);
+}
diff --git a/source/blender/blenlib/tests/BLI_heap_simple_test.cc b/source/blender/blenlib/tests/BLI_heap_simple_test.cc
new file mode 100644
index 00000000000..e717a6e2653
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_heap_simple_test.cc
@@ -0,0 +1,121 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_compiler_attrs.h"
+#include "BLI_heap_simple.h"
+#include "BLI_rand.h"
+#include "BLI_sys_types.h"
+#include "BLI_utildefines.h"
+
+#define SIZE 1024
+
+static void range_fl(float *array_tar, const int size)
+{
+ float *array_pt = array_tar + (size - 1);
+ int i = size;
+ while (i--) {
+ *(array_pt--) = (float)i;
+ }
+}
+
+TEST(heap, SimpleEmpty)
+{
+ HeapSimple *heap;
+
+ heap = BLI_heapsimple_new();
+ EXPECT_TRUE(BLI_heapsimple_is_empty(heap));
+ EXPECT_EQ(BLI_heapsimple_len(heap), 0);
+ BLI_heapsimple_free(heap, NULL);
+}
+
+TEST(heap, SimpleOne)
+{
+ HeapSimple *heap;
+ const char *in = "test";
+
+ heap = BLI_heapsimple_new();
+
+ BLI_heapsimple_insert(heap, 0.0f, (void *)in);
+ EXPECT_FALSE(BLI_heapsimple_is_empty(heap));
+ EXPECT_EQ(BLI_heapsimple_len(heap), 1);
+ EXPECT_EQ(in, BLI_heapsimple_pop_min(heap));
+ EXPECT_TRUE(BLI_heapsimple_is_empty(heap));
+ EXPECT_EQ(BLI_heapsimple_len(heap), 0);
+ BLI_heapsimple_free(heap, NULL);
+}
+
+TEST(heap, SimpleRange)
+{
+ const int items_total = SIZE;
+ HeapSimple *heap = BLI_heapsimple_new();
+ for (int in = 0; in < items_total; in++) {
+ BLI_heapsimple_insert(heap, (float)in, POINTER_FROM_INT(in));
+ }
+ for (int out_test = 0; out_test < items_total; out_test++) {
+ EXPECT_EQ(out_test, POINTER_AS_INT(BLI_heapsimple_pop_min(heap)));
+ }
+ EXPECT_TRUE(BLI_heapsimple_is_empty(heap));
+ BLI_heapsimple_free(heap, NULL);
+}
+
+TEST(heap, SimpleRangeReverse)
+{
+ const int items_total = SIZE;
+ HeapSimple *heap = BLI_heapsimple_new();
+ for (int in = 0; in < items_total; in++) {
+ BLI_heapsimple_insert(heap, (float)-in, POINTER_FROM_INT(-in));
+ }
+ for (int out_test = items_total - 1; out_test >= 0; out_test--) {
+ EXPECT_EQ(-out_test, POINTER_AS_INT(BLI_heapsimple_pop_min(heap)));
+ }
+ EXPECT_TRUE(BLI_heapsimple_is_empty(heap));
+ BLI_heapsimple_free(heap, NULL);
+}
+
+TEST(heap, SimpleDuplicates)
+{
+ const int items_total = SIZE;
+ HeapSimple *heap = BLI_heapsimple_new();
+ for (int in = 0; in < items_total; in++) {
+ BLI_heapsimple_insert(heap, 1.0f, 0);
+ }
+ for (int out_test = 0; out_test < items_total; out_test++) {
+ EXPECT_EQ(0, POINTER_AS_INT(BLI_heapsimple_pop_min(heap)));
+ }
+ EXPECT_TRUE(BLI_heapsimple_is_empty(heap));
+ BLI_heapsimple_free(heap, NULL);
+}
+
+static void random_heapsimple_helper(const int items_total, const int random_seed)
+{
+ HeapSimple *heap = BLI_heapsimple_new();
+ float *values = (float *)MEM_mallocN(sizeof(float) * items_total, __func__);
+ range_fl(values, items_total);
+ BLI_array_randomize(values, sizeof(float), items_total, random_seed);
+ for (int i = 0; i < items_total; i++) {
+ BLI_heapsimple_insert(heap, values[i], POINTER_FROM_INT((int)values[i]));
+ }
+ for (int out_test = 0; out_test < items_total; out_test++) {
+ EXPECT_EQ(out_test, POINTER_AS_INT(BLI_heapsimple_pop_min(heap)));
+ }
+ EXPECT_TRUE(BLI_heapsimple_is_empty(heap));
+ BLI_heapsimple_free(heap, NULL);
+ MEM_freeN(values);
+}
+
+TEST(heap, SimpleRand1)
+{
+ random_heapsimple_helper(1, 1234);
+}
+TEST(heap, SimpleRand2)
+{
+ random_heapsimple_helper(2, 1234);
+}
+TEST(heap, SimpleRand100)
+{
+ random_heapsimple_helper(100, 4321);
+}
diff --git a/source/blender/blenlib/tests/BLI_heap_test.cc b/source/blender/blenlib/tests/BLI_heap_test.cc
new file mode 100644
index 00000000000..87e68c175a2
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_heap_test.cc
@@ -0,0 +1,207 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_compiler_attrs.h"
+#include "BLI_heap.h"
+#include "BLI_rand.h"
+#include "BLI_utildefines.h"
+
+#define SIZE 1024
+
+static void range_fl(float *array_tar, const int size)
+{
+ float *array_pt = array_tar + (size - 1);
+ int i = size;
+ while (i--) {
+ *(array_pt--) = (float)i;
+ }
+}
+
+TEST(heap, Empty)
+{
+ Heap *heap;
+
+ heap = BLI_heap_new();
+ EXPECT_TRUE(BLI_heap_is_empty(heap));
+ EXPECT_EQ(BLI_heap_len(heap), 0);
+ BLI_heap_free(heap, NULL);
+}
+
+TEST(heap, One)
+{
+ Heap *heap;
+ const char *in = "test";
+
+ heap = BLI_heap_new();
+
+ BLI_heap_insert(heap, 0.0f, (void *)in);
+ EXPECT_FALSE(BLI_heap_is_empty(heap));
+ EXPECT_EQ(BLI_heap_len(heap), 1);
+ EXPECT_EQ(in, BLI_heap_pop_min(heap));
+ EXPECT_TRUE(BLI_heap_is_empty(heap));
+ EXPECT_EQ(BLI_heap_len(heap), 0);
+ BLI_heap_free(heap, NULL);
+}
+
+TEST(heap, Range)
+{
+ const int items_total = SIZE;
+ Heap *heap = BLI_heap_new();
+ for (int in = 0; in < items_total; in++) {
+ BLI_heap_insert(heap, (float)in, POINTER_FROM_INT(in));
+ }
+ for (int out_test = 0; out_test < items_total; out_test++) {
+ EXPECT_EQ(out_test, POINTER_AS_INT(BLI_heap_pop_min(heap)));
+ }
+ EXPECT_TRUE(BLI_heap_is_empty(heap));
+ BLI_heap_free(heap, NULL);
+}
+
+TEST(heap, RangeReverse)
+{
+ const int items_total = SIZE;
+ Heap *heap = BLI_heap_new();
+ for (int in = 0; in < items_total; in++) {
+ BLI_heap_insert(heap, (float)-in, POINTER_FROM_INT(-in));
+ }
+ for (int out_test = items_total - 1; out_test >= 0; out_test--) {
+ EXPECT_EQ(-out_test, POINTER_AS_INT(BLI_heap_pop_min(heap)));
+ }
+ EXPECT_TRUE(BLI_heap_is_empty(heap));
+ BLI_heap_free(heap, NULL);
+}
+
+TEST(heap, RangeRemove)
+{
+ const int items_total = SIZE;
+ Heap *heap = BLI_heap_new();
+ HeapNode **nodes = (HeapNode **)MEM_mallocN(sizeof(HeapNode *) * items_total, __func__);
+ for (int in = 0; in < items_total; in++) {
+ nodes[in] = BLI_heap_insert(heap, (float)in, POINTER_FROM_INT(in));
+ }
+ for (int i = 0; i < items_total; i += 2) {
+ BLI_heap_remove(heap, nodes[i]);
+ nodes[i] = NULL;
+ }
+ for (int out_test = 1; out_test < items_total; out_test += 2) {
+ EXPECT_EQ(out_test, POINTER_AS_INT(BLI_heap_pop_min(heap)));
+ }
+ EXPECT_TRUE(BLI_heap_is_empty(heap));
+ BLI_heap_free(heap, NULL);
+ MEM_freeN(nodes);
+}
+
+TEST(heap, Duplicates)
+{
+ const int items_total = SIZE;
+ Heap *heap = BLI_heap_new();
+ for (int in = 0; in < items_total; in++) {
+ BLI_heap_insert(heap, 1.0f, 0);
+ }
+ for (int out_test = 0; out_test < items_total; out_test++) {
+ EXPECT_EQ(0, POINTER_AS_INT(BLI_heap_pop_min(heap)));
+ }
+ EXPECT_TRUE(BLI_heap_is_empty(heap));
+ BLI_heap_free(heap, NULL);
+}
+
+static void random_heap_helper(const int items_total, const int random_seed)
+{
+ Heap *heap = BLI_heap_new();
+ float *values = (float *)MEM_mallocN(sizeof(float) * items_total, __func__);
+ range_fl(values, items_total);
+ BLI_array_randomize(values, sizeof(float), items_total, random_seed);
+ for (int i = 0; i < items_total; i++) {
+ BLI_heap_insert(heap, values[i], POINTER_FROM_INT((int)values[i]));
+ }
+ for (int out_test = 0; out_test < items_total; out_test++) {
+ EXPECT_EQ(out_test, POINTER_AS_INT(BLI_heap_pop_min(heap)));
+ }
+ EXPECT_TRUE(BLI_heap_is_empty(heap));
+ BLI_heap_free(heap, NULL);
+ MEM_freeN(values);
+}
+
+TEST(heap, Rand1)
+{
+ random_heap_helper(1, 1234);
+}
+TEST(heap, Rand2)
+{
+ random_heap_helper(2, 1234);
+}
+TEST(heap, Rand100)
+{
+ random_heap_helper(100, 4321);
+}
+
+TEST(heap, ReInsertSimple)
+{
+ const int items_total = SIZE;
+ Heap *heap = BLI_heap_new();
+ HeapNode **nodes = (HeapNode **)MEM_mallocN(sizeof(HeapNode *) * items_total, __func__);
+ for (int in = 0; in < items_total; in++) {
+ nodes[in] = BLI_heap_insert(heap, (float)in, POINTER_FROM_INT(in));
+ }
+ for (int i = 0; i < items_total; i++) {
+ BLI_heap_node_value_update(heap, nodes[i], (float)(items_total + i));
+ }
+
+ for (int out_test = 0; out_test < items_total; out_test++) {
+ EXPECT_EQ(out_test, POINTER_AS_INT(BLI_heap_pop_min(heap)));
+ }
+
+ EXPECT_TRUE(BLI_heap_is_empty(heap));
+ BLI_heap_free(heap, NULL);
+ MEM_freeN(nodes);
+}
+
+static void random_heap_reinsert_helper(const int items_total, const int random_seed)
+{
+ Heap *heap = BLI_heap_new();
+ HeapNode **nodes = (HeapNode **)MEM_mallocN(sizeof(HeapNode *) * items_total, __func__);
+ for (int in = 0; in < items_total; in++) {
+ nodes[in] = BLI_heap_insert(heap, (float)in, POINTER_FROM_INT(in));
+ }
+ BLI_array_randomize(nodes, sizeof(HeapNode *), items_total, random_seed);
+ for (int i = 0; i < items_total; i++) {
+ BLI_heap_node_value_update(heap, nodes[i], (float)i);
+ }
+ EXPECT_TRUE(BLI_heap_is_valid(heap));
+
+ for (int out_test = 0; out_test < items_total; out_test++) {
+ HeapNode *node_top = BLI_heap_top(heap);
+ float out = BLI_heap_node_value(node_top);
+ EXPECT_EQ(out, BLI_heap_top_value(heap));
+ EXPECT_EQ((float)out_test, out);
+ BLI_heap_pop_min(heap);
+ }
+ EXPECT_TRUE(BLI_heap_is_empty(heap));
+ BLI_heap_free(heap, NULL);
+ MEM_freeN(nodes);
+}
+
+TEST(heap, ReInsertRandom1)
+{
+ random_heap_reinsert_helper(1, 1234);
+}
+TEST(heap, ReInsertRandom2)
+{
+ random_heap_reinsert_helper(2, 1234);
+}
+TEST(heap, ReInsertRandom100)
+{
+ random_heap_reinsert_helper(100, 4321);
+}
+TEST(heap, ReInsertRandom1024)
+{
+ random_heap_reinsert_helper(1024, 9876);
+}
+TEST(heap, ReInsertRandom2048)
+{
+ random_heap_reinsert_helper(2048, 5321);
+}
diff --git a/source/blender/blenlib/tests/BLI_kdopbvh_test.cc b/source/blender/blenlib/tests/BLI_kdopbvh_test.cc
new file mode 100644
index 00000000000..2e8032400e3
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_kdopbvh_test.cc
@@ -0,0 +1,134 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+/* TODO: ray intersection, overlap ... etc.*/
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_compiler_attrs.h"
+#include "BLI_kdopbvh.h"
+#include "BLI_math_vector.h"
+#include "BLI_rand.h"
+
+/* -------------------------------------------------------------------- */
+/* Helper Functions */
+
+static void rng_v3_round(float *coords, int coords_len, struct RNG *rng, int round, float scale)
+{
+ for (int i = 0; i < coords_len; i++) {
+ float f = BLI_rng_get_float(rng) * 2.0f - 1.0f;
+ coords[i] = ((float)((int)(f * round)) / (float)round) * scale;
+ }
+}
+
+/* -------------------------------------------------------------------- */
+/* Tests */
+
+TEST(kdopbvh, Empty)
+{
+ BVHTree *tree = BLI_bvhtree_new(0, 0.0, 8, 8);
+ BLI_bvhtree_balance(tree);
+ EXPECT_EQ(0, BLI_bvhtree_get_len(tree));
+ BLI_bvhtree_free(tree);
+}
+
+TEST(kdopbvh, Single)
+{
+ BVHTree *tree = BLI_bvhtree_new(1, 0.0, 8, 8);
+ {
+ float co[3] = {0};
+ BLI_bvhtree_insert(tree, 0, co, 1);
+ }
+
+ EXPECT_EQ(BLI_bvhtree_get_len(tree), 1);
+
+ BLI_bvhtree_balance(tree);
+ BLI_bvhtree_free(tree);
+}
+
+static void optimal_check_callback(void *userdata,
+ int index,
+ const float co[3],
+ BVHTreeNearest *nearest)
+{
+ float(*points)[3] = (float(*)[3])userdata;
+
+ /* BVH_NEAREST_OPTIMAL_ORDER should hit the right node on the first try */
+ EXPECT_EQ(nearest->index, -1);
+ EXPECT_EQ_ARRAY(co, points[index], 3);
+
+ nearest->index = index;
+ nearest->dist_sq = len_squared_v3v3(co, points[index]);
+}
+
+/**
+ * Note that a small epsilon is added to the BVH nodes bounds, even if we pass in zero.
+ * Use rounding to ensure very close nodes don't cause the wrong node to be found as nearest.
+ */
+static void find_nearest_points_test(
+ int points_len, float scale, int round, int random_seed, bool optimal = false)
+{
+ struct RNG *rng = BLI_rng_new(random_seed);
+ BVHTree *tree = BLI_bvhtree_new(points_len, 0.0, 8, 8);
+
+ void *mem = MEM_mallocN(sizeof(float[3]) * points_len, __func__);
+ float(*points)[3] = (float(*)[3])mem;
+
+ for (int i = 0; i < points_len; i++) {
+ rng_v3_round(points[i], 3, rng, round, scale);
+ BLI_bvhtree_insert(tree, i, points[i], 1);
+ }
+ BLI_bvhtree_balance(tree);
+
+ /* first find each point */
+ BVHTree_NearestPointCallback callback = optimal ? optimal_check_callback : NULL;
+ int flags = optimal ? BVH_NEAREST_OPTIMAL_ORDER : 0;
+
+ for (int i = 0; i < points_len; i++) {
+ const int j = BLI_bvhtree_find_nearest_ex(tree, points[i], NULL, callback, points, flags);
+ if (j != i) {
+#if 0
+ const float dist = len_v3v3(points[i], points[j]);
+ if (dist > (1.0f / (float)round)) {
+ printf("%.15f (%d %d)\n", dist, i, j);
+ print_v3_id(points[i]);
+ print_v3_id(points[j]);
+ fflush(stdout);
+ }
+#endif
+ EXPECT_GE(j, 0);
+ EXPECT_LT(j, points_len);
+ EXPECT_EQ_ARRAY(points[i], points[j], 3);
+ }
+ }
+ BLI_bvhtree_free(tree);
+ BLI_rng_free(rng);
+ MEM_freeN(points);
+}
+
+TEST(kdopbvh, FindNearest_1)
+{
+ find_nearest_points_test(1, 1.0, 1000, 1234);
+}
+TEST(kdopbvh, FindNearest_2)
+{
+ find_nearest_points_test(2, 1.0, 1000, 123);
+}
+TEST(kdopbvh, FindNearest_500)
+{
+ find_nearest_points_test(500, 1.0, 1000, 12);
+}
+
+TEST(kdopbvh, OptimalFindNearest_1)
+{
+ find_nearest_points_test(1, 1.0, 1000, 1234, true);
+}
+TEST(kdopbvh, OptimalFindNearest_2)
+{
+ find_nearest_points_test(2, 1.0, 1000, 123, true);
+}
+TEST(kdopbvh, OptimalFindNearest_500)
+{
+ find_nearest_points_test(500, 1.0, 1000, 12, true);
+}
diff --git a/source/blender/blenlib/tests/BLI_linklist_lockfree_test.cc b/source/blender/blenlib/tests/BLI_linklist_lockfree_test.cc
new file mode 100644
index 00000000000..d1a527d57ac
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_linklist_lockfree_test.cc
@@ -0,0 +1,111 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_linklist_lockfree.h"
+#include "BLI_task.h"
+#include "BLI_threads.h"
+#include "BLI_utildefines.h"
+
+TEST(LockfreeLinkList, Init)
+{
+ LockfreeLinkList list;
+ BLI_linklist_lockfree_init(&list);
+ EXPECT_EQ(list.head, &list.dummy_node);
+ EXPECT_EQ(list.tail, &list.dummy_node);
+ BLI_linklist_lockfree_free(&list, NULL);
+}
+
+TEST(LockfreeLinkList, InsertSingle)
+{
+ LockfreeLinkList list;
+ LockfreeLinkNode node;
+ BLI_linklist_lockfree_init(&list);
+ BLI_linklist_lockfree_insert(&list, &node);
+ EXPECT_EQ(list.head, &list.dummy_node);
+ EXPECT_EQ(list.head->next, &node);
+ EXPECT_EQ(list.tail, &node);
+ BLI_linklist_lockfree_free(&list, NULL);
+}
+
+TEST(LockfreeLinkList, InsertMultiple)
+{
+ static const int num_nodes = 128;
+ LockfreeLinkList list;
+ LockfreeLinkNode nodes[num_nodes];
+ BLI_linklist_lockfree_init(&list);
+ /* Insert all the nodes. */
+ for (int i = 0; i < num_nodes; ++i) {
+ BLI_linklist_lockfree_insert(&list, &nodes[i]);
+ }
+ /* Check head and tail. */
+ EXPECT_EQ(list.head, &list.dummy_node);
+ EXPECT_EQ(list.tail, &nodes[num_nodes - 1]);
+ /* Check rest of the nodes. */
+ int node_index = 0;
+ for (LockfreeLinkNode *node = BLI_linklist_lockfree_begin(&list); node != NULL;
+ node = node->next, ++node_index) {
+ EXPECT_EQ(node, &nodes[node_index]);
+ if (node_index != num_nodes - 1) {
+ EXPECT_EQ(node->next, &nodes[node_index + 1]);
+ }
+ }
+ /* Free list. */
+ BLI_linklist_lockfree_free(&list, NULL);
+}
+
+namespace {
+
+struct IndexedNode {
+ IndexedNode *next;
+ int index;
+};
+
+void concurrent_insert(TaskPool *__restrict pool, void *taskdata)
+{
+ LockfreeLinkList *list = (LockfreeLinkList *)BLI_task_pool_user_data(pool);
+ CHECK_NOTNULL(list);
+ IndexedNode *node = (IndexedNode *)MEM_mallocN(sizeof(IndexedNode), "test node");
+ node->index = POINTER_AS_INT(taskdata);
+ BLI_linklist_lockfree_insert(list, (LockfreeLinkNode *)node);
+}
+
+} // namespace
+
+TEST(LockfreeLinkList, InsertMultipleConcurrent)
+{
+ static const int num_nodes = 655360;
+ /* Initialize list. */
+ LockfreeLinkList list;
+ BLI_linklist_lockfree_init(&list);
+ /* Initialize task scheduler and pool. */
+ TaskPool *pool = BLI_task_pool_create_suspended(&list, TASK_PRIORITY_HIGH);
+ /* Push tasks to the pool. */
+ for (int i = 0; i < num_nodes; ++i) {
+ BLI_task_pool_push(pool, concurrent_insert, POINTER_FROM_INT(i), false, NULL);
+ }
+ /* Run all the tasks. */
+ BLI_task_pool_work_and_wait(pool);
+ /* Verify we've got all the data properly inserted. */
+ EXPECT_EQ(list.head, &list.dummy_node);
+ bool *visited_nodes = (bool *)MEM_callocN(sizeof(bool) * num_nodes, "visited nodes");
+ /* First, we make sure that none of the nodes are added twice. */
+ for (LockfreeLinkNode *node_v = BLI_linklist_lockfree_begin(&list); node_v != NULL;
+ node_v = node_v->next) {
+ IndexedNode *node = (IndexedNode *)node_v;
+ EXPECT_GE(node->index, 0);
+ EXPECT_LT(node->index, num_nodes);
+ EXPECT_FALSE(visited_nodes[node->index]);
+ visited_nodes[node->index] = true;
+ }
+ /* Then we make sure node was added. */
+ for (int node_index = 0; node_index < num_nodes; ++node_index) {
+ EXPECT_TRUE(visited_nodes[node_index]);
+ }
+ MEM_freeN(visited_nodes);
+ /* Cleanup data. */
+ BLI_linklist_lockfree_free(&list, MEM_freeN);
+ BLI_task_pool_free(pool);
+}
diff --git a/source/blender/blenlib/tests/BLI_listbase_test.cc b/source/blender/blenlib/tests/BLI_listbase_test.cc
new file mode 100644
index 00000000000..e5b504a0040
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_listbase_test.cc
@@ -0,0 +1,255 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_array_utils.h"
+#include "BLI_listbase.h"
+#include "BLI_path_util.h"
+#include "BLI_ressource_strings.h"
+#include "BLI_string.h"
+
+/* local validation function */
+static bool listbase_is_valid(const ListBase *listbase)
+{
+#define TESTFAIL(test) \
+ if (!(test)) { \
+ goto fail; \
+ } \
+ ((void)0)
+
+ if (listbase->first) {
+ const Link *prev, *link;
+ link = (Link *)listbase->first;
+ TESTFAIL(link->prev == NULL);
+
+ link = (Link *)listbase->last;
+ TESTFAIL(link->next == NULL);
+
+ prev = NULL;
+ link = (Link *)listbase->first;
+ do {
+ TESTFAIL(link->prev == prev);
+ } while ((void)(prev = link), (link = link->next));
+ TESTFAIL(prev == listbase->last);
+
+ prev = NULL;
+ link = (Link *)listbase->last;
+ do {
+ TESTFAIL(link->next == prev);
+ } while ((void)(prev = link), (link = link->prev));
+ TESTFAIL(prev == listbase->first);
+ }
+ else {
+ TESTFAIL(listbase->last == NULL);
+ }
+#undef TESTFAIL
+
+ return true;
+
+fail:
+ return false;
+}
+
+static int char_switch(char *string, char ch_src, char ch_dst)
+{
+ int tot = 0;
+ while (*string != 0) {
+ if (*string == ch_src) {
+ *string = ch_dst;
+ tot++;
+ }
+ string++;
+ }
+ return tot;
+}
+
+TEST(listbase, FindLinkOrIndex)
+{
+ ListBase lb;
+ void *link1 = MEM_callocN(sizeof(Link), "link1");
+ void *link2 = MEM_callocN(sizeof(Link), "link2");
+
+ /* Empty list */
+ BLI_listbase_clear(&lb);
+ EXPECT_EQ(BLI_findlink(&lb, -1), (void *)NULL);
+ EXPECT_EQ(BLI_findlink(&lb, 0), (void *)NULL);
+ EXPECT_EQ(BLI_findlink(&lb, 1), (void *)NULL);
+ EXPECT_EQ(BLI_rfindlink(&lb, -1), (void *)NULL);
+ EXPECT_EQ(BLI_rfindlink(&lb, 0), (void *)NULL);
+ EXPECT_EQ(BLI_rfindlink(&lb, 1), (void *)NULL);
+ EXPECT_EQ(BLI_findindex(&lb, link1), -1);
+
+ /* One link */
+ BLI_addtail(&lb, link1);
+ EXPECT_EQ(BLI_findlink(&lb, 0), link1);
+ EXPECT_EQ(BLI_rfindlink(&lb, 0), link1);
+ EXPECT_EQ(BLI_findindex(&lb, link1), 0);
+
+ /* Two links */
+ BLI_addtail(&lb, link2);
+ EXPECT_EQ(BLI_findlink(&lb, 1), link2);
+ EXPECT_EQ(BLI_rfindlink(&lb, 0), link2);
+ EXPECT_EQ(BLI_findindex(&lb, link2), 1);
+
+ BLI_freelistN(&lb);
+}
+
+/* -------------------------------------------------------------------- */
+/* Sort utilities & test */
+
+static int testsort_array_str_cmp(const void *a, const void *b)
+{
+ int i = strcmp(*(const char **)a, *(const char **)b);
+ return (i > 0) ? 1 : (i < 0) ? -1 : 0;
+}
+
+static int testsort_listbase_str_cmp(const void *a, const void *b)
+{
+ const LinkData *link_a = (LinkData *)a;
+ const LinkData *link_b = (LinkData *)b;
+ int i = strcmp((const char *)link_a->data, (const char *)link_b->data);
+ return (i > 0) ? 1 : (i < 0) ? -1 : 0;
+}
+
+static int testsort_array_str_cmp_reverse(const void *a, const void *b)
+{
+ return -testsort_array_str_cmp(a, b);
+}
+
+static int testsort_listbase_str_cmp_reverse(const void *a, const void *b)
+{
+ return -testsort_listbase_str_cmp(a, b);
+}
+
+/* check array and listbase compare */
+static bool testsort_listbase_array_str_cmp(ListBase *lb, char **arr, int arr_tot)
+{
+ LinkData *link_step;
+ int i;
+
+ link_step = (LinkData *)lb->first;
+ for (i = 0; i < arr_tot; i++) {
+ if (strcmp(arr[i], (char *)link_step->data) != 0) {
+ return false;
+ }
+ link_step = link_step->next;
+ }
+ if (link_step) {
+ return false;
+ }
+
+ return true;
+}
+
+/* assumes nodes are allocated in-order */
+static bool testsort_listbase_sort_is_stable(ListBase *lb, bool forward)
+{
+ LinkData *link_step;
+
+ link_step = (LinkData *)lb->first;
+ while (link_step && link_step->next) {
+ if (strcmp((const char *)link_step->data, (const char *)link_step->next->data) == 0) {
+ if ((link_step < link_step->next) != forward) {
+ return false;
+ }
+ }
+ link_step = link_step->next;
+ }
+ return true;
+}
+
+TEST(listbase, Sort)
+{
+ const int words_len = sizeof(words10k) - 1;
+ char *words = BLI_strdupn(words10k, words_len);
+ int words_tot;
+ char **words_arr; /* qsort for comparison */
+ int i;
+ char *w_step;
+ ListBase words_lb;
+ LinkData *words_linkdata_arr;
+
+ /* delimit words */
+ words_tot = 1 + char_switch(words, ' ', '\0');
+
+ words_arr = (char **)MEM_mallocN(sizeof(*words_arr) * words_tot, __func__);
+
+ words_linkdata_arr = (LinkData *)MEM_mallocN(sizeof(*words_linkdata_arr) * words_tot, __func__);
+
+ /* create array */
+ w_step = words;
+ for (i = 0; i < words_tot; i++) {
+ words_arr[i] = w_step;
+ w_step += strlen(w_step) + 1;
+ }
+
+ /* sort empty list */
+ {
+ BLI_listbase_clear(&words_lb);
+ BLI_listbase_sort(&words_lb, testsort_listbase_str_cmp);
+ EXPECT_TRUE(listbase_is_valid(&words_lb));
+ }
+
+ /* sort single single */
+ {
+ LinkData link;
+ link.data = words;
+ BLI_addtail(&words_lb, &link);
+ BLI_listbase_sort(&words_lb, testsort_listbase_str_cmp);
+ EXPECT_TRUE(listbase_is_valid(&words_lb));
+ BLI_listbase_clear(&words_lb);
+ }
+
+ /* create listbase */
+ BLI_listbase_clear(&words_lb);
+ w_step = words;
+ for (i = 0; i < words_tot; i++) {
+ LinkData *link = &words_linkdata_arr[i];
+ link->data = w_step;
+ BLI_addtail(&words_lb, link);
+ w_step += strlen(w_step) + 1;
+ }
+ EXPECT_TRUE(listbase_is_valid(&words_lb));
+
+ /* sort (forward) */
+ {
+ qsort(words_arr, words_tot, sizeof(*words_arr), testsort_array_str_cmp);
+
+ BLI_listbase_sort(&words_lb, testsort_listbase_str_cmp);
+ EXPECT_TRUE(listbase_is_valid(&words_lb));
+ EXPECT_TRUE(testsort_listbase_array_str_cmp(&words_lb, words_arr, words_tot));
+ EXPECT_TRUE(testsort_listbase_sort_is_stable(&words_lb, true));
+ }
+
+ /* sort (reverse) */
+ {
+ qsort(words_arr, words_tot, sizeof(*words_arr), testsort_array_str_cmp_reverse);
+
+ BLI_listbase_sort(&words_lb, testsort_listbase_str_cmp_reverse);
+ EXPECT_TRUE(listbase_is_valid(&words_lb));
+ EXPECT_TRUE(testsort_listbase_array_str_cmp(&words_lb, words_arr, words_tot));
+ EXPECT_TRUE(testsort_listbase_sort_is_stable(&words_lb, true));
+ }
+
+ /* sort (forward but after reversing, test stability in alternate direction) */
+ {
+ BLI_array_reverse(words_arr, words_tot);
+ BLI_listbase_reverse(&words_lb);
+
+ EXPECT_TRUE(listbase_is_valid(&words_lb));
+ EXPECT_TRUE(testsort_listbase_array_str_cmp(&words_lb, words_arr, words_tot));
+ EXPECT_TRUE(testsort_listbase_sort_is_stable(&words_lb, false));
+
+ /* and again */
+ BLI_array_reverse(words_arr, words_tot);
+ BLI_listbase_sort(&words_lb, testsort_listbase_str_cmp_reverse);
+ EXPECT_TRUE(testsort_listbase_array_str_cmp(&words_lb, words_arr, words_tot));
+ EXPECT_TRUE(testsort_listbase_sort_is_stable(&words_lb, false));
+ }
+
+ MEM_freeN(words);
+ MEM_freeN(words_arr);
+ MEM_freeN(words_linkdata_arr);
+}
diff --git a/source/blender/blenlib/tests/BLI_math_base_test.cc b/source/blender/blenlib/tests/BLI_math_base_test.cc
new file mode 100644
index 00000000000..dc20c75576d
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_math_base_test.cc
@@ -0,0 +1,115 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_math.h"
+
+/* In tests below, when we are using -1.0f as max_diff value, we actually turn the function into a
+ * pure-ULP one. */
+
+/* Put this here, since we cannot use BLI_assert() in inline math files it seems... */
+TEST(math_base, CompareFFRelativeValid)
+{
+ EXPECT_TRUE(sizeof(float) == sizeof(int));
+}
+
+TEST(math_base, CompareFFRelativeNormal)
+{
+ float f1 = 1.99999988f; /* *(float *)&(*(int *)&f2 - 1) */
+ float f2 = 2.00000000f;
+ float f3 = 2.00000048f; /* *(float *)&(*(int *)&f2 + 2) */
+ float f4 = 2.10000000f; /* *(float *)&(*(int *)&f2 + 419430) */
+
+ const float max_diff = FLT_EPSILON * 0.1f;
+
+ EXPECT_TRUE(compare_ff_relative(f1, f2, max_diff, 1));
+ EXPECT_TRUE(compare_ff_relative(f2, f1, max_diff, 1));
+
+ EXPECT_TRUE(compare_ff_relative(f3, f2, max_diff, 2));
+ EXPECT_TRUE(compare_ff_relative(f2, f3, max_diff, 2));
+
+ EXPECT_FALSE(compare_ff_relative(f3, f2, max_diff, 1));
+ EXPECT_FALSE(compare_ff_relative(f2, f3, max_diff, 1));
+
+ EXPECT_FALSE(compare_ff_relative(f3, f2, -1.0f, 1));
+ EXPECT_FALSE(compare_ff_relative(f2, f3, -1.0f, 1));
+
+ EXPECT_TRUE(compare_ff_relative(f3, f2, -1.0f, 2));
+ EXPECT_TRUE(compare_ff_relative(f2, f3, -1.0f, 2));
+
+ EXPECT_FALSE(compare_ff_relative(f4, f2, max_diff, 64));
+ EXPECT_FALSE(compare_ff_relative(f2, f4, max_diff, 64));
+
+ EXPECT_TRUE(compare_ff_relative(f1, f3, max_diff, 64));
+ EXPECT_TRUE(compare_ff_relative(f3, f1, max_diff, 64));
+}
+
+TEST(math_base, CompareFFRelativeZero)
+{
+ float f0 = 0.0f;
+ float f1 = 4.2038954e-045f; /* *(float *)&(*(int *)&f0 + 3) */
+
+ float fn0 = -0.0f;
+ float fn1 = -2.8025969e-045f; /* *(float *)&(*(int *)&fn0 - 2) */
+
+ const float max_diff = FLT_EPSILON * 0.1f;
+
+ EXPECT_TRUE(compare_ff_relative(f0, f1, -1.0f, 3));
+ EXPECT_TRUE(compare_ff_relative(f1, f0, -1.0f, 3));
+
+ EXPECT_FALSE(compare_ff_relative(f0, f1, -1.0f, 1));
+ EXPECT_FALSE(compare_ff_relative(f1, f0, -1.0f, 1));
+
+ EXPECT_TRUE(compare_ff_relative(fn0, fn1, -1.0f, 8));
+ EXPECT_TRUE(compare_ff_relative(fn1, fn0, -1.0f, 8));
+
+ EXPECT_TRUE(compare_ff_relative(f0, f1, max_diff, 1));
+ EXPECT_TRUE(compare_ff_relative(f1, f0, max_diff, 1));
+
+ EXPECT_TRUE(compare_ff_relative(fn0, f0, max_diff, 1));
+ EXPECT_TRUE(compare_ff_relative(f0, fn0, max_diff, 1));
+
+ EXPECT_TRUE(compare_ff_relative(f0, fn1, max_diff, 1));
+ EXPECT_TRUE(compare_ff_relative(fn1, f0, max_diff, 1));
+
+ /* Note: in theory, this should return false, since 0.0f and -0.0f have 0x80000000 diff,
+ * but overflow in subtraction seems to break something here
+ * (abs(*(int *)&fn0 - *(int *)&f0) == 0x80000000 == fn0), probably because int32 cannot
+ * hold this abs value. this is yet another illustration of why one shall never use (near-)zero
+ * floats in pure-ULP comparison. */
+ // EXPECT_FALSE(compare_ff_relative(fn0, f0, -1.0f, 1024));
+ // EXPECT_FALSE(compare_ff_relative(f0, fn0, -1.0f, 1024));
+
+ EXPECT_FALSE(compare_ff_relative(fn0, f1, -1.0f, 1024));
+ EXPECT_FALSE(compare_ff_relative(f1, fn0, -1.0f, 1024));
+}
+
+TEST(math_base, Log2FloorU)
+{
+ EXPECT_EQ(log2_floor_u(0), 0);
+ EXPECT_EQ(log2_floor_u(1), 0);
+ EXPECT_EQ(log2_floor_u(2), 1);
+ EXPECT_EQ(log2_floor_u(3), 1);
+ EXPECT_EQ(log2_floor_u(4), 2);
+ EXPECT_EQ(log2_floor_u(5), 2);
+ EXPECT_EQ(log2_floor_u(6), 2);
+ EXPECT_EQ(log2_floor_u(7), 2);
+ EXPECT_EQ(log2_floor_u(8), 3);
+ EXPECT_EQ(log2_floor_u(9), 3);
+ EXPECT_EQ(log2_floor_u(123456), 16);
+}
+
+TEST(math_base, Log2CeilU)
+{
+ EXPECT_EQ(log2_ceil_u(0), 0);
+ EXPECT_EQ(log2_ceil_u(1), 0);
+ EXPECT_EQ(log2_ceil_u(2), 1);
+ EXPECT_EQ(log2_ceil_u(3), 2);
+ EXPECT_EQ(log2_ceil_u(4), 2);
+ EXPECT_EQ(log2_ceil_u(5), 3);
+ EXPECT_EQ(log2_ceil_u(6), 3);
+ EXPECT_EQ(log2_ceil_u(7), 3);
+ EXPECT_EQ(log2_ceil_u(8), 3);
+ EXPECT_EQ(log2_ceil_u(9), 4);
+ EXPECT_EQ(log2_ceil_u(123456), 17);
+}
diff --git a/source/blender/blenlib/tests/BLI_math_bits_test.cc b/source/blender/blenlib/tests/BLI_math_bits_test.cc
new file mode 100644
index 00000000000..4fa4809beed
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_math_bits_test.cc
@@ -0,0 +1,50 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_math_bits.h"
+#include "testing/testing.h"
+#include <iostream>
+
+TEST(math_bits, BitscanReverseClearUint)
+{
+ uint a = 1234;
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 21);
+ EXPECT_EQ(a, 210);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 24);
+ EXPECT_EQ(a, 82);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 25);
+ EXPECT_EQ(a, 18);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 27);
+ EXPECT_EQ(a, 2);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 30);
+ EXPECT_EQ(a, 0);
+
+ a = 3563987529;
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 0);
+ EXPECT_EQ(a, 1416503881);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 1);
+ EXPECT_EQ(a, 342762057);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 3);
+ EXPECT_EQ(a, 74326601);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 5);
+ EXPECT_EQ(a, 7217737);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 9);
+ EXPECT_EQ(a, 3023433);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 10);
+ EXPECT_EQ(a, 926281);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 12);
+ EXPECT_EQ(a, 401993);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 13);
+ EXPECT_EQ(a, 139849);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 14);
+ EXPECT_EQ(a, 8777);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 18);
+ EXPECT_EQ(a, 585);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 22);
+ EXPECT_EQ(a, 73);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 25);
+ EXPECT_EQ(a, 9);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 28);
+ EXPECT_EQ(a, 1);
+ EXPECT_EQ(bitscan_reverse_clear_uint(&a), 31);
+ EXPECT_EQ(a, 0);
+}
diff --git a/source/blender/blenlib/tests/BLI_math_color_test.cc b/source/blender/blenlib/tests/BLI_math_color_test.cc
new file mode 100644
index 00000000000..7df47e74eb0
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_math_color_test.cc
@@ -0,0 +1,76 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_math.h"
+
+TEST(math_color, RGBToHSVRoundtrip)
+{
+ float orig_rgb[3] = {0.1f, 0.2f, 0.3f};
+ float hsv[3], rgb[3];
+ rgb_to_hsv_v(orig_rgb, hsv);
+ hsv_to_rgb_v(hsv, rgb);
+ EXPECT_V3_NEAR(orig_rgb, rgb, 1e-5);
+}
+
+TEST(math_color, RGBToHSLRoundtrip)
+{
+ float orig_rgb[3] = {0.1f, 0.2f, 0.3f};
+ float hsl[3], rgb[3];
+ rgb_to_hsl_v(orig_rgb, hsl);
+ hsl_to_rgb_v(hsl, rgb);
+ EXPECT_V3_NEAR(orig_rgb, rgb, 1e-5);
+}
+
+TEST(math_color, RGBToYUVRoundtrip)
+{
+ float orig_rgb[3] = {0.1f, 0.2f, 0.3f};
+ float yuv[3], rgb[3];
+ rgb_to_yuv(orig_rgb[0], orig_rgb[1], orig_rgb[2], &yuv[0], &yuv[1], &yuv[2], BLI_YUV_ITU_BT709);
+ yuv_to_rgb(yuv[0], yuv[1], yuv[2], &rgb[0], &rgb[1], &rgb[2], BLI_YUV_ITU_BT709);
+ EXPECT_V3_NEAR(orig_rgb, rgb, 1e-4);
+}
+
+TEST(math_color, RGBToYCCRoundtrip)
+{
+ float orig_rgb[3] = {0.1f, 0.2f, 0.3f};
+ float ycc[3], rgb[3];
+
+ rgb_to_ycc(orig_rgb[0], orig_rgb[1], orig_rgb[2], &ycc[0], &ycc[1], &ycc[2], BLI_YCC_ITU_BT601);
+ ycc_to_rgb(ycc[0], ycc[1], ycc[2], &rgb[0], &rgb[1], &rgb[2], BLI_YCC_ITU_BT601);
+ EXPECT_V3_NEAR(orig_rgb, rgb, 1e-3);
+
+ rgb_to_ycc(orig_rgb[0], orig_rgb[1], orig_rgb[2], &ycc[0], &ycc[1], &ycc[2], BLI_YCC_ITU_BT709);
+ ycc_to_rgb(ycc[0], ycc[1], ycc[2], &rgb[0], &rgb[1], &rgb[2], BLI_YCC_ITU_BT709);
+ EXPECT_V3_NEAR(orig_rgb, rgb, 1e-3);
+
+ rgb_to_ycc(orig_rgb[0], orig_rgb[1], orig_rgb[2], &ycc[0], &ycc[1], &ycc[2], BLI_YCC_JFIF_0_255);
+ ycc_to_rgb(ycc[0], ycc[1], ycc[2], &rgb[0], &rgb[1], &rgb[2], BLI_YCC_JFIF_0_255);
+ EXPECT_V3_NEAR(orig_rgb, rgb, 1e-3);
+}
+
+TEST(math_color, LinearRGBTosRGBNearZero)
+{
+ float linear_color = 0.002f;
+ float srgb_color = linearrgb_to_srgb(linear_color);
+ EXPECT_NEAR(0.02584f, srgb_color, 1e-5);
+}
+
+TEST(math_color, LinearRGBTosRGB)
+{
+ float linear_color = 0.75f;
+ float srgb_color = linearrgb_to_srgb(linear_color);
+ EXPECT_NEAR(0.880824f, srgb_color, 1e-5);
+}
+
+TEST(math_color, LinearRGBTosRGBRoundtrip)
+{
+ const int N = 50;
+ int i;
+ for (i = 0; i < N; ++i) {
+ float orig_linear_color = (float)i / N;
+ float srgb_color = linearrgb_to_srgb(orig_linear_color);
+ float linear_color = srgb_to_linearrgb(srgb_color);
+ EXPECT_NEAR(orig_linear_color, linear_color, 1e-5);
+ }
+}
diff --git a/source/blender/blenlib/tests/BLI_math_geom_test.cc b/source/blender/blenlib/tests/BLI_math_geom_test.cc
new file mode 100644
index 00000000000..44154d49128
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_math_geom_test.cc
@@ -0,0 +1,19 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_math.h"
+
+TEST(math_geom, DistToLine2DSimple)
+{
+ float p[2] = {5.0f, 1.0f}, a[2] = {0.0f, 0.0f}, b[2] = {2.0f, 0.0f};
+ float distance = dist_to_line_v2(p, a, b);
+ EXPECT_NEAR(1.0f, distance, 1e-6);
+}
+
+TEST(math_geom, DistToLineSegment2DSimple)
+{
+ float p[2] = {3.0f, 1.0f}, a[2] = {0.0f, 0.0f}, b[2] = {2.0f, 0.0f};
+ float distance = dist_to_line_segment_v2(p, a, b);
+ EXPECT_NEAR(sqrtf(2.0f), distance, 1e-6);
+}
diff --git a/source/blender/blenlib/tests/BLI_math_matrix_test.cc b/source/blender/blenlib/tests/BLI_math_matrix_test.cc
new file mode 100644
index 00000000000..9c47c02ceaf
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_math_matrix_test.cc
@@ -0,0 +1,99 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_math_matrix.h"
+
+TEST(math_matrix, interp_m4_m4m4_regular)
+{
+ /* Test 4x4 matrix interpolation without singularity, i.e. without axis flip. */
+
+ /* Transposed matrix, so that the code here is written in the same way as print_m4() outputs. */
+ /* This matrix represents T=(0.1, 0.2, 0.3), R=(40, 50, 60) degrees, S=(0.7, 0.8, 0.9) */
+ float matrix_a[4][4] = {
+ {0.224976f, -0.333770f, 0.765074f, 0.100000f},
+ {0.389669f, 0.647565f, 0.168130f, 0.200000f},
+ {-0.536231f, 0.330541f, 0.443163f, 0.300000f},
+ {0.000000f, 0.000000f, 0.000000f, 1.000000f},
+ };
+ transpose_m4(matrix_a);
+
+ float matrix_i[4][4];
+ unit_m4(matrix_i);
+
+ float result[4][4];
+ const float epsilon = 1e-6;
+ interp_m4_m4m4(result, matrix_i, matrix_a, 0.0f);
+ EXPECT_M4_NEAR(result, matrix_i, epsilon);
+
+ interp_m4_m4m4(result, matrix_i, matrix_a, 1.0f);
+ EXPECT_M4_NEAR(result, matrix_a, epsilon);
+
+ /* This matrix is based on the current implementation of the code, and isn't guaranteed to be
+ * correct. It's just consistent with the current implementation. */
+ float matrix_halfway[4][4] = {
+ {0.690643f, -0.253244f, 0.484996f, 0.050000f},
+ {0.271924f, 0.852623f, 0.012348f, 0.100000f},
+ {-0.414209f, 0.137484f, 0.816778f, 0.150000f},
+ {0.000000f, 0.000000f, 0.000000f, 1.000000f},
+ };
+
+ transpose_m4(matrix_halfway);
+ interp_m4_m4m4(result, matrix_i, matrix_a, 0.5f);
+ EXPECT_M4_NEAR(result, matrix_halfway, epsilon);
+}
+
+TEST(math_matrix, interp_m3_m3m3_singularity)
+{
+ /* A singluarity means that there is an axis mirror in the rotation component of the matrix. This
+ * is reflected in its negative determinant.
+ *
+ * The interpolation of 4x4 matrices performs linear interpolation on the translation component,
+ * and then uses the 3x3 interpolation function to handle rotation and scale. As a result, this
+ * test for a singularity in the rotation matrix only needs to test the 3x3 case. */
+
+ /* Transposed matrix, so that the code here is written in the same way as print_m4() outputs. */
+ /* This matrix represents R=(4, 5, 6) degrees, S=(-1, 1, 1) */
+ float matrix_a[3][3] = {
+ {-0.990737f, -0.098227f, 0.093759f},
+ {-0.104131f, 0.992735f, -0.060286f},
+ {0.087156f, 0.069491f, 0.993768f},
+ };
+ transpose_m3(matrix_a);
+ EXPECT_NEAR(-1.0f, determinant_m3_array(matrix_a), 1e-6);
+
+ /* This matrix represents R=(0, 0, 0), S=(-1, 0, 0) */
+ float matrix_b[3][3] = {
+ {-1.0f, 0.0f, 0.0f},
+ {0.0f, 1.0f, 0.0f},
+ {0.0f, 0.0f, 1.0f},
+ };
+ transpose_m3(matrix_b);
+
+ float result[3][3];
+ interp_m3_m3m3(result, matrix_a, matrix_b, 0.0f);
+ EXPECT_M3_NEAR(result, matrix_a, 1e-5);
+
+ interp_m3_m3m3(result, matrix_a, matrix_b, 1.0f);
+ EXPECT_M3_NEAR(result, matrix_b, 1e-5);
+
+ interp_m3_m3m3(result, matrix_a, matrix_b, 0.5f);
+ float expect[3][3] = {
+ {-0.997681f, -0.049995f, 0.046186f},
+ {-0.051473f, 0.998181f, -0.031385f},
+ {0.044533f, 0.033689f, 0.998440f},
+ };
+ transpose_m3(expect);
+ EXPECT_M3_NEAR(result, expect, 1e-5);
+
+ /* Interpolating between a matrix with and without axis flip can cause it to go through a zero
+ * point. The determinant det(A) of a matrix represents the change in volume; interpolating
+ * between matrices with det(A)=-1 and det(B)=1 will have to go through a point where
+ * det(result)=0, so where the volume becomes zero. */
+ float matrix_i[3][3];
+ unit_m3(matrix_i);
+ zero_m3(expect);
+ interp_m3_m3m3(result, matrix_a, matrix_i, 0.5f);
+ EXPECT_NEAR(0.0f, determinant_m3_array(result), 1e-5);
+ EXPECT_M3_NEAR(result, expect, 1e-5);
+}
diff --git a/source/blender/blenlib/tests/BLI_math_vector_test.cc b/source/blender/blenlib/tests/BLI_math_vector_test.cc
new file mode 100644
index 00000000000..7e75a521d4c
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_math_vector_test.cc
@@ -0,0 +1,47 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_math.h"
+
+TEST(math_vector, ClampVecWithFloats)
+{
+ const float min = 0.0f;
+ const float max = 1.0f;
+
+ float a[2] = {-1.0f, -1.0f};
+ clamp_v2(a, min, max);
+ EXPECT_FLOAT_EQ(0.0f, a[0]);
+ EXPECT_FLOAT_EQ(0.0f, a[1]);
+
+ float b[2] = {0.5f, 0.5f};
+ clamp_v2(b, min, max);
+ EXPECT_FLOAT_EQ(0.5f, b[0]);
+ EXPECT_FLOAT_EQ(0.5f, b[1]);
+
+ float c[2] = {2.0f, 2.0f};
+ clamp_v2(c, min, max);
+ EXPECT_FLOAT_EQ(1.0f, c[0]);
+ EXPECT_FLOAT_EQ(1.0f, c[1]);
+}
+
+TEST(math_vector, ClampVecWithVecs)
+{
+ const float min[2] = {0.0f, 2.0f};
+ const float max[2] = {1.0f, 3.0f};
+
+ float a[2] = {-1.0f, -1.0f};
+ clamp_v2_v2v2(a, min, max);
+ EXPECT_FLOAT_EQ(0.0f, a[0]);
+ EXPECT_FLOAT_EQ(2.0f, a[1]);
+
+ float b[2] = {0.5f, 2.5f};
+ clamp_v2_v2v2(b, min, max);
+ EXPECT_FLOAT_EQ(0.5f, b[0]);
+ EXPECT_FLOAT_EQ(2.5f, b[1]);
+
+ float c[2] = {2.0f, 4.0f};
+ clamp_v2_v2v2(c, min, max);
+ EXPECT_FLOAT_EQ(1.0f, c[0]);
+ EXPECT_FLOAT_EQ(3.0f, c[1]);
+}
diff --git a/source/blender/blenlib/tests/BLI_memiter_test.cc b/source/blender/blenlib/tests/BLI_memiter_test.cc
new file mode 100644
index 00000000000..3cc86630005
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_memiter_test.cc
@@ -0,0 +1,277 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_array_utils.h"
+#include "BLI_memiter.h"
+
+#include "BLI_ressource_strings.h"
+#include "BLI_string.h"
+
+TEST(memiter, Nop)
+{
+ BLI_memiter *mi = BLI_memiter_create(64);
+ BLI_memiter_destroy(mi);
+}
+
+static void memiter_empty_test(int num_elems, const int chunk_size)
+{
+ BLI_memiter *mi = BLI_memiter_create(chunk_size);
+ void *data;
+ for (int index = 0; index < num_elems; index++) {
+ data = BLI_memiter_alloc(mi, 0);
+ }
+ int index = 0, total_size = 0;
+ BLI_memiter_handle it;
+ BLI_memiter_iter_init(mi, &it);
+ uint elem_size;
+ while ((data = BLI_memiter_iter_step_size(&it, &elem_size))) {
+ index += 1;
+ total_size += elem_size;
+ }
+ EXPECT_EQ(0, total_size);
+ EXPECT_EQ(num_elems, index);
+
+ BLI_memiter_destroy(mi);
+}
+
+#define MEMITER_NUMBER_TEST_FN(fn, number_type) \
+ static void fn(int num_elems, const int chunk_size) \
+ { \
+ BLI_memiter *mi = BLI_memiter_create(chunk_size); \
+ number_type *data; \
+ for (int index = 0; index < num_elems; index++) { \
+ data = (number_type *)BLI_memiter_alloc(mi, sizeof(number_type)); \
+ *data = index; \
+ } \
+ BLI_memiter_handle it; \
+ BLI_memiter_iter_init(mi, &it); \
+ uint elem_size; \
+ int index = 0; \
+ while ((data = (number_type *)BLI_memiter_iter_step_size(&it, &elem_size))) { \
+ EXPECT_EQ(sizeof(number_type), elem_size); \
+ EXPECT_EQ(index, *data); \
+ index += 1; \
+ } \
+ BLI_memiter_destroy(mi); \
+ }
+
+/* generate number functions */
+MEMITER_NUMBER_TEST_FN(memiter_char_test, char)
+MEMITER_NUMBER_TEST_FN(memiter_short_test, short)
+MEMITER_NUMBER_TEST_FN(memiter_int_test, int)
+MEMITER_NUMBER_TEST_FN(memiter_long_test, int64_t)
+
+static void memiter_string_test(const char *strings[], const int chunk_size)
+{
+ BLI_memiter *mi = BLI_memiter_create(chunk_size);
+ char *data;
+ int index = 0;
+ int total_size_expect = 0;
+ while (strings[index]) {
+ const int size = strlen(strings[index]) + 1;
+ BLI_memiter_alloc_from(mi, size, strings[index]);
+ total_size_expect += size;
+ index += 1;
+ }
+ const int strings_len = index;
+ int total_size = 0;
+ BLI_memiter_handle it;
+ BLI_memiter_iter_init(mi, &it);
+ uint elem_size;
+ index = 0;
+ while ((data = (char *)BLI_memiter_iter_step_size(&it, &elem_size))) {
+ EXPECT_EQ(strlen(strings[index]) + 1, elem_size);
+ EXPECT_STREQ(strings[index], data);
+ total_size += elem_size;
+ index += 1;
+ }
+ EXPECT_EQ(total_size_expect, total_size);
+ EXPECT_EQ(strings_len, index);
+
+ BLI_memiter_destroy(mi);
+}
+
+static void memiter_words10k_test(const char split_char, const int chunk_size)
+{
+ const int words_len = sizeof(words10k) - 1;
+ char *words = BLI_strdupn(words10k, words_len);
+ BLI_str_replace_char(words, split_char, '\0');
+
+ BLI_memiter *mi = BLI_memiter_create(chunk_size);
+
+ char *data;
+ int index;
+ char *c_end, *c;
+ c_end = words + words_len;
+ c = words;
+ index = 0;
+ while (c < c_end) {
+ int elem_size = strlen(c) + 1;
+ data = (char *)BLI_memiter_alloc(mi, elem_size);
+ memcpy(data, c, elem_size);
+ c += elem_size;
+ index += 1;
+ }
+ const int len_expect = index;
+ c = words;
+ uint size;
+ BLI_memiter_handle it;
+ BLI_memiter_iter_init(mi, &it);
+ index = 0;
+ while ((data = (char *)BLI_memiter_iter_step_size(&it, &size))) {
+ int size_expect = strlen(c) + 1;
+ EXPECT_EQ(size_expect, size);
+ EXPECT_STREQ(c, data);
+ c += size;
+ index += 1;
+ }
+ EXPECT_EQ(len_expect, index);
+ BLI_memiter_destroy(mi);
+ MEM_freeN(words);
+}
+
+#define TEST_EMPTY_AT_CHUNK_SIZE(chunk_size) \
+ TEST(memiter, Empty0_##chunk_size) \
+ { \
+ memiter_empty_test(0, chunk_size); \
+ } \
+ TEST(memiter, Empty1_##chunk_size) \
+ { \
+ memiter_empty_test(1, chunk_size); \
+ } \
+ TEST(memiter, Empty2_##chunk_size) \
+ { \
+ memiter_empty_test(2, chunk_size); \
+ } \
+ TEST(memiter, Empty3_##chunk_size) \
+ { \
+ memiter_empty_test(3, chunk_size); \
+ } \
+ TEST(memiter, Empty13_##chunk_size) \
+ { \
+ memiter_empty_test(13, chunk_size); \
+ } \
+ TEST(memiter, Empty256_##chunk_size) \
+ { \
+ memiter_empty_test(256, chunk_size); \
+ }
+
+TEST_EMPTY_AT_CHUNK_SIZE(1)
+TEST_EMPTY_AT_CHUNK_SIZE(2)
+TEST_EMPTY_AT_CHUNK_SIZE(3)
+TEST_EMPTY_AT_CHUNK_SIZE(13)
+TEST_EMPTY_AT_CHUNK_SIZE(256)
+
+#define TEST_NUMBER_AT_CHUNK_SIZE(chunk_size) \
+ TEST(memiter, Char1_##chunk_size) \
+ { \
+ memiter_char_test(1, chunk_size); \
+ } \
+ TEST(memiter, Short1_##chunk_size) \
+ { \
+ memiter_short_test(1, chunk_size); \
+ } \
+ TEST(memiter, Int1_##chunk_size) \
+ { \
+ memiter_int_test(1, chunk_size); \
+ } \
+ TEST(memiter, Long1_##chunk_size) \
+ { \
+ memiter_long_test(1, chunk_size); \
+ } \
+\
+ TEST(memiter, Char2_##chunk_size) \
+ { \
+ memiter_char_test(2, chunk_size); \
+ } \
+ TEST(memiter, Short2_##chunk_size) \
+ { \
+ memiter_short_test(2, chunk_size); \
+ } \
+ TEST(memiter, Int2_##chunk_size) \
+ { \
+ memiter_int_test(2, chunk_size); \
+ } \
+ TEST(memiter, Long2_##chunk_size) \
+ { \
+ memiter_long_test(2, chunk_size); \
+ } \
+\
+ TEST(memiter, Char3_##chunk_size) \
+ { \
+ memiter_char_test(3, chunk_size); \
+ } \
+ TEST(memiter, Short3_##chunk_size) \
+ { \
+ memiter_short_test(3, chunk_size); \
+ } \
+ TEST(memiter, Int3_##chunk_size) \
+ { \
+ memiter_int_test(3, chunk_size); \
+ } \
+ TEST(memiter, Long3_##chunk_size) \
+ { \
+ memiter_long_test(3, chunk_size); \
+ } \
+\
+ TEST(memiter, Char256_##chunk_size) \
+ { \
+ memiter_char_test(256, chunk_size); \
+ } \
+ TEST(memiter, Short256_##chunk_size) \
+ { \
+ memiter_short_test(256, chunk_size); \
+ } \
+ TEST(memiter, Int256_##chunk_size) \
+ { \
+ memiter_int_test(256, chunk_size); \
+ } \
+ TEST(memiter, Long256_##chunk_size) \
+ { \
+ memiter_long_test(256, chunk_size); \
+ }
+
+TEST_NUMBER_AT_CHUNK_SIZE(1)
+TEST_NUMBER_AT_CHUNK_SIZE(2)
+TEST_NUMBER_AT_CHUNK_SIZE(3)
+TEST_NUMBER_AT_CHUNK_SIZE(13)
+TEST_NUMBER_AT_CHUNK_SIZE(256)
+
+#define STRINGS_TEST(chunk_size, ...) \
+ { \
+ const char *data[] = {__VA_ARGS__, NULL}; \
+ memiter_string_test(data, chunk_size); \
+ }
+
+#define TEST_STRINGS_AT_CHUNK_SIZE(chunk_size) \
+ TEST(memiter, Strings_##chunk_size) \
+ { \
+ STRINGS_TEST(chunk_size, ""); \
+ STRINGS_TEST(chunk_size, "test", "me"); \
+ STRINGS_TEST(chunk_size, "more", "test", "data", "to", "follow"); \
+ }
+
+TEST_STRINGS_AT_CHUNK_SIZE(1)
+TEST_STRINGS_AT_CHUNK_SIZE(2)
+TEST_STRINGS_AT_CHUNK_SIZE(3)
+TEST_STRINGS_AT_CHUNK_SIZE(13)
+TEST_STRINGS_AT_CHUNK_SIZE(256)
+
+#define TEST_WORDS10K_AT_CHUNK_SIZE(chunk_size) \
+ TEST(memiter, Words10kSentence_##chunk_size) \
+ { \
+ memiter_words10k_test('.', chunk_size); \
+ } \
+ TEST(memiter, Words10kWords_##chunk_size) \
+ { \
+ memiter_words10k_test(' ', chunk_size); \
+ }
+
+TEST_WORDS10K_AT_CHUNK_SIZE(1)
+TEST_WORDS10K_AT_CHUNK_SIZE(2)
+TEST_WORDS10K_AT_CHUNK_SIZE(3)
+TEST_WORDS10K_AT_CHUNK_SIZE(13)
+TEST_WORDS10K_AT_CHUNK_SIZE(256)
diff --git a/source/blender/blenlib/tests/BLI_path_util_test.cc b/source/blender/blenlib/tests/BLI_path_util_test.cc
new file mode 100644
index 00000000000..4b8e6ed8085
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_path_util_test.cc
@@ -0,0 +1,606 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "IMB_imbuf.h"
+
+#include "BLI_fileops.h"
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+
+#ifdef _WIN32
+# include "BKE_global.h"
+#endif
+
+/* -------------------------------------------------------------------- */
+/* tests */
+
+/* BLI_path_normalize */
+#ifndef _WIN32
+TEST(path_util, Clean)
+{
+ /* "/./" -> "/" */
+ {
+ char path[FILE_MAX] = "/a/./b/./c/./";
+ BLI_path_normalize(NULL, path);
+ EXPECT_STREQ("/a/b/c/", path);
+ }
+
+ {
+ char path[FILE_MAX] = "/./././";
+ BLI_path_normalize(NULL, path);
+ EXPECT_STREQ("/", path);
+ }
+
+ {
+ char path[FILE_MAX] = "/a/./././b/";
+ BLI_path_normalize(NULL, path);
+ EXPECT_STREQ("/a/b/", path);
+ }
+
+ /* "//" -> "/" */
+ {
+ char path[FILE_MAX] = "a////";
+ BLI_path_normalize(NULL, path);
+ EXPECT_STREQ("a/", path);
+ }
+
+ if (0) /* FIXME */
+ {
+ char path[FILE_MAX] = "./a////";
+ BLI_path_normalize(NULL, path);
+ EXPECT_STREQ("./a/", path);
+ }
+
+ /* "foo/bar/../" -> "foo/" */
+ {
+ char path[FILE_MAX] = "/a/b/c/../../../";
+ BLI_path_normalize(NULL, path);
+ EXPECT_STREQ("/", path);
+ }
+
+ {
+ char path[FILE_MAX] = "/a/../a/b/../b/c/../c/";
+ BLI_path_normalize(NULL, path);
+ EXPECT_STREQ("/a/b/c/", path);
+ }
+
+ {
+ char path[FILE_MAX] = "//../";
+ BLI_path_normalize("/a/b/c/", path);
+ EXPECT_STREQ("/a/b/", path);
+ }
+}
+#endif
+
+#define AT_INDEX(str_input, index_input, str_expect) \
+ { \
+ char path[] = str_input; \
+ const char *expect = str_expect; \
+ int index_output, len_output; \
+ const bool ret = BLI_path_name_at_index(path, index_input, &index_output, &len_output); \
+ if (expect == NULL) { \
+ EXPECT_FALSE(ret); \
+ } \
+ else { \
+ EXPECT_TRUE(ret); \
+ EXPECT_EQ(strlen(expect), len_output); \
+ path[index_output + len_output] = '\0'; \
+ EXPECT_STREQ(&path[index_output], expect); \
+ } \
+ } \
+ ((void)0)
+
+/* BLI_path_name_at_index */
+TEST(path_util, NameAtIndex_Single)
+{
+ AT_INDEX("/a", 0, "a");
+ AT_INDEX("/a/", 0, "a");
+ AT_INDEX("a/", 0, "a");
+ AT_INDEX("//a//", 0, "a");
+ AT_INDEX("a/b", 0, "a");
+
+ AT_INDEX("/a", 1, NULL);
+ AT_INDEX("/a/", 1, NULL);
+ AT_INDEX("a/", 1, NULL);
+ AT_INDEX("//a//", 1, NULL);
+}
+TEST(path_util, NameAtIndex_SingleNeg)
+{
+ AT_INDEX("/a", -1, "a");
+ AT_INDEX("/a/", -1, "a");
+ AT_INDEX("a/", -1, "a");
+ AT_INDEX("//a//", -1, "a");
+ AT_INDEX("a/b", -1, "b");
+
+ AT_INDEX("/a", -2, NULL);
+ AT_INDEX("/a/", -2, NULL);
+ AT_INDEX("a/", -2, NULL);
+ AT_INDEX("//a//", -2, NULL);
+}
+
+TEST(path_util, NameAtIndex_Double)
+{
+ AT_INDEX("/ab", 0, "ab");
+ AT_INDEX("/ab/", 0, "ab");
+ AT_INDEX("ab/", 0, "ab");
+ AT_INDEX("//ab//", 0, "ab");
+ AT_INDEX("ab/c", 0, "ab");
+
+ AT_INDEX("/ab", 1, NULL);
+ AT_INDEX("/ab/", 1, NULL);
+ AT_INDEX("ab/", 1, NULL);
+ AT_INDEX("//ab//", 1, NULL);
+}
+
+TEST(path_util, NameAtIndex_DoublNeg)
+{
+ AT_INDEX("/ab", -1, "ab");
+ AT_INDEX("/ab/", -1, "ab");
+ AT_INDEX("ab/", -1, "ab");
+ AT_INDEX("//ab//", -1, "ab");
+ AT_INDEX("ab/c", -1, "c");
+
+ AT_INDEX("/ab", -2, NULL);
+ AT_INDEX("/ab/", -2, NULL);
+ AT_INDEX("ab/", -2, NULL);
+ AT_INDEX("//ab//", -2, NULL);
+}
+
+TEST(path_util, NameAtIndex_Misc)
+{
+ AT_INDEX("/how/now/brown/cow", 0, "how");
+ AT_INDEX("/how/now/brown/cow", 1, "now");
+ AT_INDEX("/how/now/brown/cow", 2, "brown");
+ AT_INDEX("/how/now/brown/cow", 3, "cow");
+ AT_INDEX("/how/now/brown/cow", 4, NULL);
+ AT_INDEX("/how/now/brown/cow/", 4, NULL);
+}
+
+TEST(path_util, NameAtIndex_MiscNeg)
+{
+ AT_INDEX("/how/now/brown/cow", 0, "how");
+ AT_INDEX("/how/now/brown/cow", 1, "now");
+ AT_INDEX("/how/now/brown/cow", 2, "brown");
+ AT_INDEX("/how/now/brown/cow", 3, "cow");
+ AT_INDEX("/how/now/brown/cow", 4, NULL);
+ AT_INDEX("/how/now/brown/cow/", 4, NULL);
+}
+
+TEST(path_util, NameAtIndex_MiscComplex)
+{
+ AT_INDEX("how//now/brown/cow", 0, "how");
+ AT_INDEX("//how///now\\/brown/cow", 1, "now");
+ AT_INDEX("/how/now\\//brown\\/cow", 2, "brown");
+ AT_INDEX("/how/now/brown/cow//\\", 3, "cow");
+ AT_INDEX("/how/now/brown/\\cow", 4, NULL);
+ AT_INDEX("how/now/brown/\\cow\\", 4, NULL);
+}
+
+TEST(path_util, NameAtIndex_MiscComplexNeg)
+{
+ AT_INDEX("how//now/brown/cow", -4, "how");
+ AT_INDEX("//how///now\\/brown/cow", -3, "now");
+ AT_INDEX("/how/now\\//brown\\/cow", -2, "brown");
+ AT_INDEX("/how/now/brown/cow//\\", -1, "cow");
+ AT_INDEX("/how/now/brown/\\cow", -5, NULL);
+ AT_INDEX("how/now/brown/\\cow\\", -5, NULL);
+}
+
+TEST(path_util, NameAtIndex_NoneComplex)
+{
+ AT_INDEX("", 0, NULL);
+ AT_INDEX("/", 0, NULL);
+ AT_INDEX("//", 0, NULL);
+ AT_INDEX("///", 0, NULL);
+}
+
+TEST(path_util, NameAtIndex_NoneComplexNeg)
+{
+ AT_INDEX("", -1, NULL);
+ AT_INDEX("/", -1, NULL);
+ AT_INDEX("//", -1, NULL);
+ AT_INDEX("///", -1, NULL);
+}
+
+#undef AT_INDEX
+
+#define JOIN(str_expect, out_size, ...) \
+ { \
+ const char *expect = str_expect; \
+ char result[(out_size) + 1024]; \
+ /* check we don't write past the last byte */ \
+ result[out_size] = '\0'; \
+ BLI_path_join(result, out_size, __VA_ARGS__, NULL); \
+ /* simplify expected string */ \
+ BLI_str_replace_char(result, '\\', '/'); \
+ EXPECT_STREQ(result, expect); \
+ EXPECT_EQ(result[out_size], '\0'); \
+ } \
+ ((void)0)
+
+/* BLI_path_join */
+TEST(path_util, JoinNop)
+{
+ JOIN("", 100, "");
+ JOIN("", 100, "", "");
+ JOIN("", 100, "", "", "");
+ JOIN("/", 100, "/", "", "");
+ JOIN("/", 100, "/", "/");
+ JOIN("/", 100, "/", "", "/");
+ JOIN("/", 100, "/", "", "/", "");
+}
+
+TEST(path_util, JoinSingle)
+{
+ JOIN("test", 100, "test");
+ JOIN("", 100, "");
+ JOIN("a", 100, "a");
+ JOIN("/a", 100, "/a");
+ JOIN("a/", 100, "a/");
+ JOIN("/a/", 100, "/a/");
+ JOIN("/a/", 100, "/a//");
+ JOIN("//a/", 100, "//a//");
+}
+
+TEST(path_util, JoinTriple)
+{
+ JOIN("/a/b/c", 100, "/a", "b", "c");
+ JOIN("/a/b/c", 100, "/a/", "/b/", "/c");
+ JOIN("/a/b/c", 100, "/a/b/", "/c");
+ JOIN("/a/b/c", 100, "/a/b/c");
+ JOIN("/a/b/c", 100, "/", "a/b/c");
+
+ JOIN("/a/b/c/", 100, "/a/", "/b/", "/c/");
+ JOIN("/a/b/c/", 100, "/a/b/c/");
+ JOIN("/a/b/c/", 100, "/a/b/", "/c/");
+ JOIN("/a/b/c/", 100, "/a/b/c", "/");
+ JOIN("/a/b/c/", 100, "/", "a/b/c", "/");
+}
+
+TEST(path_util, JoinTruncateShort)
+{
+ JOIN("", 1, "/");
+ JOIN("/", 2, "/");
+ JOIN("a", 2, "", "aa");
+ JOIN("a", 2, "", "a/");
+ JOIN("a/b", 4, "a", "bc");
+ JOIN("ab/", 4, "ab", "c");
+ JOIN("/a/", 4, "/a", "b");
+ JOIN("/a/", 4, "/a/", "b/");
+ JOIN("/a/", 4, "/a", "/b/");
+ JOIN("/a/", 4, "/", "a/b/");
+ JOIN("//a", 4, "//", "a/b/");
+
+ JOIN("/a/b", 5, "/a", "b", "c");
+}
+
+TEST(path_util, JoinTruncateLong)
+{
+ JOIN("", 1, "//", "//longer", "path");
+ JOIN("/", 2, "//", "//longer", "path");
+ JOIN("//", 3, "//", "//longer", "path");
+ JOIN("//l", 4, "//", "//longer", "path");
+ /* snip */
+ JOIN("//longe", 8, "//", "//longer", "path");
+ JOIN("//longer", 9, "//", "//longer", "path");
+ JOIN("//longer/", 10, "//", "//longer", "path");
+ JOIN("//longer/p", 11, "//", "//longer", "path");
+ JOIN("//longer/pa", 12, "//", "//longer", "path");
+ JOIN("//longer/pat", 13, "//", "//longer", "path");
+ JOIN("//longer/path", 14, "//", "//longer", "path"); // not truncated
+ JOIN("//longer/path", 14, "//", "//longer", "path/");
+ JOIN("//longer/path/", 15, "//", "//longer", "path/"); // not truncated
+ JOIN("//longer/path/", 15, "//", "//longer", "path/", "trunc");
+ JOIN("//longer/path/t", 16, "//", "//longer", "path/", "trunc");
+}
+
+TEST(path_util, JoinComplex)
+{
+ JOIN("/a/b/c/d/e/f/g/", 100, "/", "\\a/b", "//////c/d", "", "e\\\\", "f", "g//");
+ JOIN("/aa/bb/cc/dd/ee/ff/gg/", 100, "/", "\\aa/bb", "//////cc/dd", "", "ee\\\\", "ff", "gg//");
+ JOIN("1/2/3/", 100, "1", "////////", "", "2", "3\\");
+}
+
+#undef JOIN
+
+/* BLI_path_frame */
+TEST(path_util, Frame)
+{
+ bool ret;
+
+ {
+ char path[FILE_MAX] = "";
+ ret = BLI_path_frame(path, 123, 1);
+ EXPECT_TRUE(ret);
+ EXPECT_STREQ("123", path);
+ }
+
+ {
+ char path[FILE_MAX] = "";
+ ret = BLI_path_frame(path, 123, 12);
+ EXPECT_TRUE(ret);
+ EXPECT_STREQ("000000000123", path);
+ }
+
+ {
+ char path[FILE_MAX] = "test_";
+ ret = BLI_path_frame(path, 123, 1);
+ EXPECT_TRUE(ret);
+ EXPECT_STREQ("test_123", path);
+ }
+
+ {
+ char path[FILE_MAX] = "test_";
+ ret = BLI_path_frame(path, 1, 12);
+ EXPECT_TRUE(ret);
+ EXPECT_STREQ("test_000000000001", path);
+ }
+
+ {
+ char path[FILE_MAX] = "test_############";
+ ret = BLI_path_frame(path, 1, 0);
+ EXPECT_TRUE(ret);
+ EXPECT_STREQ("test_000000000001", path);
+ }
+
+ {
+ char path[FILE_MAX] = "test_#_#_middle";
+ ret = BLI_path_frame(path, 123, 0);
+ EXPECT_TRUE(ret);
+ EXPECT_STREQ("test_#_123_middle", path);
+ }
+
+ /* intentionally fail */
+ {
+ char path[FILE_MAX] = "";
+ ret = BLI_path_frame(path, 123, 0);
+ EXPECT_FALSE(ret);
+ EXPECT_STREQ("", path);
+ }
+
+ {
+ char path[FILE_MAX] = "test_middle";
+ ret = BLI_path_frame(path, 123, 0);
+ EXPECT_FALSE(ret);
+ EXPECT_STREQ("test_middle", path);
+ }
+
+ /* negative frame numbers */
+ {
+ char path[FILE_MAX] = "test_####";
+ ret = BLI_path_frame(path, -1, 4);
+ EXPECT_TRUE(ret);
+ EXPECT_STREQ("test_-0001", path);
+ }
+ {
+ char path[FILE_MAX] = "test_####";
+ ret = BLI_path_frame(path, -100, 4);
+ EXPECT_TRUE(ret);
+ EXPECT_STREQ("test_-0100", path);
+ }
+}
+
+/* BLI_split_dirfile */
+TEST(path_util, SplitDirfile)
+{
+ {
+ const char *path = "";
+ char dir[FILE_MAX], file[FILE_MAX];
+ BLI_split_dirfile(path, dir, file, sizeof(dir), sizeof(file));
+ EXPECT_STREQ("", dir);
+ EXPECT_STREQ("", file);
+ }
+
+ {
+ const char *path = "/";
+ char dir[FILE_MAX], file[FILE_MAX];
+ BLI_split_dirfile(path, dir, file, sizeof(dir), sizeof(file));
+ EXPECT_STREQ("/", dir);
+ EXPECT_STREQ("", file);
+ }
+
+ {
+ const char *path = "fileonly";
+ char dir[FILE_MAX], file[FILE_MAX];
+ BLI_split_dirfile(path, dir, file, sizeof(dir), sizeof(file));
+ EXPECT_STREQ("", dir);
+ EXPECT_STREQ("fileonly", file);
+ }
+
+ {
+ const char *path = "dironly/";
+ char dir[FILE_MAX], file[FILE_MAX];
+ BLI_split_dirfile(path, dir, file, sizeof(dir), sizeof(file));
+ EXPECT_STREQ("dironly/", dir);
+ EXPECT_STREQ("", file);
+ }
+
+ {
+ const char *path = "/a/b";
+ char dir[FILE_MAX], file[FILE_MAX];
+ BLI_split_dirfile(path, dir, file, sizeof(dir), sizeof(file));
+ EXPECT_STREQ("/a/", dir);
+ EXPECT_STREQ("b", file);
+ }
+
+ {
+ const char *path = "/dirtoobig/filetoobig";
+ char dir[5], file[5];
+ BLI_split_dirfile(path, dir, file, sizeof(dir), sizeof(file));
+ EXPECT_STREQ("/dir", dir);
+ EXPECT_STREQ("file", file);
+
+ BLI_split_dirfile(path, dir, file, 1, 1);
+ EXPECT_STREQ("", dir);
+ EXPECT_STREQ("", file);
+ }
+}
+
+#define PATH_FRAME_STRIP(input_path, expect_path, expect_ext) \
+ { \
+ char path[FILE_MAX]; \
+ char ext[FILE_MAX]; \
+ BLI_strncpy(path, (input_path), FILE_MAX); \
+ BLI_path_frame_strip(path, ext); \
+ EXPECT_STREQ(path, expect_path); \
+ EXPECT_STREQ(ext, expect_ext); \
+ } \
+ ((void)0)
+
+/* BLI_path_frame_strip */
+TEST(path_util, PathFrameStrip)
+{
+ PATH_FRAME_STRIP("", "", "");
+ PATH_FRAME_STRIP("nonum.abc", "nonum", ".abc");
+ PATH_FRAME_STRIP("fileonly.001.abc", "fileonly.###", ".abc");
+ PATH_FRAME_STRIP("/abspath/to/somefile.001.abc", "/abspath/to/somefile.###", ".abc");
+ PATH_FRAME_STRIP("/ext/longer/somefile.001.alembic", "/ext/longer/somefile.###", ".alembic");
+ PATH_FRAME_STRIP("/ext/shorter/somefile.123001.abc", "/ext/shorter/somefile.######", ".abc");
+}
+#undef PATH_FRAME_STRIP
+
+#define PATH_EXTENSION_CHECK(input_path, input_ext, expect_ext) \
+ { \
+ const bool ret = BLI_path_extension_check(input_path, input_ext); \
+ if (strcmp(input_ext, expect_ext) == 0) { \
+ EXPECT_TRUE(ret); \
+ } \
+ else { \
+ EXPECT_FALSE(ret); \
+ } \
+ } \
+ ((void)0)
+
+/* BLI_path_extension_check */
+TEST(path_util, PathExtensionCheck)
+{
+ PATH_EXTENSION_CHECK("a/b/c.exe", ".exe", ".exe");
+ PATH_EXTENSION_CHECK("correct/path/to/file.h", ".h", ".h");
+ PATH_EXTENSION_CHECK("correct/path/to/file.BLEND", ".BLEND", ".BLEND");
+ PATH_EXTENSION_CHECK("../tricky/path/to/file.h", ".h", ".h");
+ PATH_EXTENSION_CHECK("../dirty//../path\\to/file.h", ".h", ".h");
+ PATH_EXTENSION_CHECK("a/b/c.veryveryverylonglonglongextension",
+ ".veryveryverylonglonglongextension",
+ ".veryveryverylonglonglongextension");
+ PATH_EXTENSION_CHECK("filename.PNG", "pnG", "pnG");
+ PATH_EXTENSION_CHECK("a/b/c.h.exe", ".exe", ".exe");
+ PATH_EXTENSION_CHECK("a/b/c.h.exe", "exe", "exe");
+ PATH_EXTENSION_CHECK("a/b/c.exe", "c.exe", "c.exe");
+ PATH_EXTENSION_CHECK("a/b/noext", "noext", "noext");
+
+ PATH_EXTENSION_CHECK("a/b/c.exe", ".png", ".exe");
+ PATH_EXTENSION_CHECK("a/b/c.exe", "c.png", ".exe");
+ PATH_EXTENSION_CHECK("a/b/s.l", "l.s", "s.l");
+ PATH_EXTENSION_CHECK(".hiddenfolder", "", ".hiddenfolder");
+ PATH_EXTENSION_CHECK("../dirty//../path\\to/actual.h.file.ext", ".h", ".ext");
+ PATH_EXTENSION_CHECK("..\\dirty//../path//to/.hiddenfile.JPEG", ".hiddenfile", ".JPEG");
+}
+#undef PATH_EXTENSION_CHECK
+
+#define PATH_FRAME_CHECK_CHARS(input_path, expect_hasChars) \
+ { \
+ const bool ret = BLI_path_frame_check_chars(input_path); \
+ if (expect_hasChars) { \
+ EXPECT_TRUE(ret); \
+ } \
+ else { \
+ EXPECT_FALSE(ret); \
+ } \
+ } \
+ ((void)0)
+
+/* BLI_path_frame_check_chars */
+TEST(path_util, PathFrameCheckChars)
+{
+ PATH_FRAME_CHECK_CHARS("a#", true);
+ PATH_FRAME_CHECK_CHARS("aaaaa#", true);
+ PATH_FRAME_CHECK_CHARS("#aaaaa", true);
+ PATH_FRAME_CHECK_CHARS("a##.###", true);
+ PATH_FRAME_CHECK_CHARS("####.abc#", true);
+ PATH_FRAME_CHECK_CHARS("path/to/chars/a#", true);
+ PATH_FRAME_CHECK_CHARS("path/to/chars/123#123.exe", true);
+
+ PATH_FRAME_CHECK_CHARS("&", false);
+ PATH_FRAME_CHECK_CHARS("\35", false);
+ PATH_FRAME_CHECK_CHARS("path#/to#/chars#/$.h", false);
+ PATH_FRAME_CHECK_CHARS("path#/to#/chars#/nochars.h", false);
+ PATH_FRAME_CHECK_CHARS("..\\dirty\\path#/..//to#\\chars#/nochars.h", false);
+ PATH_FRAME_CHECK_CHARS("..\\dirty\\path#/..//to#/chars#\\nochars.h", false);
+}
+#undef PATH_FRAME_CHECK_CHARS
+
+#define PATH_FRAME_RANGE(input_path, sta, end, digits, expect_outpath) \
+ { \
+ char path[FILE_MAX]; \
+ bool ret; \
+ BLI_strncpy(path, input_path, FILE_MAX); \
+ ret = BLI_path_frame_range(path, sta, end, digits); \
+ if (expect_outpath == NULL) { \
+ EXPECT_FALSE(ret); \
+ } \
+ else { \
+ EXPECT_TRUE(ret); \
+ EXPECT_STREQ(path, expect_outpath); \
+ } \
+ } \
+ ((void)0)
+
+/* BLI_path_frame_range */
+TEST(path_util, PathFrameRange)
+{
+ int dummy = -1;
+ PATH_FRAME_RANGE("#", 1, 2, dummy, "1-2");
+ PATH_FRAME_RANGE("##", 1, 2, dummy, "01-02");
+ PATH_FRAME_RANGE("##", 1000, 2000, dummy, "1000-2000");
+ PATH_FRAME_RANGE("###", 100, 200, dummy, "100-200");
+ PATH_FRAME_RANGE("###", 8, 9, dummy, "008-009");
+
+ PATH_FRAME_RANGE("", 100, 200, 1, "100-200");
+ PATH_FRAME_RANGE("", 123, 321, 4, "0123-0321");
+ PATH_FRAME_RANGE("", 1, 0, 20, "00000000000000000001-00000000000000000000");
+}
+#undef PATH_FRAME_RANGE
+
+#define PATH_FRAME_GET(input_path, expect_frame, expect_numdigits, expect_pathisvalid) \
+ { \
+ char path[FILE_MAX]; \
+ int out_frame = -1, out_numdigits = -1; \
+ BLI_strncpy(path, input_path, FILE_MAX); \
+ const bool ret = BLI_path_frame_get(path, &out_frame, &out_numdigits); \
+ if (expect_pathisvalid) { \
+ EXPECT_TRUE(ret); \
+ } \
+ else { \
+ EXPECT_FALSE(ret); \
+ } \
+ EXPECT_EQ(out_frame, expect_frame); \
+ EXPECT_EQ(out_numdigits, expect_numdigits); \
+ } \
+ ((void)0)
+
+/* BLI_path_frame_get */
+TEST(path_util, PathFrameGet)
+{
+ PATH_FRAME_GET("001.avi", 1, 3, true);
+ PATH_FRAME_GET("0000299.ext", 299, 7, true);
+ PATH_FRAME_GET("path/to/frame_2810.dummy_quite_long_extension", 2810, 4, true);
+ PATH_FRAME_GET("notframe_7_frame00018.bla", 18, 5, true);
+
+ PATH_FRAME_GET("", -1, -1, false);
+}
+#undef PATH_FRAME_GET
+
+/* BLI_path_extension */
+TEST(path_util, PathExtension)
+{
+ EXPECT_EQ(NULL, BLI_path_extension("some.def/file"));
+ EXPECT_EQ(NULL, BLI_path_extension("Text"));
+ EXPECT_EQ(NULL, BLI_path_extension("Text…001"));
+
+ EXPECT_STREQ(".", BLI_path_extension("some/file."));
+ EXPECT_STREQ(".gz", BLI_path_extension("some/file.tar.gz"));
+ EXPECT_STREQ(".abc", BLI_path_extension("some.def/file.abc"));
+ EXPECT_STREQ(".abc", BLI_path_extension("C:\\some.def\\file.abc"));
+ EXPECT_STREQ(".001", BLI_path_extension("Text.001"));
+}
diff --git a/source/blender/blenlib/tests/BLI_polyfill_2d_test.cc b/source/blender/blenlib/tests/BLI_polyfill_2d_test.cc
new file mode 100644
index 00000000000..a5949c58037
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_polyfill_2d_test.cc
@@ -0,0 +1,751 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+/* Use to write out OBJ files, handy for checking output */
+// #define USE_OBJ_PREVIEW
+
+/* test every possible offset and reverse */
+#define USE_COMBINATIONS_ALL
+#define USE_BEAUTIFY
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_array_utils.h"
+#include "BLI_edgehash.h"
+#include "BLI_math.h"
+#include "BLI_polyfill_2d.h"
+#include "BLI_utildefines.h"
+
+#ifdef USE_OBJ_PREVIEW
+# include "BLI_string.h"
+#endif
+
+#ifdef USE_BEAUTIFY
+# include "BLI_heap.h"
+# include "BLI_memarena.h"
+# include "BLI_polyfill_2d_beautify.h"
+#endif
+
+static void polyfill_to_obj(const char *id,
+ const float poly[][2],
+ const unsigned int poly_tot,
+ const unsigned int tris[][3],
+ const unsigned int tris_tot);
+
+/* -------------------------------------------------------------------- */
+/* test utility functions */
+
+#define TRI_ERROR_VALUE (unsigned int)-1
+
+static void test_valid_polyfill_prepare(unsigned int tris[][3], unsigned int tris_tot)
+{
+ unsigned int i;
+ for (i = 0; i < tris_tot; i++) {
+ unsigned int j;
+ for (j = 0; j < 3; j++) {
+ tris[i][j] = TRI_ERROR_VALUE;
+ }
+ }
+}
+
+/**
+ * Basic check for face index values:
+ *
+ * - no duplicates.
+ * - all tris set.
+ * - all verts used at least once.
+ */
+static void test_polyfill_simple(const float poly[][2],
+ const unsigned int poly_tot,
+ const unsigned int tris[][3],
+ const unsigned int tris_tot)
+{
+ unsigned int i;
+ int *tot_used = (int *)MEM_callocN(poly_tot * sizeof(int), __func__);
+ for (i = 0; i < tris_tot; i++) {
+ unsigned int j;
+ for (j = 0; j < 3; j++) {
+ EXPECT_NE(TRI_ERROR_VALUE, tris[i][j]);
+ tot_used[tris[i][j]] += 1;
+ }
+ EXPECT_NE(tris[i][0], tris[i][1]);
+ EXPECT_NE(tris[i][1], tris[i][2]);
+ EXPECT_NE(tris[i][2], tris[i][0]);
+ }
+ for (i = 0; i < poly_tot; i++) {
+ EXPECT_NE(0, tot_used[i]);
+ }
+ MEM_freeN(tot_used);
+}
+
+static void test_polyfill_topology(const float poly[][2],
+ const unsigned int poly_tot,
+ const unsigned int tris[][3],
+ const unsigned int tris_tot)
+{
+ EdgeHash *edgehash = BLI_edgehash_new(__func__);
+ EdgeHashIterator *ehi;
+ unsigned int i;
+ for (i = 0; i < tris_tot; i++) {
+ unsigned int j;
+ for (j = 0; j < 3; j++) {
+ const unsigned int v1 = tris[i][j];
+ const unsigned int v2 = tris[i][(j + 1) % 3];
+ void **p = BLI_edgehash_lookup_p(edgehash, v1, v2);
+ if (p) {
+ *p = (void *)((intptr_t)*p + (intptr_t)1);
+ }
+ else {
+ BLI_edgehash_insert(edgehash, v1, v2, (void *)(intptr_t)1);
+ }
+ }
+ }
+ EXPECT_EQ(BLI_edgehash_len(edgehash), poly_tot + (poly_tot - 3));
+
+ for (i = 0; i < poly_tot; i++) {
+ const unsigned int v1 = i;
+ const unsigned int v2 = (i + 1) % poly_tot;
+ void **p = BLI_edgehash_lookup_p(edgehash, v1, v2);
+ EXPECT_NE((void *)p, nullptr);
+ EXPECT_EQ((intptr_t)*p, 1);
+ }
+
+ for (ehi = BLI_edgehashIterator_new(edgehash), i = 0; BLI_edgehashIterator_isDone(ehi) == false;
+ BLI_edgehashIterator_step(ehi), i++) {
+ void **p = BLI_edgehashIterator_getValue_p(ehi);
+ EXPECT_TRUE(ELEM((intptr_t)*p, 1, 2));
+ }
+
+ BLI_edgehashIterator_free(ehi);
+ BLI_edgehash_free(edgehash, NULL);
+}
+
+/**
+ * Check all faces are flipped the same way
+ */
+static void test_polyfill_winding(const float poly[][2],
+ const unsigned int poly_tot,
+ const unsigned int tris[][3],
+ const unsigned int tris_tot)
+{
+ unsigned int i;
+ unsigned int count[2] = {0, 0};
+ for (i = 0; i < tris_tot; i++) {
+ float winding_test = cross_tri_v2(poly[tris[i][0]], poly[tris[i][1]], poly[tris[i][2]]);
+ if (fabsf(winding_test) > FLT_EPSILON) {
+ count[winding_test < 0.0f] += 1;
+ }
+ }
+ EXPECT_TRUE(ELEM(0, count[0], count[1]));
+}
+
+/**
+ * Check the accumulated triangle area is close to the original area.
+ */
+static void test_polyfill_area(const float poly[][2],
+ const unsigned int poly_tot,
+ const unsigned int tris[][3],
+ const unsigned int tris_tot)
+{
+ unsigned int i;
+ const float area_tot = area_poly_v2(poly, poly_tot);
+ float area_tot_tris = 0.0f;
+ const float eps_abs = 0.00001f;
+ const float eps = area_tot > 1.0f ? (area_tot * eps_abs) : eps_abs;
+ for (i = 0; i < tris_tot; i++) {
+ area_tot_tris += area_tri_v2(poly[tris[i][0]], poly[tris[i][1]], poly[tris[i][2]]);
+ }
+ EXPECT_NEAR(area_tot, area_tot_tris, eps);
+}
+
+/* -------------------------------------------------------------------- */
+/* Macro and helpers to manage checking */
+/**
+ * Main template for polyfill testing.
+ */
+static void test_polyfill_template_check(const char *id,
+ bool is_degenerate,
+ const float poly[][2],
+ const unsigned int poly_tot,
+ const unsigned int tris[][3],
+ const unsigned int tris_tot)
+{
+ test_polyfill_simple(poly, poly_tot, tris, tris_tot);
+ test_polyfill_topology(poly, poly_tot, tris, tris_tot);
+ if (!is_degenerate) {
+ test_polyfill_winding(poly, poly_tot, tris, tris_tot);
+
+ test_polyfill_area(poly, poly_tot, tris, tris_tot);
+ }
+ polyfill_to_obj(id, poly, poly_tot, tris, tris_tot);
+}
+
+static void test_polyfill_template(const char *id,
+ bool is_degenerate,
+ const float poly[][2],
+ const unsigned int poly_tot,
+ unsigned int tris[][3],
+ const unsigned int tris_tot)
+{
+ test_valid_polyfill_prepare(tris, tris_tot);
+ BLI_polyfill_calc(poly, poly_tot, 0, tris);
+
+ /* check all went well */
+ test_polyfill_template_check(id, is_degenerate, poly, poly_tot, tris, tris_tot);
+
+#ifdef USE_BEAUTIFY
+ /* check beautify gives good results too */
+ {
+ MemArena *pf_arena = BLI_memarena_new(BLI_POLYFILL_ARENA_SIZE, __func__);
+ Heap *pf_heap = BLI_heap_new_ex(BLI_POLYFILL_ALLOC_NGON_RESERVE);
+
+ BLI_polyfill_beautify(poly, poly_tot, tris, pf_arena, pf_heap);
+
+ test_polyfill_template_check(id, is_degenerate, poly, poly_tot, tris, tris_tot);
+
+ BLI_memarena_free(pf_arena);
+ BLI_heap_free(pf_heap, NULL);
+ }
+#endif
+}
+
+static void test_polyfill_template_flip_sign(const char *id,
+ bool is_degenerate,
+ const float poly[][2],
+ const unsigned int poly_tot,
+ unsigned int tris[][3],
+ const unsigned int tris_tot)
+{
+ float(*poly_copy)[2] = (float(*)[2])MEM_mallocN(sizeof(float[2]) * poly_tot, id);
+ for (int flip_x = 0; flip_x < 2; flip_x++) {
+ for (int flip_y = 0; flip_y < 2; flip_y++) {
+ float sign_x = flip_x ? -1.0f : 1.0f;
+ float sign_y = flip_y ? -1.0f : 1.0f;
+ for (int i = 0; i < poly_tot; i++) {
+ poly_copy[i][0] = poly[i][0] * sign_x;
+ poly_copy[i][1] = poly[i][1] * sign_y;
+ }
+ test_polyfill_template(id, is_degenerate, poly_copy, poly_tot, tris, tris_tot);
+ }
+ }
+ MEM_freeN(poly_copy);
+}
+
+#ifdef USE_COMBINATIONS_ALL
+static void test_polyfill_template_main(const char *id,
+ bool is_degenerate,
+ const float poly[][2],
+ const unsigned int poly_tot,
+ unsigned int tris[][3],
+ const unsigned int tris_tot)
+{
+ /* overkill? - try at _every_ offset & reverse */
+ unsigned int poly_reverse;
+ float(*poly_copy)[2] = (float(*)[2])MEM_mallocN(sizeof(float[2]) * poly_tot, id);
+ float tmp[2];
+
+ memcpy(poly_copy, poly, sizeof(float[2]) * poly_tot);
+
+ for (poly_reverse = 0; poly_reverse < 2; poly_reverse++) {
+ unsigned int poly_cycle;
+
+ if (poly_reverse) {
+ BLI_array_reverse(poly_copy, poly_tot);
+ }
+
+ for (poly_cycle = 0; poly_cycle < poly_tot; poly_cycle++) {
+ // printf("polytest %s ofs=%d, reverse=%d\n", id, poly_cycle, poly_reverse);
+ test_polyfill_template_flip_sign(id, is_degenerate, poly, poly_tot, tris, tris_tot);
+
+ /* cycle */
+ copy_v2_v2(tmp, poly_copy[0]);
+ memmove(&poly_copy[0], &poly_copy[1], (poly_tot - 1) * sizeof(float[2]));
+ copy_v2_v2(poly_copy[poly_tot - 1], tmp);
+ }
+ }
+
+ MEM_freeN(poly_copy);
+}
+#else /* USE_COMBINATIONS_ALL */
+static void test_polyfill_template_main(const char *id,
+ bool is_degenerate,
+ const float poly[][2],
+ const unsigned int poly_tot,
+ unsigned int tris[][3],
+ const unsigned int tris_tot)
+{
+ test_polyfill_template_flip_sign(id, is_degenerate, poly, poly_tot, tris, tris_tot);
+}
+#endif /* USE_COMBINATIONS_ALL */
+
+#define TEST_POLYFILL_TEMPLATE_STATIC(poly, is_degenerate) \
+ { \
+ unsigned int tris[POLY_TRI_COUNT(ARRAY_SIZE(poly))][3]; \
+ const unsigned int poly_tot = ARRAY_SIZE(poly); \
+ const unsigned int tris_tot = ARRAY_SIZE(tris); \
+ const char *id = typeid(*this).name(); \
+\
+ test_polyfill_template_main(id, is_degenerate, poly, poly_tot, tris, tris_tot); \
+ } \
+ (void)0
+
+/* -------------------------------------------------------------------- */
+/* visualisation functions (not needed for testing) */
+
+#ifdef USE_OBJ_PREVIEW
+static void polyfill_to_obj(const char *id,
+ const float poly[][2],
+ const unsigned int poly_tot,
+ const unsigned int tris[][3],
+ const unsigned int tris_tot)
+{
+ char path[1024];
+ FILE *f;
+ unsigned int i;
+
+ BLI_snprintf(path, sizeof(path), "%s.obj", id);
+
+ f = fopen(path, "w");
+ if (!f) {
+ return;
+ }
+
+ for (i = 0; i < poly_tot; i++) {
+ fprintf(f, "v %f %f 0.0\n", UNPACK2(poly[i]));
+ }
+
+ for (i = 0; i < tris_tot; i++) {
+ fprintf(f, "f %u %u %u\n", UNPACK3_EX(1 +, tris[i], ));
+ }
+
+ fclose(f);
+}
+#else
+static void polyfill_to_obj(const char *id,
+ const float poly[][2],
+ const unsigned int poly_tot,
+ const unsigned int tris[][3],
+ const unsigned int tris_tot)
+{
+ (void)id;
+ (void)poly, (void)poly_tot;
+ (void)tris, (void)tris_tot;
+}
+#endif /* USE_OBJ_PREVIEW */
+
+/* -------------------------------------------------------------------- */
+/* tests */
+
+/**
+ * Script to generate the data below:
+ *
+ * \code{.py}
+ * # This example assumes we have a mesh object in edit-mode
+ *
+ * import bpy
+ * import bmesh
+ *
+ * obj = bpy.context.edit_object
+ * me = obj.data
+ * bm = bmesh.from_edit_mesh(me)
+ *
+ * def clean_float(num):
+ * if int(num) == num:
+ * return str(int(num))
+ * prec = 1
+ * while True:
+ * text = f"{num:.{prec}f}"
+ * if float(text) == num:
+ * return text
+ * prec += 1
+ *
+ * for f in bm.faces:
+ * if f.select:
+ * print(f"\t// data for face: {f.index}")
+ * print("\tconst float poly[][2] = {", end="")
+ * coords = [[clean_float(num) for num in l.vert.co[0:2]] for l in f.loops]
+ * print("\t ", end="")
+ * for i, (x, y) in enumerate(coords):
+ * if (i % 2) == 0:
+ * print("\n\t ", end="")
+ * print(f"{{{x}, {y}}}", end=",")
+ * print("\n\t};")
+ * \endcode
+ */
+
+#define POLY_TRI_COUNT(len) ((len)-2)
+
+/* A counterclockwise triangle */
+TEST(polyfill2d, TriangleCCW)
+{
+ const float poly[][2] = {{0, 0}, {0, 1}, {1, 0}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* A counterclockwise square */
+TEST(polyfill2d, SquareCCW)
+{
+ const float poly[][2] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* A clockwise square */
+TEST(polyfill2d, SquareCW)
+{
+ const float poly[][2] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Starfleet insigna */
+TEST(polyfill2d, Starfleet)
+{
+ const float poly[][2] = {{0, 0}, {0.6f, 0.4f}, {1, 0}, {0.5f, 1}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Starfleet insigna with repeated point */
+TEST(polyfill2d, StarfleetDegenerate)
+{
+ const float poly[][2] = {{0, 0}, {0.6f, 0.4f}, {0.6f, 0.4f}, {1, 0}, {0.5f, 1}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Three collinear points */
+TEST(polyfill2d, 3Colinear)
+{
+ const float poly[][2] = {{0, 0}, {1, 0}, {2, 0}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Four collinear points */
+TEST(polyfill2d, 4Colinear)
+{
+ const float poly[][2] = {{0, 0}, {1, 0}, {2, 0}, {3, 0}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Non-consecutive collinear points */
+TEST(polyfill2d, UnorderedColinear)
+{
+ const float poly[][2] = {{0, 0}, {1, 1}, {2, 0}, {3, 1}, {4, 0}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Plus shape */
+TEST(polyfill2d, PlusShape)
+{
+ const float poly[][2] = {
+ {1, 0},
+ {2, 0},
+ {2, 1},
+ {3, 1},
+ {3, 2},
+ {2, 2},
+ {2, 3},
+ {1, 3},
+ {1, 2},
+ {0, 2},
+ {0, 1},
+ {1, 1},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Star shape */
+TEST(polyfill2d, StarShape)
+{
+ const float poly[][2] = {{4, 0}, {5, 3}, {8, 4}, {5, 5}, {4, 8}, {3, 5}, {0, 4}, {3, 3}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* U shape */
+TEST(polyfill2d, UShape)
+{
+ const float poly[][2] = {
+ {1, 0}, {2, 0}, {3, 1}, {3, 3}, {2, 3}, {2, 1}, {1, 1}, {1, 3}, {0, 3}, {0, 1}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Spiral */
+TEST(polyfill2d, Spiral)
+{
+ const float poly[][2] = {
+ {1, 0},
+ {4, 0},
+ {5, 1},
+ {5, 4},
+ {4, 5},
+ {1, 5},
+ {0, 4},
+ {0, 3},
+ {1, 2},
+ {2, 2},
+ {3, 3},
+ {1, 3},
+ {1, 4},
+ {4, 4},
+ {4, 1},
+ {0, 1},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Test case from http:# www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml */
+TEST(polyfill2d, TestFlipCode)
+{
+ const float poly[][2] = {
+ {0, 6},
+ {0, 0},
+ {3, 0},
+ {4, 1},
+ {6, 1},
+ {8, 0},
+ {12, 0},
+ {13, 2},
+ {8, 2},
+ {8, 4},
+ {11, 4},
+ {11, 6},
+ {6, 6},
+ {4, 3},
+ {2, 6},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Self-intersection */
+TEST(polyfill2d, SelfIntersect)
+{
+ const float poly[][2] = {{0, 0}, {1, 1}, {2, -1}, {3, 1}, {4, 0}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, true);
+}
+
+/* Self-touching */
+TEST(polyfill2d, SelfTouch)
+{
+ const float poly[][2] = {
+ {0, 0},
+ {4, 0},
+ {4, 4},
+ {2, 4},
+ {2, 3},
+ {3, 3},
+ {3, 1},
+ {1, 1},
+ {1, 3},
+ {2, 3},
+ {2, 4},
+ {0, 4},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Self-overlapping */
+TEST(polyfill2d, SelfOverlap)
+{
+ const float poly[][2] = {
+ {0, 0},
+ {4, 0},
+ {4, 4},
+ {1, 4},
+ {1, 3},
+ {3, 3},
+ {3, 1},
+ {1, 1},
+ {1, 3},
+ {3, 3},
+ {3, 4},
+ {0, 4},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, true);
+}
+
+/* Test case from http:# www.davdata.nl/math/polygons.html */
+TEST(polyfill2d, TestDavData)
+{
+ const float poly[][2] = {
+ {190, 480}, {140, 180}, {310, 100}, {330, 390}, {290, 390}, {280, 260}, {220, 260},
+ {220, 430}, {370, 430}, {350, 30}, {50, 30}, {160, 560}, {730, 510}, {710, 20},
+ {410, 30}, {470, 440}, {640, 410}, {630, 140}, {590, 140}, {580, 360}, {510, 370},
+ {510, 60}, {650, 70}, {660, 450}, {190, 480},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Issue 815, http:# code.google.com/p/libgdx/issues/detail?id=815 */
+TEST(polyfill2d, Issue815)
+{
+ const float poly[][2] = {
+ {-2.0f, 0.0f},
+ {-2.0f, 0.5f},
+ {0.0f, 1.0f},
+ {0.5f, 2.875f},
+ {1.0f, 0.5f},
+ {1.5f, 1.0f},
+ {2.0f, 1.0f},
+ {2.0f, 0.0f},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Issue 207, comment #1, http:# code.google.com/p/libgdx/issues/detail?id=207#c1 */
+TEST(polyfill2d, Issue207_1)
+{
+ const float poly[][2] = {
+ {72.42465f, 197.07095f},
+ {78.485535f, 189.92776f},
+ {86.12059f, 180.92929f},
+ {99.68253f, 164.94557f},
+ {105.24325f, 165.79604f},
+ {107.21862f, 166.09814f},
+ {112.41958f, 162.78253f},
+ {113.73238f, 161.94562f},
+ {123.29477f, 167.93805f},
+ {126.70667f, 170.07617f},
+ {73.22717f, 199.51062f},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, true);
+}
+
+/* Issue 207, comment #11, http:# code.google.com/p/libgdx/issues/detail?id=207#c11 */
+/* Also on issue 1081, http:# code.google.com/p/libgdx/issues/detail?id=1081 */
+TEST(polyfill2d, Issue207_11)
+{
+ const float poly[][2] = {
+ {2400.0f, 480.0f}, {2400.0f, 176.0f}, {1920.0f, 480.0f},
+ {1920.0459f, 484.22314f}, {1920.1797f, 487.91016f}, {1920.3955f, 491.0874f},
+ {1920.6875f, 493.78125f}, {1921.0498f, 496.01807f}, {1921.4766f, 497.82422f},
+ {1921.9619f, 499.22607f}, {1922.5f, 500.25f}, {1923.085f, 500.92236f},
+ {1923.7109f, 501.26953f}, {1924.3721f, 501.31787f}, {1925.0625f, 501.09375f},
+ {1925.7764f, 500.62354f}, {1926.5078f, 499.9336f}, {1927.251f, 499.0503f},
+ {1928.0f, 498.0f}, {1928.749f, 496.80908f}, {1929.4922f, 495.5039f},
+ {1930.2236f, 494.11084f}, {1930.9375f, 492.65625f}, {1931.6279f, 491.1665f},
+ {1932.2891f, 489.66797f}, {1932.915f, 488.187f}, {1933.5f, 486.75f},
+ {1934.0381f, 485.3833f}, {1934.5234f, 484.11328f}, {1934.9502f, 482.9663f},
+ {1935.3125f, 481.96875f}, {1935.6045f, 481.14697f}, {1935.8203f, 480.52734f},
+ {1935.9541f, 480.13623f}, {1936.0f, 480.0f}};
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Issue 1407, http:# code.google.com/p/libgdx/issues/detail?id=1407 */
+TEST(polyfill2d, Issue1407)
+{
+ const float poly[][2] = {
+ {3.914329f, 1.9008259f},
+ {4.414321f, 1.903619f},
+ {4.8973203f, 1.9063174f},
+ {5.4979978f, 1.9096732f},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Issue 1407, http:# code.google.com/p/libgdx/issues/detail?id=1407, */
+/* with an additional point to show what is happening. */
+TEST(polyfill2d, Issue1407_pt)
+{
+ const float poly[][2] = {
+ {3.914329f, 1.9008259f},
+ {4.414321f, 1.903619f},
+ {4.8973203f, 1.9063174f},
+ {5.4979978f, 1.9096732f},
+ {4, 4},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Simplified from Blender bug T40777 */
+TEST(polyfill2d, IssueT40777_colinear)
+{
+ const float poly[][2] = {
+ {0.7, 0.37}, {0.7, 0}, {0.76, 0}, {0.76, 0.4}, {0.83, 0.4}, {0.83, 0}, {0.88, 0},
+ {0.88, 0.4}, {0.94, 0.4}, {0.94, 0}, {1, 0}, {1, 0.4}, {0.03, 0.62}, {0.03, 0.89},
+ {0.59, 0.89}, {0.03, 1}, {0, 1}, {0, 0}, {0.03, 0}, {0.03, 0.37},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Blender bug T41986 */
+TEST(polyfill2d, IssueT41986_axis_align)
+{
+ const float poly[][2] = {
+ {-0.25, -0.07}, {-0.25, 0.27}, {-1.19, 0.14}, {-0.06, 0.73}, {0.17, 1.25},
+ {-0.25, 1.07}, {-0.38, 1.02}, {-0.25, 0.94}, {-0.40, 0.90}, {-0.41, 0.86},
+ {-0.34, 0.83}, {-0.25, 0.82}, {-0.66, 0.73}, {-0.56, 1.09}, {-0.25, 1.10},
+ {0.00, 1.31}, {-0.03, 1.47}, {-0.25, 1.53}, {0.12, 1.62}, {0.36, 1.07},
+ {0.12, 0.67}, {0.29, 0.57}, {0.44, 0.45}, {0.57, 0.29}, {0.66, 0.12},
+ {0.68, 0.06}, {0.57, -0.36}, {-0.25, -0.37}, {0.49, -0.74}, {-0.59, -1.21},
+ {-0.25, -0.15}, {-0.46, -0.52}, {-1.08, -0.83}, {-1.45, -0.33}, {-1.25, -0.04}};
+
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Blender bug T52834 */
+TEST(polyfill2d, IssueT52834_axis_align_co_linear)
+{
+ const float poly[][2] = {
+ {40, 0}, {36, 0}, {36, 5}, {35, 5}, {35, 0}, {30, 0}, {30, 5}, {29, 5},
+ {29, 0}, {24, 0}, {24, 3}, {23, 4}, {23, 0}, {18, 0}, {18, 5}, {17, 5},
+ {17, 0}, {12, 0}, {12, 5}, {11, 5}, {11, 0}, {6, 0}, {6, 5}, {5, 5},
+ {5, 0}, {0, 0}, {0, 5}, {-1, 5}, {-1, 0}, {-6, 0}, {-9, -3}, {-6, -3},
+ {-6, -2}, {-1, -2}, {0, -2}, {5, -2}, {6, -2}, {11, -2}, {12, -2}, {17, -2},
+ {18, -2}, {23, -2}, {24, -2}, {29, -2}, {30, -2}, {35, -2}, {36, -2}, {40, -2},
+ };
+
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Blender bug T67109 (version a). */
+/* Multiple versions are offset & rotated, this fails in cases where others works. */
+TEST(polyfill2d, IssueT67109_axis_align_co_linear_a)
+{
+ const float poly[][2] = {
+ {3.2060661, -11.438997},
+ {2.8720665, -5.796999},
+ {-2.8659325, -5.796999},
+ {-2.8659325, -8.307999},
+ {-3.2549324, -11.438997},
+ {-2.8659325, -5.4869995},
+ {2.8720665, -5.4869995},
+ {2.8720665, -2.9759989},
+ {2.8720665, -2.6659985},
+ {2.8720665, -0.15499878},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Blender bug T67109, (version b). */
+TEST(polyfill2d, IssueT67109_axis_align_co_linear_b)
+{
+ const float poly[][2] = {
+ {32.41416, -12.122593},
+ {28.094929, -8.477332},
+ {24.141455, -12.636018},
+ {25.96133, -14.366093},
+ {27.96254, -16.805279},
+ {23.916779, -12.422427},
+ {27.870255, -8.263744},
+ {26.050375, -6.533667},
+ {25.825695, -6.320076},
+ {24.00582, -4.5899982},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
+
+/* Blender bug T67109 (version c). */
+TEST(polyfill2d, IssueT67109_axis_align_co_linear_c)
+{
+ const float poly[][2] = {
+ {-67.10034, 43.677097},
+ {-63.253956, 61.399143},
+ {-80.98382, 66.36057},
+ {-83.15499, 58.601795},
+ {-87.06422, 49.263668},
+ {-80.71576, 67.31843},
+ {-62.985912, 62.35701},
+ {-60.81475, 70.11576},
+ {-60.546703, 71.07365},
+ {-58.37554, 78.83239},
+ };
+ TEST_POLYFILL_TEMPLATE_STATIC(poly, false);
+}
diff --git a/source/blender/blenlib/tests/BLI_ressource_strings.h b/source/blender/blenlib/tests/BLI_ressource_strings.h
new file mode 100644
index 00000000000..119aaeb0036
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_ressource_strings.h
@@ -0,0 +1,610 @@
+/* Apache License, Version 2.0 */
+
+#ifndef __BLENDER_TESTING_BLI_RESSOURCE_STRING_H__
+#define __BLENDER_TESTING_BLI_RESSOURCE_STRING_H__
+
+/* Data file, don't format. */
+/* clang-format off */
+
+const char words10k[] =
+"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam auctor ultrices purus tincidunt mollis. Vestibulum "
+"tincidunt imperdiet molestie. Vivamus posuere, risus ut mollis rutrum, lacus nulla mollis velit, consectetur auctor "
+"erat est in odio. Proin quis lobortis ex. Ut id quam lacus. Morbi ultrices orci quis sem suscipit tincidunt. Nullam "
+"ut molestie justo, vulputate placerat diam. Nunc tincidunt auctor venenatis. Phasellus placerat, odio ac dictum "
+"pretium, nisi odio tristique sem, sit amet hendrerit odio tortor eu felis. Duis placerat tristique neque, sit amet "
+"ornare nulla fermentum vel. Vivamus vitae rhoncus ante. Sed a dolor mauris. Nullam bibendum vehicula semper. Duis ut "
+"commodo nibh. Nulla sit amet eros feugiat, accumsan nisl a, ornare quam. In non magna orci. Curabitur finibus tempus "
+"semper. Aliquam fringilla arcu consectetur blandit vestibulum. Mauris mollis est arcu. Praesent pellentesque lacus "
+"bibendum massa commodo commodo. Aenean facilisis lobortis varius. Ut semper ullamcorper dui, at pellentesque felis. "
+"Duis accumsan sapien ut malesuada lacinia. Praesent elementum venenatis arcu in mattis. Nunc sagittis mauris risus, "
+"quis rutrum nisi egestas quis. Maecenas pharetra posuere auctor. Suspendisse mollis sollicitudin elit, id cursus "
+"massa bibendum eu. Integer tincidunt dolor non porttitor tempus. Donec lacinia sapien eu enim feugiat suscipit non "
+"malesuada diam. Suspendisse nec convallis elit. Nulla eu augue ultrices, consequat lorem at, malesuada magna. "
+"Aliquam sed tempor ipsum. Sed hendrerit nec lectus et pharetra. In felis sem, cursus at nunc in, tristique convallis "
+"purus. Praesent augue turpis, porttitor consequat risus ornare, laoreet commodo dui. Nulla congue ultrices sapien a "
+"cursus. Nulla facilisi. Integer lacinia enim sodales sem mattis, sit amet egestas lectus tincidunt. Ut quis nisl ut "
+"ex luctus fermentum quis et diam. Maecenas lectus leo, hendrerit eu facilisis et, mattis ut sem. Duis imperdiet nisl "
+"vitae urna consequat suscipit. Suspendisse sed viverra massa, dapibus condimentum sem. Morbi suscipit congue odio. "
+"Nullam eleifend fringilla nisl et semper. Sed eu neque ante. Sed eget viverra urna. Duis tempor laoreet interdum. "
+"Nunc fringilla aliquet urna sit amet commodo. Curabitur non orci nec libero egestas ullamcorper nec nec velit. Nam "
+"vitae ligula lobortis, vehicula nulla id, lacinia urna. Morbi id dignissim eros. Etiam eu risus in sem vestibulum "
+"dapibus ut mollis sem. Quisque ultricies pulvinar maximus. Proin risus turpis, auctor eget molestie nec, molestie a "
+"ipsum. Donec dapibus dui in lorem rhoncus, non rutrum neque convallis. Donec at tincidunt turpis, nec scelerisque "
+"lorem. Donec ac sapien mi. Sed commodo efficitur tempus. Maecenas eu lobortis diam. Phasellus enim nulla, ornare ac "
+"laoreet egestas, vestibulum ac arcu. Pellentesque ultrices mauris sem, a iaculis diam tristique id. Proin sed "
+"facilisis mauris. Aliquam nibh ex, varius in consequat laoreet, sollicitudin id diam. Vivamus semper ultrices sem "
+"non tempor. Sed hendrerit maximus malesuada. In ex orci, elementum non magna eget, congue sagittis tellus. Donec "
+"malesuada sem leo, quis malesuada risus blandit et. Praesent porta malesuada metus eget pretium. Vestibulum "
+"venenatis tempor tellus at varius. Donec mauris arcu, elementum vitae aliquet nec, ullamcorper vitae neque. Nunc eu "
+"viverra justo, sit amet viverra elit. Proin urna elit, luctus ut placerat quis, blandit vitae diam. Vestibulum id "
+"fringilla enim. Ut eleifend augue ante, ac euismod sapien luctus sit amet. Pellentesque mattis tortor ac rutrum "
+"malesuada. Sed et nulla id metus faucibus condimentum. Vestibulum cursus posuere vestibulum. Proin auctor arcu erat, "
+"quis porta sem dignissim a. Donec sed finibus ante. Integer porttitor pretium nunc, eu semper elit. Nam sit amet "
+"ornare urna. Suspendisse porta augue id massa luctus maximus. Fusce tellus ligula, finibus sed lacus eget, tristique "
+"mollis libero. Vivamus velit diam, faucibus vel fringilla vitae, ornare id lacus. Pellentesque vel sem quis nunc "
+"semper porta ut sit amet sapien. Integer nec leo at tortor ullamcorper pulvinar at ut ante. Fusce velit nisl, "
+"fermentum in tempus ac, gravida ac tellus. In aliquet sollicitudin erat, non vestibulum diam aliquam in. Duis purus "
+"justo, aliquet ut libero vel, egestas mollis nibh. Praesent sed tempor mauris, vel tempor augue. Morbi eu eros vel "
+"velit condimentum porttitor nec sit amet odio. Nunc suscipit risus at ex aliquam, in pretium mi maximus. Mauris "
+"sollicitudin sit amet arcu luctus maximus. Curabitur vehicula condimentum porta. Nunc consequat vitae urna vel "
+"gravida. Vivamus vitae mattis augue, sit amet blandit enim. Phasellus odio leo, cursus eget lacus sit amet, "
+"facilisis mattis tortor. Duis venenatis ante libero, eu condimentum urna viverra fermentum. Suspendisse libero leo, "
+"pretium eu leo at, imperdiet ultricies nunc. Fusce ante neque, feugiat id lacus sed, fringilla suscipit ligula. "
+"Phasellus cursus malesuada urna, vel ullamcorper massa suscipit vitae. In eu bibendum augue. Duis auctor posuere "
+"turpis nec vestibulum. Vestibulum nec dui in mi consequat auctor sed at nisl. Suspendisse tellus elit, congue ut "
+"facilisis vel, ornare id mauris. Integer rutrum fermentum neque, vitae pharetra metus consectetur in. Duis vitae "
+"lacus scelerisque, rhoncus nisl id, sagittis elit. Praesent lacinia libero ac ultricies tempus. Etiam ut maximus "
+"sapien. Maecenas sit amet ante auctor, feugiat diam non, vulputate diam. Nulla facilisi. Vestibulum id augue velit. "
+"Donec at elementum urna. Morbi elementum nunc in neque ornare, sit amet tempor mauris vulputate. Nunc mauris mauris, "
+"lobortis non nibh sed, gravida sollicitudin nunc. Nunc vel dolor non augue venenatis semper vitae non turpis. "
+"Praesent mattis elit eu interdum porttitor. Etiam quis magna magna. Praesent a ipsum est. Aenean at ligula vel leo "
+"faucibus pulvinar sed eget mauris. Nam accumsan blandit nibh, nec tincidunt nisl eleifend sit amet. Etiam ornare, "
+"arcu nec dictum volutpat, nulla orci porttitor orci, vel venenatis mi massa at erat. Maecenas eget accumsan nisl, "
+"quis ullamcorper turpis. Pellentesque sit amet mi aliquet, feugiat felis in, dictum urna. Cras nulla leo, congue vel "
+"consequat gravida, aliquet a nulla. Nulla commodo, nisi eu ultricies feugiat, justo velit tempor ligula, a tincidunt "
+"nisi tellus ut sapien. Sed eget ornare magna. Cras ut vehicula sapien. Quisque id malesuada urna, vitae congue ante. "
+"Donec nec leo pretium, finibus nibh a, porta lectus. Fusce arcu tellus, tempor semper sem id, aliquam fringilla "
+"ipsum. Ut massa ante, placerat quis sapien quis, sollicitudin blandit turpis. Aenean posuere ullamcorper massa. Nam "
+"faucibus egestas arcu. Vivamus vehicula auctor diam, eu placerat diam ullamcorper at. Nulla eu consequat elit, vel "
+"semper turpis. Curabitur rhoncus nunc vel vestibulum interdum. Nam augue neque, pharetra vel nisi dignissim, "
+"vehicula dapibus risus. Cras eget mattis nisi. Sed tempor posuere gravida. Proin sagittis a nisl eget gravida. "
+"Curabitur viverra dapibus arcu, sit amet rutrum nibh fringilla euismod. Donec vitae risus non lorem facilisis cursus "
+"eu eu quam. Donec quis lacus blandit, consectetur elit ut, sagittis ligula. Etiam dapibus ex sit amet elit commodo "
+"finibus. Suspendisse non finibus felis, non cursus libero. Vivamus semper aliquet velit vel elementum. Phasellus "
+"dictum, tortor id sagittis ultrices, ex dui porttitor tortor, nec mattis dolor sem nec mi. Ut aliquam consequat eros "
+"sit amet mollis. Nullam mollis venenatis porttitor. Donec sit amet velit at velit luctus auctor dictum in neque. Ut "
+"vulputate ultricies mollis. Pellentesque elementum augue dolor, non varius ligula tristique ac. Nullam eget mauris "
+"urna. Integer elementum eleifend pulvinar. Morbi gravida ante eget ornare faucibus. Mauris pulvinar consequat nunc "
+"vel accumsan. Curabitur egestas urna elit, ut accumsan magna dictum in. Nam neque mi, ornare sed leo at, tempor "
+"vulputate nunc. Nunc dignissim mauris id dui iaculis fringilla. Praesent malesuada tellus in dapibus feugiat. "
+"Vivamus posuere, nisi et consequat euismod, lorem augue iaculis velit, eget iaculis neque quam eu mi. Nullam ac "
+"hendrerit felis, non elementum ipsum. Aliquam erat volutpat. Proin vel molestie felis. Nullam luctus vel ante nec "
+"facilisis. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis et metus "
+"justo. Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut tristique sit amet elit et congue. Aenean "
+"quis elementum enim, vitae pharetra sem. Vestibulum vel finibus nisl, at consequat eros. In vitae mollis lacus, et "
+"pharetra elit. Mauris varius sapien quis tincidunt blandit. Proin non semper nibh. Aliquam non elit id felis laoreet "
+"interdum eget a risus. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. "
+"Suspendisse nisl tellus, mollis id erat vel, hendrerit volutpat nunc. Quisque scelerisque cursus tellus, nec "
+"placerat quam imperdiet in. Sed porttitor arcu vel ligula finibus, a vestibulum enim ultrices. Fusce imperdiet augue "
+"eget est vehicula porttitor. Quisque convallis odio vitae lorem porttitor iaculis. Ut dictum velit ac tortor "
+"lobortis ultrices. Vestibulum tincidunt vestibulum mauris, at fermentum elit imperdiet nec. Nunc finibus ornare "
+"lorem vel malesuada. Praesent arcu turpis, pulvinar sit amet accumsan quis, tincidunt vel justo. Pellentesque "
+"volutpat nec enim sit amet pulvinar. Nam eu libero dignissim, volutpat elit ut, semper tortor. Morbi pellentesque "
+"nisl lectus. In vel tellus sed sem luctus lobortis ut nec diam. Phasellus id semper sem. Phasellus in purus "
+"consequat, rhoncus mi mollis, finibus ligula. Fusce feugiat dictum consequat. Mauris egestas, est ut euismod "
+"consequat, arcu dui dignissim quam, pharetra dignissim orci dolor quis nisl. Nunc dapibus blandit urna non feugiat. "
+"Suspendisse non maximus augue. Quisque ut orci aliquet, vulputate massa eget, mattis diam. Etiam efficitur "
+"consectetur viverra. Nulla massa augue, elementum at turpis et, cursus ultricies risus. Suspendisse vel nibh "
+"placerat, imperdiet elit et, viverra ligula. Donec lorem lorem, hendrerit nec aliquam sit amet, scelerisque sit amet "
+"massa. Mauris convallis ullamcorper tortor sed malesuada. Fusce ultricies a turpis eu ornare. Suspendisse potenti. "
+"Sed non nulla condimentum, vulputate nisi nec, tincidunt arcu. Morbi erat leo, lobortis id odio ac, hendrerit "
+"sodales sem. Ut malesuada, lectus at posuere molestie, orci metus vehicula justo, mattis tincidunt arcu risus quis "
+"odio. Fusce non sem sed nisi consectetur finibus vitae quis diam. Vivamus a lacinia nisl. Praesent tempus nunc "
+"gravida, lacinia lacus in, lobortis massa. Aliquam gravida consequat nisi at fringilla. Quisque tortor tortor, "
+"tincidunt cursus lorem eget, ultrices ultricies lacus. Phasellus mattis iaculis elit, eget mattis nisl bibendum sed. "
+"Integer faucibus gravida nisl, ac consequat ex tempor at. Sed tempus elementum vestibulum. Suspendisse vitae enim "
+"semper, pulvinar diam eget, suscipit turpis. Maecenas ultricies, diam sed consectetur sagittis, diam sem cursus "
+"nisl, nec aliquet tellus augue quis ipsum. Cras vel lorem convallis, mattis risus at, placerat massa. Curabitur vel "
+"rutrum ligula. Quisque in nibh libero. Pellentesque diam tellus, consectetur eget quam ac, faucibus imperdiet odio. "
+"Sed tortor nulla, scelerisque non turpis nec, fringilla bibendum est. Etiam a urna eget erat tincidunt ultrices. "
+"Maecenas lorem odio, volutpat nec ligula id, hendrerit aliquam nulla. Aenean congue lacinia fermentum. Suspendisse "
+"sed interdum lacus. Fusce scelerisque posuere sagittis. Ut at semper tellus. Donec condimentum orci nunc, non "
+"fermentum purus volutpat eget. Maecenas elementum dapibus ante, eu suscipit quam imperdiet ut. Integer non congue "
+"elit. Sed venenatis, turpis varius commodo euismod, libero magna fringilla lacus, quis venenatis velit lectus sed "
+"augue. Morbi gravida orci odio, ut ornare massa sollicitudin a. Donec convallis mi et sapien tempor, non dapibus "
+"dolor fringilla. Aenean euismod rutrum turpis, et facilisis orci porttitor eu. Suspendisse in neque leo. Nulla "
+"facilisi. Etiam mollis orci nisl, quis scelerisque metus efficitur vehicula. Nam porta molestie tortor, sit amet "
+"consectetur leo vestibulum vel. Pellentesque a volutpat augue. Maecenas vel elementum ex, eget elementum leo. "
+"Curabitur at maximus metus, quis porttitor orci. Praesent auctor commodo elit, a dapibus tortor volutpat et. "
+"Praesent dictum posuere dolor sit amet molestie. Sed viverra augue nec eros mattis blandit. In quis sodales dolor. "
+"Donec sed purus ex. Fusce erat magna, efficitur ac tempus ac, lacinia quis augue. Aliquam porta efficitur est vel "
+"placerat. Phasellus egestas vel nunc eu consequat. Maecenas ligula arcu, molestie ut dui ut, ornare finibus felis. "
+"Duis condimentum non augue ut posuere. Aenean mattis eros ut ligula ornare finibus. Aliquam feugiat ut turpis a "
+"feugiat. Vestibulum eget sollicitudin orci, nec fermentum justo. Praesent efficitur est a metus bibendum, eget "
+"feugiat diam suscipit. Suspendisse sit amet ipsum ut purus feugiat pretium. Morbi nisl risus, ultricies sit amet "
+"ullamcorper euismod, commodo eu libero. Aenean fringilla ipsum nec orci rutrum aliquet. Aenean lacus ante, eleifend "
+"eu eleifend fringilla, elementum ac justo. Vestibulum tincidunt interdum lectus sit amet fermentum. Etiam rhoncus eu "
+"ante lacinia sagittis. Maecenas iaculis ut erat quis feugiat. Maecenas sed est vel tellus bibendum rutrum volutpat "
+"nec odio. Vivamus euismod augue nec purus euismod, mattis finibus nisi finibus. Donec quis ultrices massa. Quisque "
+"at nisl faucibus, facilisis tellus ut, ultricies dui. Class aptent taciti sociosqu ad litora torquent per conubia "
+"nostra, per inceptos himenaeos. Donec et arcu eros. Etiam dapibus bibendum felis eu viverra. Integer a lacus "
+"venenatis elit lacinia facilisis non non felis. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed "
+"ultricies augue at sapien mattis aliquam. Quisque nec semper purus. Cras auctor aliquet lacus, sed facilisis urna "
+"sollicitudin non. Vivamus eget erat purus. Sed a risus augue. Donec non sem sed sapien accumsan lacinia. Ut mauris "
+"odio, vehicula id accumsan at, tincidunt non odio. Nunc porttitor luctus ante ac cursus. Cras et dapibus ex, id "
+"pretium ligula. Proin volutpat rhoncus ex vitae venenatis. Pellentesque imperdiet, magna non tempus auctor, metus "
+"dolor scelerisque dui, id tempor purus est in risus. Suspendisse vehicula imperdiet sapien, nec pulvinar dolor "
+"ornare ac. Nulla luctus, nisl in aliquam blandit, risus orci placerat nunc, id tempus sem neque vitae leo. Aenean at "
+"elit elit. Suspendisse finibus dictum interdum. Nunc consectetur eget quam vitae egestas. Pellentesque tellus augue, "
+"aliquet at faucibus ac, imperdiet ut nulla. Maecenas quis lorem velit. Donec porta ligula et suscipit luctus. "
+"Aliquam sed pretium nunc. Nunc quis posuere tortor. Fusce in lectus nec turpis rhoncus pellentesque eu at quam. "
+"Nulla facilisi. Sed ante nulla, posuere ac ullamcorper vel, rhoncus vitae nisl. Nam non pellentesque arcu. Vivamus "
+"nibh leo, pellentesque a mollis non, gravida ut erat. Donec purus urna, pulvinar eu iaculis blandit, rutrum eget "
+"nulla. Fusce quis fermentum diam, faucibus volutpat lorem. Maecenas aliquet nisi nisl, eget sollicitudin ipsum "
+"facilisis at. Mauris nec sapien nisi. Duis ac laoreet sapien, a condimentum nisi. Nam vitae sapien sed sem convallis "
+"ornare. Pellentesque neque diam, ullamcorper et dolor sit amet, faucibus venenatis tortor. Nunc vel erat malesuada, "
+"vulputate odio sit amet, aliquam dui. Donec tincidunt arcu ut risus laoreet, id malesuada leo ultrices. Praesent a "
+"scelerisque libero, vitae suscipit massa. Quisque faucibus mauris rhoncus turpis vestibulum rhoncus. Donec vel "
+"molestie magna. Aenean et lorem dui. Nam iaculis ante sapien, semper tincidunt tortor hendrerit id. Nulla sed orci "
+"mi. Aliquam hendrerit libero erat, ac aliquam massa rutrum non. Suspendisse eleifend, elit in aliquet hendrerit, "
+"tellus erat sodales neque, quis rhoncus tellus sem vitae est. Interdum et malesuada fames ac ante ipsum primis in "
+"faucibus. Etiam quis mauris non ipsum tristique interdum sit amet eget mi. Ut velit risus, gravida ut efficitur sit "
+"amet, commodo at diam. Sed consectetur dui porttitor quam feugiat, et auctor mauris maximus. Nullam lobortis ac mi "
+"lacinia egestas. Proin ante massa, malesuada ut nulla elementum, venenatis mollis ante. Cum sociis natoque penatibus "
+"et magnis dis parturient montes, nascetur ridiculus mus. Mauris eget gravida eros, non varius velit. Integer "
+"consectetur lectus nec arcu scelerisque, scelerisque vulputate mauris suscipit. Aliquam orci dui, faucibus et rutrum "
+"in, rhoncus quis dolor. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; "
+"Maecenas ante nunc, placerat id lectus sit amet, luctus cursus ante. Nulla nec placerat arcu. Fusce ac dictum ex. "
+"Vivamus semper nulla vitae neque volutpat, auctor vestibulum arcu tempus. Pellentesque aliquam tincidunt arcu, et "
+"pharetra neque. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc "
+"risus augue, malesuada quis risus a, suscipit semper metus. Suspendisse ac rhoncus felis. Aliquam orci lectus, "
+"elementum at nulla at, ullamcorper pellentesque leo. Quisque nisi tellus, pharetra in pellentesque in, facilisis "
+"vitae velit. In ex ex, sagittis at dolor vel, congue ultricies velit. Duis quis gravida mi. Aenean tempor efficitur "
+"lectus. Fusce sodales, ex eu efficitur iaculis, metus sem eleifend purus, ut commodo arcu tortor eget urna. Etiam "
+"nisi nisl, malesuada convallis ex at, malesuada elementum nunc. Vivamus commodo mi id ligula tincidunt posuere. "
+"Integer eget arcu cursus, sagittis quam eu, aliquam leo. In auctor eget mauris et elementum. Aenean sagittis euismod "
+"tellus sed accumsan. Aliquam erat volutpat. Aliquam erat volutpat. Ut consectetur porta ipsum sit amet porttitor. "
+"Nam ut nunc a turpis auctor finibus. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac "
+"turpis egestas. Donec non nisl condimentum, fermentum augue in, egestas libero. Pellentesque ut odio rhoncus, "
+"sollicitudin felis vitae, pellentesque est. Suspendisse tincidunt eros eget ex vestibulum elementum. Vivamus mollis "
+"scelerisque diam, quis dignissim dolor venenatis at. Ut gravida sapien vitae risus efficitur, ut auctor justo "
+"gravida. Cras arcu elit, interdum vel purus sit amet, venenatis molestie tellus. Integer consectetur tempor velit a "
+"varius. Praesent congue, massa non congue blandit, tortor purus imperdiet elit, sit amet pharetra arcu lacus egestas "
+"neque. Maecenas in erat arcu. In varius, risus vitae mollis sodales, nisi velit bibendum tortor, vitae sagittis "
+"augue tortor quis nunc. Fusce posuere dolor ac tincidunt facilisis. Phasellus in lacus diam. Fusce mattis sapien "
+"tellus, scelerisque pharetra leo eleifend nec. Cras libero diam, convallis in luctus a, iaculis a ipsum. Duis arcu "
+"leo, volutpat non mauris et, scelerisque suscipit diam. Ut vulputate placerat velit quis placerat. Duis commodo non "
+"turpis et convallis. Duis nec pulvinar metus, ac tristique leo. Fusce vehicula augue ac placerat elementum. Nulla "
+"dapibus nisi pretium lectus sodales, ac congue sapien ornare. Vestibulum sagittis orci ut purus efficitur, eu mollis "
+"libero placerat. Vestibulum ullamcorper odio non quam mollis, eget rhoncus metus eleifend. Mauris scelerisque, massa "
+"rutrum sodales malesuada, elit dolor blandit lectus, quis faucibus felis odio feugiat lacus. Nunc bibendum congue "
+"efficitur. Nunc a purus neque. In lobortis metus nisi, vel pellentesque mi facilisis sed. Donec in pretium neque, in "
+"maximus metus. Integer faucibus diam sed tristique sagittis. Nullam eget maximus leo, eget malesuada leo. Vestibulum "
+"ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean porttitor risus eget eros "
+"euismod molestie. Integer tristique tincidunt elit, non posuere libero pretium vel. Fusce dapibus, nisi nec egestas "
+"dapibus, lectus arcu maximus leo, a finibus diam arcu ut mauris. Vivamus tincidunt lectus ut augue ultrices, et "
+"cursus sem cursus. Proin in quam mauris. Maecenas vel magna dapibus, interdum ipsum mattis, posuere tortor. Cras eu "
+"massa ex. Donec eget massa vel dui gravida luctus vel a quam. Etiam eu lobortis neque. Etiam ligula dui, dictum ut "
+"turpis ac, eleifend pretium turpis. Vestibulum convallis finibus commodo. Morbi fermentum ante nunc, a rhoncus lacus "
+"ultricies quis. Suspendisse finibus quam blandit odio elementum, non efficitur diam laoreet. Cras aliquet ligula "
+"eget magna scelerisque, ut ornare nisi elementum. Duis nisl massa, suscipit id nibh a, venenatis auctor risus. Nulla "
+"luctus eget odio quis ultrices. Etiam consequat sapien ut nisl mollis cursus. Pellentesque a lacinia odio, id varius "
+"lorem. Curabitur scelerisque in urna eget pretium. Class aptent taciti sociosqu ad litora torquent per conubia "
+"nostra, per inceptos himenaeos. Sed leo metus, fermentum vitae quam ut, suscipit efficitur purus. Sed facilisis "
+"dapibus pulvinar. Cras sed eleifend mi. Sed quis nibh in sapien venenatis interdum ac nec orci. Sed non tortor urna. "
+"Nam rutrum lacinia diam id vehicula. Quisque vitae lobortis nibh, at tempor purus. Suspendisse dictum interdum nisi, "
+"quis maximus ipsum commodo tempus. Nulla semper congue gravida. Aenean at nibh in eros aliquam egestas. Nulla "
+"fermentum efficitur laoreet. Donec non lorem nec augue porttitor cursus eu in quam. Aenean laoreet quam neque, at "
+"tempus nisi ultrices id. Quisque in diam lacinia nulla scelerisque rhoncus vitae eget nulla. Donec vel est metus. "
+"Nullam suscipit odio eu enim lacinia facilisis eget in tellus. Vestibulum vehicula risus nec odio consectetur, a "
+"cursus massa imperdiet. Duis facilisis felis quis nunc mattis, nec volutpat libero tempor. Nulla nec leo sed tellus "
+"maximus lobortis. Suspendisse at urna nibh. Vestibulum eget turpis nisl. Donec scelerisque neque auctor erat tempor "
+"elementum sed id lacus. Sed metus nulla, dictum non luctus vel, suscipit et ex. Quisque laoreet sapien non neque "
+"iaculis, at aliquam massa viverra. Nullam nibh diam, imperdiet eu nunc sed, congue cursus leo. Morbi tristique diam "
+"metus, at faucibus magna mollis at. Sed eget nibh nunc. Nam nec elementum sem, sit amet tincidunt lorem. In viverra "
+"elit et interdum fermentum. Integer imperdiet orci ac justo molestie ullamcorper. Pellentesque fringilla tortor "
+"erat, scelerisque maximus nisl sollicitudin a. Integer nisi elit, pharetra eget lacinia non, congue sit amet ex. "
+"Phasellus tempus suscipit ultrices. Quisque ac nibh dignissim erat bibendum cursus vel a enim. Curabitur a augue sit "
+"amet lorem pharetra feugiat. Donec euismod, massa at venenatis bibendum, elit libero pellentesque velit, eget congue "
+"metus risus a enim. Aenean pretium vestibulum enim, sit amet vulputate urna auctor vitae. Praesent porttitor erat eu "
+"mi cursus venenatis. Maecenas ut ultrices neque, ac feugiat libero. Nulla finibus sit amet sem in auctor. Nam "
+"fermentum maximus ex, et consequat velit lobortis id. Aliquam eu feugiat est. Donec quis leo ex. Suspendisse "
+"convallis eget nulla eu aliquet. Quisque aliquet tortor vitae ipsum fermentum tristique. Sed convallis rutrum augue, "
+"ac viverra est pharetra quis. Ut porttitor magna massa, placerat maximus lectus scelerisque quis. Sed viverra urna "
+"in neque feugiat rhoncus. Donec ut viverra odio, laoreet dignissim dui. Aenean tristique feugiat diam vel luctus. "
+"Cras sit amet condimentum neque, ut faucibus ante. Aenean vitae elit id est laoreet efficitur in sit amet magna. "
+"Praesent ante felis, blandit id nisl ut, porta fringilla orci. Aenean vel accumsan metus, vel vehicula metus. Nulla "
+"placerat nibh et auctor convallis. Maecenas magna metus, pretium ac sodales ac, eleifend quis eros. Praesent "
+"volutpat quam a pulvinar pharetra. Sed arcu dolor, aliquet nec magna in, faucibus consequat lorem. In tincidunt, ex "
+"a finibus rutrum, metus dui fringilla ex, ac mollis elit leo eget augue. Nunc vehicula facilisis nibh, quis "
+"ultricies sem. Praesent nulla est, finibus in lorem in, mattis placerat urna. Proin hendrerit risus nunc, id congue "
+"ex posuere id. Aenean ullamcorper tortor quis lorem consectetur, et euismod leo fermentum. Praesent vulputate congue "
+"lectus sit amet pulvinar. Vestibulum vel vestibulum quam, in convallis diam. Maecenas sollicitudin magna odio, eget "
+"mollis purus posuere eu. Curabitur molestie mattis ligula, a maximus dui fermentum ut. Fusce justo velit, eleifend "
+"ut tellus vitae, volutpat maximus risus. Pellentesque suscipit mauris non purus placerat porta. Nunc in malesuada "
+"mi, vel bibendum felis. Aenean pretium nunc id efficitur porttitor. Mauris malesuada, tortor sit amet blandit "
+"tincidunt, tellus est ullamcorper diam, sit amet aliquet ex velit interdum quam. In hac habitasse platea dictumst. "
+"Sed vitae est eu elit posuere mattis nec a mauris. Morbi id ligula sed nunc sagittis finibus vitae eu nisi. Cras "
+"dignissim sagittis tellus a suscipit. Nunc semper erat nec libero vestibulum, at mattis purus scelerisque. "
+"Pellentesque egestas volutpat eleifend. Nullam venenatis erat id diam venenatis, sed rhoncus felis hendrerit. Nullam "
+"luctus facilisis risus. Mauris sed urna nisi. Ut tempus feugiat metus. Integer at purus velit. Praesent neque felis, "
+"pellentesque vitae sem nec, tempor commodo urna. Morbi malesuada ante sit amet purus tincidunt pellentesque. Aenean "
+"commodo lectus sit amet dignissim hendrerit. Phasellus auctor tellus ligula, eu ultrices ex egestas non. Mauris eget "
+"nisl dictum, scelerisque sapien et, dapibus felis. Aenean in dignissim leo. Sed semper, ex at euismod molestie, ex "
+"odio ullamcorper nisi, et facilisis lectus eros non magna. In hac habitasse platea dictumst. Pellentesque sed "
+"maximus mauris. Cras luctus dapibus nunc, sit amet suscipit dui viverra nec. Donec gravida tortor porttitor orci "
+"malesuada porttitor. Nunc condimentum eu libero sit amet varius. Curabitur mollis urna eu porta tincidunt. Nullam "
+"ultricies magna libero, et dapibus tellus tempus eu. Nullam pretium lectus nec iaculis pretium. Maecenas at arcu "
+"lobortis, ornare ante nec, euismod metus. Pellentesque volutpat tellus nulla. Aenean mattis efficitur velit vitae "
+"blandit. Duis vel egestas eros. Pellentesque aliquam placerat elit, eu congue sem ullamcorper sit amet. Ut erat "
+"nisl, luctus vitae pellentesque ut, tristique eu odio. Pellentesque nec fermentum ex, rhoncus varius dui. Mauris "
+"lobortis nunc nec dui volutpat consequat. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur "
+"ridiculus mus. Aliquam dignissim purus sed ligula pretium placerat. In vestibulum ultricies mauris. Curabitur "
+"aliquet lorem quis libero auctor, ut rhoncus purus lobortis. Nulla elementum erat nec rhoncus posuere. Integer "
+"faucibus quam sed elementum fringilla. In in lobortis sapien, nec commodo tortor. Aenean euismod ipsum nisi, vitae "
+"fringilla leo imperdiet ut. Donec a semper odio, et tempor magna. Cras cursus vel augue quis egestas. Nam nec tortor "
+"blandit, mattis quam imperdiet, finibus quam. Pellentesque tincidunt eros urna, ut tristique diam faucibus "
+"condimentum. Ut dictum risus mi, non sollicitudin turpis facilisis sit amet. Morbi finibus scelerisque mattis. Fusce "
+"vel tempor purus, nec pharetra augue. Curabitur dapibus, orci eu consectetur ultrices, diam mauris sodales urna, non "
+"euismod diam lacus luctus risus. Mauris commodo accumsan sapien. Proin vel blandit sapien. Donec porta tortor vel "
+"nibh faucibus molestie. Pellentesque placerat justo erat, vitae tristique felis fringilla eget. Quisque facilisis "
+"justo at orci lobortis, ut commodo diam egestas. Etiam non tristique nisl. Cras varius, massa a sollicitudin ornare, "
+"turpis arcu fringilla leo, non euismod ligula arcu id lacus. Suspendisse potenti. Morbi pharetra dolor eget porta "
+"tristique. Nullam sem tortor, lobortis eget hendrerit a, efficitur sit amet sapien. Fusce sit amet condimentum odio, "
+"aliquet rutrum velit. Morbi vel rhoncus ante. In blandit eros ut lectus varius, quis tempor arcu iaculis. In massa "
+"leo, venenatis nec lobortis non, pulvinar non nunc. Nunc vehicula, erat vitae placerat eleifend, eros ipsum "
+"consectetur odio, eu ornare velit mauris nec sapien. Integer a consequat libero. Quisque velit augue, blandit eu "
+"luctus sit amet, laoreet sit amet odio. Etiam in enim lacus. Interdum et malesuada fames ac ante ipsum primis in "
+"faucibus. In rutrum a tortor id pulvinar. Donec pretium lorem sed sem eleifend fringilla. Fusce sollicitudin ac "
+"ligula eget pharetra. Sed cursus diam non sem ullamcorper efficitur. Vivamus congue ligula iaculis justo iaculis "
+"elementum. Integer tempor nisl arcu, ut tincidunt erat vestibulum et. Suspendisse rutrum aliquet eros non "
+"pellentesque. Mauris laoreet, diam id tincidunt faucibus, risus velit venenatis risus, in venenatis justo diam et "
+"orci. Etiam pulvinar pulvinar nisi, id efficitur erat vulputate ut. Sed suscipit sodales ante, a blandit orci "
+"maximus vel. Vestibulum at aliquet orci. Proin tincidunt nisi quis eros consequat consectetur. Praesent congue "
+"lobortis laoreet. Donec imperdiet risus erat, eu volutpat justo posuere id. Fusce placerat sollicitudin eros vitae "
+"tincidunt. Sed orci ante, ultricies sed dapibus vel, sagittis ac massa. Pellentesque vel mauris nec est hendrerit "
+"posuere. Integer sagittis diam sed felis facilisis ultrices. Aliquam erat volutpat. Nulla pharetra justo in ipsum "
+"dapibus, nec viverra nunc euismod. Nulla massa ante, euismod at interdum vel, dapibus ut ex. Etiam consequat mauris "
+"a suscipit lobortis. Donec commodo convallis velit, eget commodo urna vulputate ac. Sed molestie vel dui ut feugiat. "
+"Donec orci purus, placerat vitae egestas sed, sodales nec ex. Sed egestas turpis non malesuada semper. Donec et mi a "
+"nisi volutpat sagittis. Suspendisse potenti. Phasellus mollis sapien ac tellus imperdiet tempus. Praesent nec sapien "
+"sit amet ipsum interdum interdum non eget nunc. Aenean fringilla lorem a viverra rutrum. Donec at maximus nibh. "
+"Phasellus facilisis justo sit amet metus pharetra sagittis. Quisque mollis metus laoreet ipsum tincidunt "
+"sollicitudin. Maecenas sit amet dictum ligula. Fusce molestie iaculis dui, et gravida libero hendrerit in. Praesent "
+"euismod libero metus, vitae rhoncus velit ultrices eget. Vestibulum ac massa bibendum, gravida dolor vel, dapibus "
+"est. Etiam non elit varius, mollis purus eget, placerat velit. Nullam lectus dui, mattis at pulvinar eu, elementum "
+"et lorem. Sed vel auctor orci, nec semper neque. Nullam cursus commodo quam, in ultricies tellus rhoncus vulputate. "
+"Mauris dapibus ipsum ipsum, dapibus euismod purus pellentesque at. Nullam euismod lectus non risus consequat "
+"vulputate. Quisque finibus a turpis eu convallis. Nam magna turpis, feugiat ut urna in, tempus facilisis elit. Duis "
+"dignissim purus sagittis porttitor posuere. Suspendisse varius ligula at egestas scelerisque. Duis placerat sagittis "
+"nisi, et molestie tortor posuere condimentum. Morbi hendrerit, ante ornare tempus finibus, ex ipsum laoreet dui, vel "
+"ornare felis tortor sit amet metus. Vivamus laoreet placerat massa, non suscipit nisl faucibus eget. Vestibulum ante "
+"ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Pellentesque orci lacus, vulputate cursus ex "
+"eu, porta aliquam massa. Proin dolor massa, faucibus vel rhoncus et, venenatis a nisi. Vivamus venenatis enim mi. "
+"Sed viverra augue vitae lectus lobortis vulputate. Phasellus ac ligula congue, sagittis est non, aliquet tortor. "
+"Suspendisse faucibus euismod neque, ac congue felis. Curabitur maximus neque sit amet odio varius gravida. Proin "
+"egestas nulla eget mi bibendum luctus. Ut non mollis mi. Quisque finibus, eros non lobortis interdum, diam nisi "
+"faucibus diam, non imperdiet leo velit et dolor. Nam est massa, vehicula sed diam sed, laoreet convallis nisi. Donec "
+"enim ligula, dignissim non sem ut, pulvinar cursus mi. In at dignissim nulla, ac fringilla neque. In hac habitasse "
+"platea dictumst. Quisque luctus mattis orci, consequat egestas nisi. Vivamus et metus et quam porttitor elementum. "
+"Suspendisse auctor mauris eu sollicitudin sagittis. Vivamus ac ante non augue iaculis consectetur quis quis dui. In "
+"bibendum risus tristique ligula iaculis finibus. Phasellus non ante risus. Maecenas ac leo cursus, molestie purus "
+"sed, tristique purus. Etiam sem nulla, aliquam nec laoreet nec, iaculis quis nulla. Maecenas id dui id neque "
+"venenatis gravida. Etiam vestibulum felis at diam porta ultrices. Ut finibus tortor et augue ornare, et efficitur "
+"purus scelerisque. Phasellus et ultricies arcu, vel lacinia lacus. Aenean tincidunt eleifend nunc, sit amet mattis "
+"purus venenatis sit amet. Curabitur eleifend sem nisl, et feugiat diam pharetra sit amet. Mauris ullamcorper mi vel "
+"condimentum egestas. Nulla pulvinar purus vel sagittis posuere. Nulla quis enim bibendum, iaculis quam in, tincidunt "
+"quam. Vestibulum rhoncus volutpat risus. Nulla ultricies bibendum est non malesuada. Nunc porta erat est, a "
+"tincidunt magna gravida vel. Maecenas sit amet aliquet odio. Vestibulum egestas, tortor scelerisque consectetur "
+"pharetra, nisi tellus feugiat justo, et bibendum libero mi in diam. Aliquam tempus sapien nec tristique convallis. "
+"Nullam congue, lacus quis bibendum dignissim, nisl purus molestie dolor, a tempor dolor nibh pretium tellus. In hac "
+"habitasse platea dictumst. Cras at est turpis. Nam nec lacus posuere, mattis mi eu, viverra ex. Nullam eleifend "
+"ornare orci, vel tempor tellus. Ut nec eros eget tortor tempus tristique commodo sed lorem. Donec quis scelerisque "
+"nibh, non tincidunt velit. Fusce in eleifend sapien. Nunc sodales sem ut nunc pellentesque, eget pharetra justo "
+"tempor. Proin pretium velit et vehicula interdum. Maecenas luctus venenatis tincidunt. Donec hendrerit, ligula non "
+"volutpat porta, dui ante facilisis massa, at congue orci mi sed quam. Donec lorem ipsum, malesuada quis purus in, "
+"commodo malesuada justo. Etiam luctus, lorem vel rutrum tristique, mauris urna volutpat felis, a laoreet urna nunc "
+"et neque. Morbi a diam tincidunt augue cursus commodo nec ut ligula. Maecenas ultrices purus fermentum ullamcorper "
+"aliquet. Maecenas mi enim, semper nec metus at, posuere tristique ligula. Suspendisse est elit, porta quis massa id, "
+"gravida commodo ante. Nullam maximus mauris sit amet dolor tempus posuere. Phasellus purus mi, interdum in ipsum "
+"quis, tristique venenatis dolor. Suspendisse potenti. Phasellus odio erat, varius sed aliquet vestibulum, laoreet "
+"sed mauris. Vivamus sapien erat, maximus tristique elementum ac, eleifend in enim. Morbi accumsan elementum neque, a "
+"facilisis enim laoreet non. Donec auctor condimentum fringilla. Proin id urna nec tellus maximus maximus tincidunt "
+"et libero. Integer ultricies venenatis odio, ut volutpat odio laoreet non. Donec in scelerisque justo. Integer "
+"mauris libero, fringilla vel sapien sit amet, laoreet tincidunt dolor. Nam efficitur sagittis arcu, vel lobortis dui "
+"gravida non. Curabitur lobortis feugiat finibus. Vestibulum dictum tortor nec magna fringilla blandit. Nulla "
+"facilisi. Sed cursus laoreet neque vitae pulvinar. Ut iaculis euismod ullamcorper. Nunc in hendrerit lectus, sed "
+"venenatis mi. Suspendisse et est dui. Sed elementum augue non ornare cursus. Quisque varius facilisis magna nec "
+"laoreet. Suspendisse consequat, risus sed tempus egestas, velit felis faucibus erat, eu pharetra erat nisl sed "
+"turpis. Sed ultricies ac quam id mollis. In consequat et erat vitae interdum. Pellentesque malesuada feugiat ligula "
+"eu consectetur. Vestibulum tempor mi quis purus luctus dictum. Etiam condimentum ac ligula eget imperdiet. Ut "
+"placerat, tortor eu lacinia imperdiet, enim nibh aliquam nibh, quis faucibus enim odio eu arcu. Nullam sagittis, "
+"diam a ornare congue, ipsum eros scelerisque est, sit amet sagittis nisl tellus in felis. Nam eget ornare turpis. "
+"Sed tempor ac enim a vestibulum. Pellentesque eleifend lacus non libero accumsan, ut consectetur sapien lacinia. "
+"Etiam ut arcu non mi feugiat accumsan ut sit amet risus. Donec consequat eros sapien, malesuada imperdiet justo "
+"bibendum sit amet. Nulla pretium varius lectus, in eleifend quam fringilla in. Quisque eu pretium velit. Sed eget "
+"lectus sit amet tortor blandit tempus vel at sapien. Sed at velit porta, venenatis lorem sed, dapibus arcu. Donec "
+"pellentesque tortor id massa interdum pretium. Praesent id diam quis nunc dictum finibus quis quis ipsum. Quisque "
+"consectetur risus eu elit viverra, eget laoreet odio efficitur. In congue turpis iaculis ullamcorper bibendum. Duis "
+"at elit et velit varius vulputate ut ac turpis. Nunc posuere, urna id lobortis ornare, neque ex ultricies erat, id "
+"sollicitudin ante quam sed magna. Nunc ultrices quam erat, eget dictum libero sollicitudin in. Nulla facilisi. "
+"Pellentesque eleifend risus non justo imperdiet aliquet. Donec finibus auctor ornare. Duis in arcu lacinia, "
+"fermentum tellus vel, efficitur justo. Morbi nec nunc leo. Proin lacinia erat vel elementum dapibus. Proin diam "
+"ipsum, mollis eu lobortis a, facilisis consectetur est. Vestibulum rutrum pellentesque urna, a laoreet justo dictum "
+"vitae. Nullam dictum, mi elementum dictum interdum, sem nisl fermentum est, nec mattis enim ante aliquam tortor. "
+"Phasellus eu quam magna. Vivamus augue enim, dictum in nibh non, condimentum tristique lorem. Suspendisse potenti. "
+"Sed nibh lacus, auctor ut arcu sollicitudin, posuere tempor urna. Phasellus at odio euismod ipsum congue auctor. "
+"Fusce vestibulum elementum nunc, vel feugiat nibh bibendum at. Quisque felis ligula, fermentum a metus ac, pulvinar "
+"hendrerit est. Proin vitae tincidunt purus, vestibulum eleifend ipsum. Ut rhoncus et elit ut varius. Praesent eu "
+"pharetra tellus. Suspendisse varius, dui quis efficitur fermentum, est lectus ultricies ex, a fermentum orci nunc eu "
+"lorem. Integer aliquet nunc ullamcorper lacinia elementum. In cursus tortor nisi, ut pharetra tortor venenatis eu. "
+"Duis tincidunt, libero sed varius dictum, neque velit facilisis enim, eget bibendum mi eros et nisl. Nam turpis "
+"neque, lobortis eget ante ac, tristique congue lacus. Aenean dictum vitae tortor sed tristique. Donec sodales in "
+"arcu ut tristique. Curabitur in facilisis nisi, non vulputate odio. Phasellus ut fringilla nunc, nec dapibus turpis. "
+"Sed ut erat tempor sem vulputate gravida at at dui. Aenean id dolor ante. Morbi auctor interdum nisi, id pretium "
+"eros ultrices vel. Nulla eget justo fringilla, finibus quam et, accumsan ex. In nisl neque, pharetra nec volutpat "
+"at, mattis nec odio. Nam et sapien sed libero lacinia tempor sit amet vitae turpis. Praesent vel porta lacus, porta "
+"dignissim nunc. Aenean vitae vulputate purus. Ut at elit arcu. Integer risus neque, varius ac elit maximus, "
+"ultricies sagittis nisi. Pellentesque sapien magna, malesuada tincidunt ornare sed, malesuada tempor odio. Morbi id "
+"neque velit. Pellentesque at velit sed elit eleifend auctor. Quisque tincidunt tempus justo, venenatis dapibus sem "
+"pellentesque quis. Suspendisse finibus feugiat est id consectetur. Nulla commodo, massa auctor vulputate egestas, "
+"arcu massa tincidunt leo, quis ullamcorper sapien augue in nibh. Pellentesque ultrices ligula tincidunt urna "
+"fringilla, ac ultricies eros convallis. Ut nec massa diam. Maecenas justo nulla, dapibus id justo sollicitudin, "
+"fermentum tempor dui. Vivamus laoreet auctor mi non venenatis. Nulla commodo libero ac ex volutpat tincidunt. Donec "
+"vestibulum blandit purus bibendum laoreet. Morbi in porta orci. Nam commodo ex eget diam maximus cursus. Proin "
+"bibendum quis felis eget euismod. Praesent neque neque, pulvinar eu sem non, gravida ornare tortor. Ut tortor nisi, "
+"suscipit in lectus ac, volutpat pretium nisi. Nam rutrum nec dui quis vulputate. Duis in velit enim. Fusce porttitor "
+"vitae nisi a tincidunt. Ut enim purus, venenatis ut purus ut, iaculis dignissim ex. Aliquam erat volutpat. "
+"Suspendisse potenti. Maecenas ut malesuada elit. Maecenas tellus neque, pulvinar non metus ut, viverra finibus diam. "
+"Sed ac porttitor dui. Fusce sit amet ligula metus. Integer id aliquet libero. Sed tempor nisl in porttitor "
+"ultricies. Maecenas molestie orci sed sapien molestie interdum non id felis. Nullam sagittis elementum erat in "
+"pretium. Nunc pellentesque, ex sit amet fringilla dignissim, augue quam dictum leo, eget tristique turpis mauris sed "
+"metus. Praesent vel mauris risus. Etiam eleifend metus ut risus tempor, ac ultrices dolor dictum. Nulla sagittis non "
+"urna vitae feugiat. In venenatis arcu vel finibus volutpat. Nam non bibendum magna, nec eleifend ex. Etiam sit amet "
+"nisl euismod, mattis nisi quis, commodo nisl. Nunc eget mauris vulputate, cursus neque in, hendrerit ante. Cras non "
+"nisl in nisl laoreet aliquam. Sed vestibulum, nunc vitae molestie varius, lectus justo vehicula est, nec placerat "
+"ipsum lectus quis leo. Maecenas efficitur semper eros, sed pretium arcu blandit eu. Aliquam eget purus cursus, "
+"sollicitudin augue quis, cursus purus. Maecenas sed finibus ligula. Curabitur at diam quis eros mollis semper. Nulla "
+"commodo nisi libero, id feugiat nisl tincidunt bibendum. Mauris convallis tincidunt justo eu sodales. Quisque arcu "
+"lacus, finibus sed hendrerit at, convallis ut diam. Nulla enim nulla, efficitur quis tincidunt et, pulvinar sit amet "
+"enim. Aenean mattis urna id mauris maximus tincidunt. In hac habitasse platea dictumst. Morbi ornare porta congue. "
+"Aliquam hendrerit efficitur mi at aliquet. Vivamus rutrum lectus vel turpis volutpat, consectetur congue sem "
+"consectetur. Sed rhoncus elit sed orci tincidunt, ut condimentum diam ornare. Nulla facilisi. Ut placerat et massa "
+"nec malesuada. Praesent dapibus condimentum augue, at imperdiet lacus facilisis sed. Praesent at metus nunc. Morbi "
+"accumsan eros et turpis viverra, nec sagittis odio iaculis. Aenean rhoncus, nibh a consectetur sodales, massa lorem "
+"commodo dui, sit amet consequat ex arcu eget augue. Praesent quis nibh urna. Cras eu congue ligula, in ultricies "
+"ante. Etiam interdum, est tincidunt euismod sollicitudin, lectus felis mollis ex, pretium fringilla magna lorem non "
+"libero. Fusce aliquam tellus eget sodales commodo. Sed sapien lectus, dapibus quis elit at, ultricies tincidunt "
+"eros. Nulla suscipit orci sit amet aliquam pellentesque. Cras sed eleifend ligula, quis vehicula ligula. Integer "
+"quis tortor in mauris dictum malesuada sed non turpis. Nulla faucibus quis arcu molestie vulputate. Proin fermentum "
+"tellus feugiat, imperdiet mi sit amet, tempor sem. Mauris hendrerit augue a vulputate vulputate. Vivamus sagittis at "
+"odio non venenatis. Nunc a molestie dolor. Nunc erat nisi, consequat et tristique in, blandit non tortor. Vivamus "
+"euismod bibendum augue, ut aliquam lorem mattis quis. Duis laoreet odio at justo ultricies, nec scelerisque enim "
+"euismod. Sed eu turpis a lorem cursus feugiat. Duis ultrices molestie nulla non pharetra. Morbi faucibus est auctor "
+"faucibus placerat. Donec blandit quis ex ac pulvinar. Vestibulum a consequat quam. Fusce vitae facilisis ex. Etiam a "
+"risus eu orci tincidunt interdum. Proin interdum eros nec nibh venenatis, sed luctus sapien tincidunt. In cursus, "
+"ante nec dapibus bibendum, augue tortor venenatis felis, eu aliquam erat est vitae diam. Cras lacinia placerat quam, "
+"eu finibus purus. Aenean et augue purus. Praesent efficitur ornare magna in cursus. Nunc quis tempor ante, ac "
+"accumsan ligula. Nullam elit diam, tempus in sollicitudin at, fermentum tincidunt mi. Vestibulum accumsan, nisi at "
+"rutrum scelerisque, justo mauris cursus nulla, finibus cursus nulla elit quis augue. Aliquam lacus ante, ullamcorper "
+"quis varius vitae, ullamcorper eget magna. Phasellus mollis nisl eu nulla eleifend, non tempus tellus faucibus. "
+"Curabitur molestie eros id eleifend accumsan. Suspendisse tristique sem ante, non blandit eros accumsan ac. Ut sit "
+"amet ante justo. Nam condimentum felis quis urna sagittis hendrerit. Cras condimentum est ac massa aliquet finibus. "
+"Donec faucibus malesuada fermentum. Aliquam malesuada augue vitae dolor rutrum pellentesque. Nullam vulputate "
+"rhoncus porta. Quisque vulputate dignissim felis sit amet aliquet. Nam elementum odio velit, eget fringilla mi "
+"dignissim at. Mauris mollis diam orci, vel porta risus tempor a. Nullam quis dolor volutpat, ornare est at, "
+"fermentum urna. Fusce mollis nisl a augue condimentum, eu dictum dolor posuere. Mauris et egestas sem. Sed pretium "
+"lectus laoreet velit feugiat luctus. Nullam sodales at augue vel semper. Pellentesque vehicula dictum augue, eget "
+"tristique orci interdum a. Aenean non est eleifend, tristique urna sed, elementum nunc. Sed consectetur id lorem "
+"quis mollis. Ut et blandit velit, et lobortis dolor. Quisque nec odio sed mi ullamcorper pellentesque. Ut vitae "
+"eleifend nisi, vitae dapibus est. Vivamus ornare eleifend volutpat. Sed et tincidunt nisi. Praesent maximus risus a "
+"bibendum consequat. Vestibulum quis ex vitae ante ultricies ultricies. Maecenas dictum tellus eget enim tincidunt "
+"imperdiet. Quisque vel libero gravida, mollis erat id, placerat dolor. Etiam ante eros, bibendum vitae ultricies in, "
+"rhoncus nec turpis. Pellentesque gravida nunc sit amet iaculis condimentum. Phasellus in ultricies libero, et "
+"maximus justo. Donec ut ultrices elit. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque "
+"rhoncus, nunc at iaculis dictum, magna lectus rhoncus augue, vel aliquam sem mauris in metus. Morbi commodo purus "
+"mi, ut faucibus dui luctus et. Suspendisse accumsan placerat tortor. Cras dignissim blandit leo, non tincidunt leo. "
+"Nulla euismod turpis ac malesuada aliquam. Ut ultrices bibendum elit sed elementum. Donec auctor aliquam vehicula. "
+"Mauris lacinia dignissim leo, ullamcorper egestas nibh rutrum eget. In semper sit amet libero eget ultricies. Proin "
+"et imperdiet odio. In hac habitasse platea dictumst. In hac habitasse platea dictumst. Integer sed dolor quis tortor "
+"pretium euismod at vel dolor. Donec aliquet et urna at porta. Vestibulum tincidunt eget sapien elementum mattis. "
+"Proin lacinia faucibus orci, sed eleifend augue mollis et. Vestibulum ante ipsum primis in faucibus orci luctus et "
+"ultrices posuere cubilia Curae; Cras pellentesque, dolor eget bibendum tincidunt, turpis ante pharetra tortor, quis "
+"interdum tellus tellus sit amet nisl. Nulla convallis tempus egestas. Curabitur quis condimentum metus, eu placerat "
+"metus. Nunc ligula nunc, posuere at iaculis nec, convallis id tellus. Curabitur pretium libero lorem, quis placerat "
+"nunc fringilla interdum. Vestibulum et finibus ante. Duis quis nisi neque. Curabitur ornare lorem nec ex fringilla, "
+"et porta massa consequat. Nulla malesuada turpis nec eleifend tincidunt. Praesent ultricies dolor vitae mauris "
+"lacinia tempor. Sed blandit sapien a odio scelerisque consequat. Mauris non dictum eros. Vestibulum ante ipsum "
+"primis in faucibus orci luctus et ultrices posuere cubilia Curae; Pellentesque habitant morbi tristique senectus et "
+"netus et malesuada fames ac turpis egestas. In ut sollicitudin tellus. Suspendisse ultrices vitae erat non pharetra. "
+"Nulla pellentesque at diam venenatis sollicitudin. Vestibulum sed finibus sapien. Curabitur a metus convallis, "
+"euismod est id, iaculis nunc. Vestibulum laoreet ornare turpis. Integer rhoncus, felis nec fermentum suscipit, dui "
+"lacus sagittis ligula, vitae vestibulum urna elit aliquam est. Sed sit amet mi tortor. Suspendisse a dapibus velit. "
+"Cras eget imperdiet turpis. Maecenas at lorem condimentum, elementum augue mattis, rutrum purus. Duis imperdiet "
+"pellentesque nunc, eu tristique lectus malesuada commodo. Vivamus aliquet congue eros ac dapibus. Nunc quis "
+"porttitor odio. Nulla quis dui luctus, vestibulum enim malesuada, imperdiet elit. Donec facilisis mollis diam ut "
+"posuere. Nulla facilisi. Duis nec magna lacus. Vestibulum consequat ut tortor ut ornare. Curabitur nec felis sit "
+"amet dui finibus rutrum. Phasellus sit amet lectus nec nisl egestas posuere. Etiam nec euismod magna, vitae "
+"ullamcorper enim. Vestibulum pretium cursus semper. Cras vel lorem ut urna molestie elementum. Mauris luctus vel "
+"arcu quis egestas. Suspendisse potenti. Nullam viverra sollicitudin lacus luctus sodales. Maecenas eget diam cursus "
+"quam tincidunt ultricies vitae nec lacus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec urna "
+"sapien, porta a efficitur vitae, imperdiet vel ligula. Nulla volutpat massa sit amet est aliquet, ut iaculis tellus "
+"convallis. Sed justo tortor, sodales non nisi quis, laoreet commodo quam. Cras tempus purus a tempor malesuada. "
+"Curabitur enim nibh, viverra in enim eget, viverra euismod nunc. Mauris nunc leo, faucibus blandit condimentum nec, "
+"rutrum sit amet leo. Quisque nec tortor sed felis pretium imperdiet. Morbi lobortis, dolor nec lobortis maximus, "
+"turpis justo aliquet massa, eget aliquet nunc mauris a lectus. Phasellus dignissim, est nec luctus consequat, ex "
+"nisi euismod lacus, a viverra nulla eros et est. Suspendisse in egestas dolor. Etiam non placerat lorem. "
+"Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aliquam ut enim "
+"tristique, porta nulla quis, placerat eros. Integer eget feugiat mi, ac condimentum felis. Fusce auctor ligula ut "
+"est placerat efficitur. Nam hendrerit condimentum ante eget tincidunt. Phasellus vel convallis neque. Vivamus sit "
+"amet elit eu enim iaculis scelerisque. Donec imperdiet lacus id magna luctus, vitae dapibus quam condimentum. Donec "
+"laoreet vehicula tellus. Nullam nec neque at massa ultricies dignissim. Suspendisse potenti. Cras convallis nunc "
+"urna, a tempor metus volutpat ut. Fusce viverra lorem vitae quam ullamcorper cursus. Mauris maximus et mi eget "
+"tincidunt. Proin molestie suscipit felis at ultricies. Duis varius rhoncus metus vehicula bibendum. Aliquam "
+"consequat non tellus at aliquam. Vivamus nec turpis facilisis, dapibus lacus in, congue tortor. Curabitur at "
+"interdum mi, sed rhoncus nibh. Morbi facilisis purus laoreet, tincidunt justo sit amet, elementum lectus. Vestibulum "
+"pellentesque sem lacus, in condimentum purus consequat at. Integer pharetra rhoncus aliquam. Duis nec sem ac elit "
+"suscipit laoreet. Integer vel est commodo, feugiat sapien eget, cursus quam. Aenean elit leo, interdum a posuere "
+"nec, laoreet eu magna. Nam sit amet felis faucibus, porttitor justo eget, commodo mi. Maecenas a eleifend nibh. "
+"Donec ut ornare augue. Aliquam pellentesque aliquet eros in hendrerit. Nullam consectetur odio id lectus ullamcorper "
+"facilisis. Donec pulvinar, magna non sollicitudin commodo, erat lacus egestas massa, a egestas nibh nulla ac lorem. "
+"Maecenas at mi posuere, fringilla lectus sed, fringilla eros. Vivamus mattis at magna ac suscipit. Proin varius mi a "
+"quam efficitur ullamcorper. Curabitur venenatis turpis lacus, vitae volutpat velit ultricies sed. Sed faucibus id "
+"neque in consequat. Nulla imperdiet fermentum placerat. Donec rutrum libero ac lorem commodo pellentesque in tempor "
+"augue. Maecenas sodales cursus ex, ac elementum felis consectetur vel. Cras ante nulla, porttitor nec ex non, "
+"venenatis consectetur justo. Nam vitae enim eget augue euismod suscipit et in nulla. Morbi eu sollicitudin libero, "
+"ut lobortis purus. Pellentesque sodales tempor diam, a luctus dui vehicula tempus. Cum sociis natoque penatibus et "
+"magnis dis parturient montes, nascetur ridiculus mus. Vestibulum dignissim sagittis diam ac aliquam. Integer iaculis "
+"ac est eu molestie. Vivamus convallis arcu nec rutrum molestie. Vestibulum mollis ipsum neque. Vestibulum "
+"condimentum neque quis tellus elementum, in facilisis neque venenatis. Donec quis ultrices risus. Cras mattis felis "
+"eget erat iaculis, id scelerisque mauris pharetra. Vivamus condimentum tempor ipsum, porta commodo erat dictum ut. "
+"Fusce et ligula sed arcu tincidunt efficitur nec ut felis. Donec eu justo pellentesque, finibus diam quis, iaculis "
+"erat. Fusce a tempus urna, at fermentum est. Sed pretium orci dapibus ante laoreet, a consequat erat scelerisque. "
+"Etiam nisi tortor, vulputate quis sapien sit amet, lobortis blandit felis. Morbi urna purus, pellentesque quis orci "
+"id, suscipit consequat velit. Donec vehicula ipsum felis. Donec at elit ipsum. Fusce purus sapien, convallis quis "
+"faucibus et, tempus at dolor. Vivamus commodo sem ac congue imperdiet. Vivamus convallis eget est eu vulputate. "
+"Aliquam vehicula augue ac urna imperdiet interdum. Praesent euismod arcu quis purus vestibulum, et placerat metus "
+"hendrerit. Fusce semper lacus sit amet ligula scelerisque scelerisque. Vestibulum neque ex, aliquam non lorem a, "
+"aliquam fringilla enim. Aenean consectetur vestibulum tortor. Donec et elit consectetur, tincidunt augue feugiat, "
+"condimentum diam. In luctus tellus at massa euismod faucibus. Ut tempus dui hendrerit, vehicula ex ut, facilisis "
+"lacus. Pellentesque bibendum enim auctor, vulputate justo vel, ultricies est. Praesent interdum turpis in convallis "
+"luctus. Duis vel enim venenatis, mollis elit vitae, mattis velit. In eu posuere nibh. Duis a est est. Nam semper "
+"tincidunt nulla id dignissim. Fusce consectetur maximus eros quis posuere. Sed efficitur, enim quis ultrices "
+"eleifend, est diam commodo dui, nec euismod augue velit sit amet ante. Integer fringilla vehicula faucibus. "
+"Curabitur non placerat turpis. Integer malesuada quam eget sapien tristique aliquet. In hac habitasse platea "
+"dictumst. Cras dignissim mauris neque, in facilisis nulla pulvinar ac. Phasellus sagittis ligula non sem aliquet "
+"iaculis. Integer interdum elit in dolor vehicula, non condimentum justo pretium. Aliquam eget feugiat tellus. "
+"Suspendisse condimentum dui at erat elementum semper. Aliquam vitae cursus lorem. Ut vestibulum porttitor purus ut "
+"dapibus. Curabitur posuere nunc quis nisi rhoncus, ac mollis enim eleifend. Aenean tristique at justo ut tempor. "
+"Proin posuere condimentum arcu ac lobortis. Proin euismod posuere ipsum, nec dignissim velit eleifend gravida. "
+"Quisque quis sem mi. Proin scelerisque consequat lectus nec sodales. Fusce id sapien a erat cursus sodales. Morbi ac "
+"magna vitae lorem dictum luctus in et lacus. Morbi imperdiet mi interdum, molestie sem in, accumsan leo. Sed lacinia "
+"enim et sem egestas, a pulvinar velit ullamcorper. Aenean laoreet, erat eu viverra dictum, eros odio venenatis mi, "
+"tincidunt blandit odio mauris id augue. Donec pretium mauris nibh, ut eleifend velit auctor vitae. Morbi tincidunt "
+"lacus id ullamcorper egestas. Proin vel porttitor purus, eu fermentum dui. Aliquam a interdum mi. Aliquam ut rhoncus "
+"nibh. Morbi nulla libero, commodo quis eros eu, scelerisque gravida ligula. Aliquam sed arcu nunc. Sed egestas "
+"hendrerit orci, nec rhoncus arcu fringilla quis. Pellentesque lobortis nulla arcu. Integer aliquam vel quam sed "
+"tempor. Morbi viverra tempus risus vel convallis. Cras eget neque ex. Mauris porta, risus at rhoncus hendrerit, "
+"libero metus pharetra sapien, quis viverra tortor nunc tincidunt magna. Aenean a tellus ullamcorper, convallis urna "
+"quis, suscipit sem. Vivamus eu eleifend est. Duis venenatis metus eget ex consequat molestie. In ullamcorper a dolor "
+"vitae feugiat. Morbi ultrices vestibulum venenatis. Phasellus luctus enim id aliquet pharetra. Aenean mauris felis, "
+"finibus eu dolor at, tempor sodales diam. Sed nisl nibh, tincidunt quis fringilla vel, congue eu dui. Duis viverra "
+"justo eu sem ultricies dignissim. Morbi et sollicitudin erat. Proin id porttitor odio, et sagittis ex. Aenean "
+"laoreet leo sit amet risus vestibulum, mollis ultrices tortor porttitor. Sed vestibulum varius ligula quis accumsan. "
+"Duis fermentum, dolor iaculis condimentum tincidunt, purus nunc bibendum nibh, ac sodales tortor odio non ante. Sed "
+"leo mauris, consequat molestie quam eu, vulputate volutpat metus. Cras fringilla risus sed arcu consequat luctus. "
+"Nam malesuada, turpis at luctus blandit, velit elit fringilla metus, eu mollis odio felis id tortor. Aliquam erat "
+"tellus, pulvinar nec iaculis et, consequat sit amet diam. Sed vestibulum, leo ut vehicula suscipit, quam justo "
+"maximus lectus, nec lobortis urna tortor nec nisi. Vestibulum eget ornare arcu, sed viverra turpis. Sed posuere "
+"tellus iaculis, scelerisque dui id, convallis lectus. Aliquam sodales at mi consectetur dignissim. In fringilla, "
+"urna id placerat mattis, diam magna commodo dui, at elementum arcu elit et libero. Duis venenatis vulputate nisl "
+"congue pharetra. Fusce sapien velit, cursus a consectetur quis, auctor gravida sem. Maecenas malesuada metus quis "
+"elit congue accumsan. Vivamus scelerisque euismod malesuada. Vestibulum purus metus, tempor eget faucibus a, cursus "
+"eu arcu. Morbi dictum urna vitae velit pellentesque facilisis. Sed arcu est, tempor ac turpis sit amet, ultricies "
+"venenatis augue. Nunc laoreet leo gravida facilisis dapibus. Aliquam convallis ullamcorper felis, sit amet tempor "
+"libero euismod sit amet. Quisque leo augue, finibus et euismod non, venenatis sed libero. Cras pharetra rhoncus "
+"odio, in pharetra lacus porttitor scelerisque. Maecenas eleifend felis vitae diam blandit viverra. Fusce at "
+"ultricies arcu, pharetra finibus enim. Etiam pellentesque semper ligula, sed tincidunt purus. Sed fermentum metus "
+"varius, aliquet libero eget, vehicula erat. Sed ac finibus metus. Pellentesque libero leo, semper et eros nec, "
+"gravida condimentum urna. Cras nec turpis convallis, efficitur lacus at, ultricies ex. Fusce eu neque elementum leo "
+"gravida semper. Duis sed tellus vitae magna fringilla maximus ac ut nisl. Integer id ligula ullamcorper, ultricies "
+"quam sit amet, ullamcorper diam. Maecenas rhoncus nulla eu dui vulputate scelerisque. Vestibulum porttitor eget nibh "
+"a mattis. Mauris tempus at urna blandit dignissim. Proin turpis leo, mattis ut turpis eget, aliquet tempor ante. "
+"Nunc in mollis nunc, et interdum nisi. Cras tristique sollicitudin tortor sit amet ultrices. Proin rhoncus neque "
+"urna. Maecenas bibendum, massa sit amet suscipit suscipit, justo tortor maximus dolor, posuere facilisis nisi tellus "
+"elementum diam. Quisque id eros vel lectus malesuada tincidunt. Donec at orci ac ligula venenatis dignissim sit amet "
+"nec purus. Sed eu neque finibus, tristique ex a, feugiat ante. Pellentesque tincidunt luctus mollis. Nullam blandit "
+"faucibus gravida. Ut sit amet malesuada nibh, vel tincidunt ipsum. Donec suscipit lorem in dui luctus, viverra "
+"imperdiet magna placerat. Pellentesque venenatis eros quis urna efficitur facilisis. Cras ligula magna, tempus "
+"facilisis tincidunt at, varius quis lectus. Sed quam neque, facilisis vel facilisis vel, lobortis ac orci. Nullam "
+"pretium interdum erat ac ultrices. Etiam enim mauris, vehicula nec rhoncus quis, volutpat vel erat. Morbi imperdiet "
+"rhoncus rutrum. Nullam auctor condimentum diam nec faucibus. Etiam sit amet porta nulla, sit amet lobortis enim. "
+"Aenean tincidunt condimentum accumsan. Vestibulum mollis diam risus, vitae ornare enim iaculis non. Nullam vitae "
+"risus tristique, imperdiet augue ut, egestas dolor. Sed sit amet leo eu diam commodo vestibulum id in dolor. Vivamus "
+"tristique molestie faucibus. Duis tempor porttitor turpis ac consectetur. Curabitur condimentum, ipsum eu dignissim "
+"semper, ipsum erat pretium quam, ut maximus erat ligula eu felis. Sed viverra, mauris id tempus tempor, nisi leo "
+"consectetur arcu, ac vulputate lorem mauris non sapien. Maecenas rhoncus magna mauris, in luctus nulla dapibus at. "
+"Sed magna est, ultrices sit amet erat nec, dapibus lacinia massa. Morbi cursus ex in elit auctor egestas. Quisque id "
+"placerat nibh, at mollis tortor. Proin fringilla sodales sapien, ac ullamcorper sem bibendum eget. Donec dui ligula, "
+"viverra eget leo ac, tincidunt fringilla mauris. Quisque vel lectus eget metus feugiat laoreet. Morbi eget "
+"vestibulum enim, ac ultricies lorem. Nam at mollis magna. Etiam vitae orci eu leo facilisis vestibulum. Ut sed "
+"turpis ut nibh iaculis rhoncus. Phasellus sit amet risus pellentesque, gravida eros a, porta nibh. Suspendisse at "
+"tincidunt ligula. Vivamus id libero diam. Morbi viverra ipsum turpis, in ullamcorper enim pellentesque nec. Sed "
+"ultricies, lectus quis pellentesque sodales, arcu diam commodo massa, a vestibulum purus sapien eget risus. Duis "
+"rhoncus in velit in dignissim. Aliquam sit amet metus in quam finibus cursus. Pellentesque eget aliquam justo. Fusce "
+"imperdiet, tellus non venenatis facilisis, diam mi lobortis dolor, at consectetur est massa id elit. Vivamus ante "
+"ex, faucibus et mollis eget, dignissim vel massa. Duis ultricies diam commodo purus facilisis pharetra. Curabitur "
+"pretium massa sed enim vehicula, id vehicula neque vehicula. In quis lectus non mauris pulvinar fermentum. Aliquam "
+"condimentum aliquet dui et congue. Maecenas quis augue eget leo gravida aliquet. Praesent sit amet fermentum odio, "
+"ut placerat nulla. Curabitur sit amet iaculis erat, eu volutpat odio. Ut iaculis ex quis tempus commodo. "
+"Pellentesque cursus eros at velit vulputate, id luctus massa pretium. Morbi ex dui, sodales id finibus id, aliquet a "
+"justo. Maecenas semper leo eget dolor rutrum, at imperdiet nibh eleifend. Aliquam eget purus tortor. Cras rutrum "
+"tortor massa, vel bibendum nunc aliquam vel. Nullam vestibulum, metus vel fermentum elementum, nulla sapien egestas "
+"justo, ac feugiat ex justo nec eros. Donec sit amet nibh mollis, commodo quam sit amet, semper magna. In tortor "
+"magna, elementum nec auctor sed, pellentesque at augue. Sed gravida arcu ac aliquet convallis. Nulla facilisi. Duis "
+"nunc quam, gravida non interdum id, cursus ac leo. Suspendisse vel ipsum nisl. Aliquam at gravida libero. Maecenas "
+"sit amet efficitur orci. Fusce id vehicula sapien. Proin euismod diam non laoreet ultricies. Nunc ullamcorper, nibh "
+"id cursus vehicula, ex purus tempor urna, et euismod orci est sed elit. Duis ut blandit mauris. Ut blandit cursus "
+"eros, sed laoreet nisl efficitur ac. Phasellus dui elit, fringilla sit amet cursus nec, pharetra quis odio. Ut ut "
+"lorem sit amet sem dapibus accumsan. Aenean a laoreet dolor. Donec eu laoreet velit. Etiam id nisi vel nibh dapibus "
+"congue a quis odio. Donec velit risus, semper quis porta non, elementum quis lorem. Interdum et malesuada fames ac "
+"ante ipsum primis in faucibus. Nullam sit amet dolor magna. Maecenas quis sapien sit amet est pulvinar lobortis "
+"efficitur cursus orci. Phasellus tristique mauris lorem, eu ultricies justo ornare condimentum. Integer urna enim, "
+"lobortis id malesuada ut, mattis eget libero. Sed commodo tincidunt eleifend. Fusce sed velit ut dui pellentesque "
+"pellentesque eget vel diam. Aenean nec turpis at tortor consectetur consectetur. Vestibulum ultrices elit at nisl "
+"pellentesque molestie. Maecenas diam dolor, faucibus eget posuere ut, sodales ut eros. Nam vulputate mollis diam nec "
+"gravida. Nam et ullamcorper diam. Aenean non nulla non lorem ullamcorper sagittis non quis erat. Pellentesque "
+"habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. In hac habitasse platea dictumst. "
+"Donec quis mauris ac nibh vestibulum eleifend placerat sed lacus. Suspendisse mi elit, viverra non velit ut, "
+"tincidunt tempus felis. Fusce ullamcorper, arcu nec aliquet porttitor, odio lacus mollis mi, id malesuada tortor "
+"velit aliquet turpis. Sed hendrerit felis nec faucibus ornare. Nulla ut metus eget augue malesuada posuere eget eu "
+"tortor. Cras ultrices odio sit amet porttitor vehicula. Sed vulputate leo vitae justo viverra, nec volutpat eros "
+"consectetur. Nunc nunc tellus, porta in arcu in, vulputate ultricies tellus. Fusce commodo efficitur lorem, sit amet "
+"lacinia sapien sollicitudin at. Etiam aliquet non mi vitae ornare. Cras condimentum imperdiet elit eu dictum. Donec "
+"sed enim sed massa tempor porta et sit amet felis. Nam interdum ornare sem, in tincidunt risus consectetur vel. Ut "
+"convallis purus mauris, in consequat ligula ullamcorper ut. Quisque elit ipsum, accumsan eget ligula vitae, "
+"sollicitudin luctus tellus. Nunc pretium turpis ligula, id dignissim lorem suscipit eu. Nulla facilisi. Sed lectus "
+"odio, vehicula vel vulputate id, ultrices non ipsum. Donec arcu quam, consequat eget aliquet sit amet, ullamcorper "
+"non nibh. Etiam finibus, mi id lobortis sagittis, leo leo lobortis lectus, sit amet aliquam dui odio sit amet massa. "
+"Suspendisse iaculis urna ac lectus gravida, iaculis efficitur tellus hendrerit. Sed tellus enim, condimentum in "
+"augue eget, sagittis ullamcorper sem. Suspendisse vitae aliquet libero. Aenean quis purus in sapien dapibus "
+"suscipit. Sed commodo nunc in lacus bibendum, vel tincidunt ante ornare. Ut tristique luctus volutpat. Class aptent "
+"taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Quisque a ultricies orci, eu porta "
+"odio. Vivamus sapien arcu, ultrices vel dui ut, luctus viverra purus. Praesent fringilla sed odio quis pretium. "
+"Vestibulum ullamcorper nisi tortor, id sollicitudin lectus tempor a. Ut malesuada sapien eu sapien posuere, non "
+"euismod eros porta. Nunc vel tincidunt ligula. Cras dolor ante, tristique tempor metus quis, mollis vulputate orci. "
+"Curabitur vitae nisl euismod, elementum purus vel, dictum lorem. Nunc eu mauris at metus porttitor dignissim ut eu "
+"neque. In tempor rhoncus neque sit amet commodo. Maecenas sed lacus semper, tempus enim ac, fermentum lorem. Nullam "
+"sollicitudin convallis turpis. Curabitur finibus placerat viverra. Pellentesque convallis condimentum tortor id "
+"efficitur. Proin semper pretium est, et vehicula ex cursus a. Nam ut felis purus. Phasellus eget felis eget leo "
+"dapibus vestibulum. Nulla eleifend malesuada turpis, quis faucibus eros. Nam aliquet euismod viverra. Ut quis semper "
+"felis. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Pellentesque at nulla "
+"arcu. Integer ut tellus ac sapien maximus tincidunt sed vitae risus. Nulla viverra, nibh eget eleifend aliquam, quam "
+"quam tempor massa, eu semper ipsum lacus in turpis. Nulla sed purus enim. Nullam sed fermentum ipsum. Sed dui nisi, "
+"elementum a auctor at, ultrices et nibh. Phasellus aliquam nulla ut lacinia accumsan. Phasellus sed arcu ligula. "
+#ifndef _MSC_VER
+"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam fermentum magna vitae dui sagittis tempor. Vivamus "
+"eu ligula blandit, imperdiet arcu at, rutrum sem. Aliquam erat volutpat. Quisque luctus enim quis volutpat lobortis. "
+"Vestibulum eget sodales libero. Aenean at condimentum est. Proin eget massa vel nulla efficitur tempor eget at enim. "
+"Integer enim sapien, ornare luctus nisl non, pretium facilisis ex. Donec pretium ligula ligula, a facilisis turpis "
+"hendrerit at. Nullam eget malesuada justo, at molestie quam. Sed consequat massa eu faucibus maximus. Curabitur "
+"placerat orci sapien, sit amet semper magna sodales non. Ut fermentum accumsan odio in consectetur. Morbi neque mi, "
+"vulputate nec mi ut, cursus scelerisque lectus. Nulla sapien enim, finibus id ipsum luctus, consequat ullamcorper "
+"lectus. Sed volutpat sed massa in sodales. Morbi lacinia diam eu commodo vulputate. Fusce aliquet pulvinar dolor in "
+"egestas. Fusce molestie commodo leo eu ultricies. Nulla mollis rhoncus pharetra. Pellentesque rutrum mauris ac lorem "
+"posuere, a eleifend mi rutrum. Nulla porta turpis aliquet felis congue rutrum. Fusce quis arcu in sem placerat "
+"condimentum a ut turpis. Quisque quis porttitor nulla. Donec sit amet quam tincidunt, pulvinar erat id, molestie "
+"dolor. Praesent luctus vitae nunc vitae pellentesque. Praesent faucibus sed urna ut lacinia. Vivamus id justo quis "
+"dolor porta rutrum nec nec odio. Cras euismod tortor quis diam ultrices, eu mattis nisi consectetur. Fusce mattis "
+"nisi vel condimentum molestie. Fusce fringilla ut nibh volutpat elementum. Mauris posuere consectetur leo a aliquet. "
+"Donec quis sodales sapien. Maecenas ut felis tempus, eleifend mauris et, faucibus mi. Quisque fringilla orci arcu, "
+"sit amet porta risus hendrerit non. Aenean id sem nisi. Nullam non nisl vestibulum, pellentesque nisl et, imperdiet "
+"ligula. Sed laoreet fringilla felis. Proin ac dolor viverra tellus mollis aliquet eget et neque. Suspendisse mattis "
+"nulla vitae nulla sagittis blandit. Sed at tortor rutrum, ornare magna nec, pellentesque nisi. Etiam non aliquet "
+"tellus. Aliquam at ex suscipit, posuere sem sit amet, tincidunt."
+#endif
+;
+
+/* clang-format on */
+
+#endif /* __BLENDER_TESTING_BLI_RESSOURCE_STRING_H__ */
diff --git a/source/blender/blenlib/tests/BLI_session_uuid_test.cc b/source/blender/blenlib/tests/BLI_session_uuid_test.cc
new file mode 100644
index 00000000000..1a5f17be06c
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_session_uuid_test.cc
@@ -0,0 +1,20 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_session_uuid.h"
+
+TEST(SessionUUID, GenerateBasic)
+{
+ {
+ const SessionUUID uuid = BLI_session_uuid_generate();
+ EXPECT_TRUE(BLI_session_uuid_is_generated(&uuid));
+ }
+
+ {
+ const SessionUUID uuid1 = BLI_session_uuid_generate();
+ const SessionUUID uuid2 = BLI_session_uuid_generate();
+
+ EXPECT_FALSE(BLI_session_uuid_is_equal(&uuid1, &uuid2));
+ }
+}
diff --git a/source/blender/blenlib/tests/BLI_stack_test.cc b/source/blender/blenlib/tests/BLI_stack_test.cc
new file mode 100644
index 00000000000..211916e3193
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_stack_test.cc
@@ -0,0 +1,216 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+#include <string.h>
+
+#include "BLI_array.h"
+#include "BLI_stack.h"
+#include "BLI_utildefines.h"
+
+#define SIZE 1024
+
+/* number of items per chunk. use a small value to expose bugs */
+#define STACK_CHUNK_SIZE 8
+
+/* Ensure block size is set to #STACK_NEW_EX_ARGS */
+#define BLI_stack_new(esize, descr) BLI_stack_new_ex(esize, descr, esize *STACK_CHUNK_SIZE)
+
+TEST(stack, Empty)
+{
+ BLI_Stack *stack;
+
+ stack = BLI_stack_new(sizeof(int), __func__);
+ EXPECT_TRUE(BLI_stack_is_empty(stack));
+ EXPECT_EQ(BLI_stack_count(stack), 0);
+ BLI_stack_free(stack);
+}
+
+TEST(stack, One)
+{
+ BLI_Stack *stack;
+ unsigned int in = -1, out = 1;
+
+ stack = BLI_stack_new(sizeof(in), __func__);
+
+ BLI_stack_push(stack, (void *)&in);
+ EXPECT_FALSE(BLI_stack_is_empty(stack));
+ EXPECT_EQ(BLI_stack_count(stack), 1);
+ BLI_stack_pop(stack, (void *)&out);
+ EXPECT_EQ(out, in);
+ EXPECT_TRUE(BLI_stack_is_empty(stack));
+ EXPECT_EQ(BLI_stack_count(stack), 0);
+ BLI_stack_free(stack);
+}
+
+TEST(stack, Range)
+{
+ const int tot = SIZE;
+ BLI_Stack *stack;
+ int in, out;
+
+ stack = BLI_stack_new(sizeof(in), __func__);
+
+ for (in = 0; in < tot; in++) {
+ BLI_stack_push(stack, (void *)&in);
+ }
+
+ for (in = tot - 1; in >= 0; in--) {
+ EXPECT_FALSE(BLI_stack_is_empty(stack));
+ BLI_stack_pop(stack, (void *)&out);
+ EXPECT_EQ(out, in);
+ }
+ EXPECT_TRUE(BLI_stack_is_empty(stack));
+
+ BLI_stack_free(stack);
+}
+
+TEST(stack, String)
+{
+ const int tot = SIZE;
+ int i;
+
+ BLI_Stack *stack;
+ char in[] = "hello world!";
+ char out[sizeof(in)];
+
+ stack = BLI_stack_new(sizeof(in), __func__);
+
+ for (i = 0; i < tot; i++) {
+ *((int *)in) = i;
+ BLI_stack_push(stack, (void *)in);
+ }
+
+ for (i = tot - 1; i >= 0; i--) {
+ EXPECT_FALSE(BLI_stack_is_empty(stack));
+ *((int *)in) = i;
+ BLI_stack_pop(stack, (void *)&out);
+ EXPECT_STREQ(in, out);
+ }
+ EXPECT_TRUE(BLI_stack_is_empty(stack));
+
+ BLI_stack_free(stack);
+}
+
+TEST(stack, Peek)
+{
+ const int tot = SIZE;
+ int i;
+
+ BLI_Stack *stack;
+ const short in[] = {1, 10, 100, 1000};
+
+ stack = BLI_stack_new(sizeof(*in), __func__);
+
+ for (i = 0; i < tot; i++) {
+ BLI_stack_push(stack, &in[i % ARRAY_SIZE(in)]);
+ }
+
+ for (i = tot - 1; i >= 0; i--, BLI_stack_discard(stack)) {
+ short *ret = (short *)BLI_stack_peek(stack);
+ EXPECT_EQ(*ret, in[i % ARRAY_SIZE(in)]);
+ }
+
+ EXPECT_TRUE(BLI_stack_is_empty(stack));
+
+ BLI_stack_free(stack);
+}
+
+/* Check that clearing the stack leaves in it a correct state. */
+TEST(stack, Clear)
+{
+ const int tot_rerun = 4;
+ int rerun;
+
+ /* based on range test */
+ int tot = SIZE;
+ BLI_Stack *stack;
+ int in, out;
+
+ /* use a small chunk size to ensure we test */
+ stack = BLI_stack_new(sizeof(in), __func__);
+
+ for (rerun = 0; rerun < tot_rerun; rerun++) {
+ for (in = 0; in < tot; in++) {
+ BLI_stack_push(stack, (void *)&in);
+ }
+
+ BLI_stack_clear(stack);
+ EXPECT_TRUE(BLI_stack_is_empty(stack));
+
+ /* and again, this time check its valid */
+ for (in = 0; in < tot; in++) {
+ BLI_stack_push(stack, (void *)&in);
+ }
+
+ for (in = tot - 1; in >= 0; in--) {
+ EXPECT_FALSE(BLI_stack_is_empty(stack));
+ BLI_stack_pop(stack, (void *)&out);
+ EXPECT_EQ(out, in);
+ }
+
+ EXPECT_TRUE(BLI_stack_is_empty(stack));
+
+ /* without this, we wont test case when mixed free/used */
+ tot /= 2;
+ }
+
+ BLI_stack_free(stack);
+}
+
+TEST(stack, Reuse)
+{
+ const int sizes[] = {3, 11, 81, 400, 999, 12, 1, 9721, 7, 99, 5, 0};
+ int sizes_test[ARRAY_SIZE(sizes)];
+ const int *s;
+ int out, i;
+ int sum, sum_test;
+
+ BLI_Stack *stack;
+
+ stack = BLI_stack_new(sizeof(i), __func__);
+
+ /* add a bunch of numbers, ensure we get same sum out */
+ sum = 0;
+ for (s = sizes; *s; s++) {
+ for (i = *s; i != 0; i--) {
+ BLI_stack_push(stack, (void *)&i);
+ sum += i;
+ }
+ }
+ sum_test = 0;
+ while (!BLI_stack_is_empty(stack)) {
+ BLI_stack_pop(stack, (void *)&out);
+ sum_test += out;
+ }
+ EXPECT_EQ(sum, sum_test);
+
+ /* add and remove all except last */
+ for (s = sizes; *s; s++) {
+ for (i = *s; i >= 0; i--) {
+ BLI_stack_push(stack, (void *)&i);
+ }
+ for (i = *s; i > 0; i--) {
+ BLI_stack_pop(stack, (void *)&out);
+ }
+ }
+
+ i = ARRAY_SIZE(sizes) - 1;
+ while (!BLI_stack_is_empty(stack)) {
+ i--;
+ BLI_stack_pop(stack, (void *)&sizes_test[i]);
+ EXPECT_EQ(sizes_test[i], sizes[i]);
+ EXPECT_GT(i, -1);
+ }
+ EXPECT_EQ(0, i);
+ EXPECT_EQ(memcmp(sizes, sizes_test, sizeof(sizes) - sizeof(int)), 0);
+
+ /* finally test BLI_stack_pop_n */
+ for (i = ARRAY_SIZE(sizes); i--;) {
+ BLI_stack_push(stack, (void *)&sizes[i]);
+ }
+ EXPECT_EQ(BLI_stack_count(stack), ARRAY_SIZE(sizes));
+ BLI_stack_pop_n(stack, (void *)sizes_test, ARRAY_SIZE(sizes));
+ EXPECT_EQ(memcmp(sizes, sizes_test, sizeof(sizes) - sizeof(int)), 0);
+
+ BLI_stack_free(stack);
+}
diff --git a/source/blender/blenlib/tests/BLI_string_test.cc b/source/blender/blenlib/tests/BLI_string_test.cc
new file mode 100644
index 00000000000..1760b7966e3
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_string_test.cc
@@ -0,0 +1,794 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include <array>
+#include <initializer_list>
+#include <ostream> // NOLINT
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "BLI_string.h"
+#include "BLI_string_utf8.h"
+#include "BLI_string_utils.h"
+#include "BLI_utildefines.h"
+
+using std::initializer_list;
+using std::pair;
+using std::string;
+using std::vector;
+
+/* -------------------------------------------------------------------- */
+/* tests */
+
+/* BLI_str_partition */
+TEST(string, StrPartition)
+{
+ const char delim[] = {'-', '.', '_', '~', '\\', '\0'};
+ const char *sep, *suf;
+ size_t pre_ln;
+
+ {
+ const char *str = "mat.e-r_ial";
+
+ /* "mat.e-r_ial" -> "mat", '.', "e-r_ial", 3 */
+ pre_ln = BLI_str_partition(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 3);
+ EXPECT_EQ(&str[3], sep);
+ EXPECT_STREQ("e-r_ial", suf);
+ }
+
+ /* Corner cases. */
+ {
+ const char *str = ".mate-rial--";
+
+ /* ".mate-rial--" -> "", '.', "mate-rial--", 0 */
+ pre_ln = BLI_str_partition(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 0);
+ EXPECT_EQ(&str[0], sep);
+ EXPECT_STREQ("mate-rial--", suf);
+ }
+
+ {
+ const char *str = ".__.--_";
+
+ /* ".__.--_" -> "", '.', "__.--_", 0 */
+ pre_ln = BLI_str_partition(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 0);
+ EXPECT_EQ(&str[0], sep);
+ EXPECT_STREQ("__.--_", suf);
+ }
+
+ {
+ const char *str = "";
+
+ /* "" -> "", NULL, NULL, 0 */
+ pre_ln = BLI_str_partition(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 0);
+ EXPECT_EQ(sep, (void *)NULL);
+ EXPECT_EQ(suf, (void *)NULL);
+ }
+
+ {
+ const char *str = "material";
+
+ /* "material" -> "material", NULL, NULL, 8 */
+ pre_ln = BLI_str_partition(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 8);
+ EXPECT_EQ(sep, (void *)NULL);
+ EXPECT_EQ(suf, (void *)NULL);
+ }
+}
+
+/* BLI_str_rpartition */
+TEST(string, StrRPartition)
+{
+ const char delim[] = {'-', '.', '_', '~', '\\', '\0'};
+ const char *sep, *suf;
+ size_t pre_ln;
+
+ {
+ const char *str = "mat.e-r_ial";
+
+ /* "mat.e-r_ial" -> "mat.e-r", '_', "ial", 7 */
+ pre_ln = BLI_str_rpartition(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 7);
+ EXPECT_EQ(&str[7], sep);
+ EXPECT_STREQ("ial", suf);
+ }
+
+ /* Corner cases. */
+ {
+ const char *str = ".mate-rial--";
+
+ /* ".mate-rial--" -> ".mate-rial-", '-', "", 11 */
+ pre_ln = BLI_str_rpartition(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 11);
+ EXPECT_EQ(&str[11], sep);
+ EXPECT_STREQ("", suf);
+ }
+
+ {
+ const char *str = ".__.--_";
+
+ /* ".__.--_" -> ".__.--", '_', "", 6 */
+ pre_ln = BLI_str_rpartition(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 6);
+ EXPECT_EQ(&str[6], sep);
+ EXPECT_STREQ("", suf);
+ }
+
+ {
+ const char *str = "";
+
+ /* "" -> "", NULL, NULL, 0 */
+ pre_ln = BLI_str_rpartition(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 0);
+ EXPECT_EQ(sep, (void *)NULL);
+ EXPECT_EQ(suf, (void *)NULL);
+ }
+
+ {
+ const char *str = "material";
+
+ /* "material" -> "material", NULL, NULL, 8 */
+ pre_ln = BLI_str_rpartition(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 8);
+ EXPECT_EQ(sep, (void *)NULL);
+ EXPECT_EQ(suf, (void *)NULL);
+ }
+}
+
+/* BLI_str_partition_ex */
+TEST(string, StrPartitionEx)
+{
+ const char delim[] = {'-', '.', '_', '~', '\\', '\0'};
+ const char *sep, *suf;
+ size_t pre_ln;
+
+ /* Only considering 'from_right' cases here. */
+
+ {
+ const char *str = "mat.e-r_ia.l";
+
+ /* "mat.e-r_ia.l" over "mat.e-r" -> "mat.e", '.', "r_ia.l", 3 */
+ pre_ln = BLI_str_partition_ex(str, str + 6, delim, &sep, &suf, true);
+ EXPECT_EQ(pre_ln, 5);
+ EXPECT_EQ(&str[5], sep);
+ EXPECT_STREQ("r_ia.l", suf);
+ }
+
+ /* Corner cases. */
+ {
+ const char *str = "mate.rial";
+
+ /* "mate.rial" over "mate" -> "mate.rial", NULL, NULL, 4 */
+ pre_ln = BLI_str_partition_ex(str, str + 4, delim, &sep, &suf, true);
+ EXPECT_EQ(pre_ln, 4);
+ EXPECT_EQ(sep, (void *)NULL);
+ EXPECT_EQ(suf, (void *)NULL);
+ }
+}
+
+/* BLI_str_partition_utf8 */
+TEST(string, StrPartitionUtf8)
+{
+ const unsigned int delim[] = {'-', '.', '_', 0x00F1 /* n tilde */, 0x262F /* ying-yang */, '\0'};
+ const char *sep, *suf;
+ size_t pre_ln;
+
+ {
+ const char *str = "ma\xc3\xb1te-r\xe2\x98\xafial";
+
+ /* "ma\xc3\xb1te-r\xe2\x98\xafial" -> "ma", '\xc3\xb1', "te-r\xe2\x98\xafial", 2 */
+ pre_ln = BLI_str_partition_utf8(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 2);
+ EXPECT_EQ(&str[2], sep);
+ EXPECT_STREQ("te-r\xe2\x98\xafial", suf);
+ }
+
+ /* Corner cases. */
+ {
+ const char *str = "\xe2\x98\xafmate-rial-\xc3\xb1";
+
+ /* "\xe2\x98\xafmate-rial-\xc3\xb1" -> "", '\xe2\x98\xaf', "mate-rial-\xc3\xb1", 0 */
+ pre_ln = BLI_str_partition_utf8(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 0);
+ EXPECT_EQ(&str[0], sep);
+ EXPECT_STREQ("mate-rial-\xc3\xb1", suf);
+ }
+
+ {
+ const char *str = "\xe2\x98\xaf.\xc3\xb1_.--\xc3\xb1";
+
+ /* "\xe2\x98\xaf.\xc3\xb1_.--\xc3\xb1" -> "", '\xe2\x98\xaf', ".\xc3\xb1_.--\xc3\xb1", 0 */
+ pre_ln = BLI_str_partition_utf8(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 0);
+ EXPECT_EQ(&str[0], sep);
+ EXPECT_STREQ(".\xc3\xb1_.--\xc3\xb1", suf);
+ }
+
+ {
+ const char *str = "";
+
+ /* "" -> "", NULL, NULL, 0 */
+ pre_ln = BLI_str_partition_utf8(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 0);
+ EXPECT_EQ(sep, (void *)NULL);
+ EXPECT_EQ(suf, (void *)NULL);
+ }
+
+ {
+ const char *str = "material";
+
+ /* "material" -> "material", NULL, NULL, 8 */
+ pre_ln = BLI_str_partition_utf8(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 8);
+ EXPECT_EQ(sep, (void *)NULL);
+ EXPECT_EQ(suf, (void *)NULL);
+ }
+}
+
+/* BLI_str_rpartition_utf8 */
+TEST(string, StrRPartitionUtf8)
+{
+ const unsigned int delim[] = {'-', '.', '_', 0x00F1 /* n tilde */, 0x262F /* ying-yang */, '\0'};
+ const char *sep, *suf;
+ size_t pre_ln;
+
+ {
+ const char *str = "ma\xc3\xb1te-r\xe2\x98\xafial";
+
+ /* "ma\xc3\xb1te-r\xe2\x98\xafial" -> "mat\xc3\xb1te-r", '\xe2\x98\xaf', "ial", 8 */
+ pre_ln = BLI_str_rpartition_utf8(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 8);
+ EXPECT_EQ(&str[8], sep);
+ EXPECT_STREQ("ial", suf);
+ }
+
+ /* Corner cases. */
+ {
+ const char *str = "\xe2\x98\xafmate-rial-\xc3\xb1";
+
+ /* "\xe2\x98\xafmate-rial-\xc3\xb1" -> "\xe2\x98\xafmate-rial-", '\xc3\xb1', "", 13 */
+ pre_ln = BLI_str_rpartition_utf8(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 13);
+ EXPECT_EQ(&str[13], sep);
+ EXPECT_STREQ("", suf);
+ }
+
+ {
+ const char *str = "\xe2\x98\xaf.\xc3\xb1_.--\xc3\xb1";
+
+ /* "\xe2\x98\xaf.\xc3\xb1_.--\xc3\xb1" -> "\xe2\x98\xaf.\xc3\xb1_.--", '\xc3\xb1', "", 10 */
+ pre_ln = BLI_str_rpartition_utf8(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 10);
+ EXPECT_EQ(&str[10], sep);
+ EXPECT_STREQ("", suf);
+ }
+
+ {
+ const char *str = "";
+
+ /* "" -> "", NULL, NULL, 0 */
+ pre_ln = BLI_str_rpartition_utf8(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 0);
+ EXPECT_EQ(sep, (void *)NULL);
+ EXPECT_EQ(suf, (void *)NULL);
+ }
+
+ {
+ const char *str = "material";
+
+ /* "material" -> "material", NULL, NULL, 8 */
+ pre_ln = BLI_str_rpartition_utf8(str, delim, &sep, &suf);
+ EXPECT_EQ(pre_ln, 8);
+ EXPECT_EQ(sep, (void *)NULL);
+ EXPECT_EQ(suf, (void *)NULL);
+ }
+}
+
+/* BLI_str_partition_ex_utf8 */
+TEST(string, StrPartitionExUtf8)
+{
+ const unsigned int delim[] = {'-', '.', '_', 0x00F1 /* n tilde */, 0x262F /* ying-yang */, '\0'};
+ const char *sep, *suf;
+ size_t pre_ln;
+
+ /* Only considering 'from_right' cases here. */
+
+ {
+ const char *str = "ma\xc3\xb1te-r\xe2\x98\xafial";
+
+ /* "ma\xc3\xb1te-r\xe2\x98\xafial" over
+ * "ma\xc3\xb1te" -> "ma", '\xc3\xb1', "te-r\xe2\x98\xafial", 2 */
+ pre_ln = BLI_str_partition_ex_utf8(str, str + 6, delim, &sep, &suf, true);
+ EXPECT_EQ(pre_ln, 2);
+ EXPECT_EQ(&str[2], sep);
+ EXPECT_STREQ("te-r\xe2\x98\xafial", suf);
+ }
+
+ /* Corner cases. */
+ {
+ const char *str = "mate\xe2\x98\xafrial";
+
+ /* "mate\xe2\x98\xafrial" over "mate" -> "mate\xe2\x98\xafrial", NULL, NULL, 4 */
+ pre_ln = BLI_str_partition_ex_utf8(str, str + 4, delim, &sep, &suf, true);
+ EXPECT_EQ(pre_ln, 4);
+ EXPECT_EQ(sep, (void *)NULL);
+ EXPECT_EQ(suf, (void *)NULL);
+ }
+}
+
+/* BLI_str_format_int_grouped */
+TEST(string, StrFormatIntGrouped)
+{
+ char num_str[16];
+ int num;
+
+ BLI_str_format_int_grouped(num_str, num = 0);
+ EXPECT_STREQ("0", num_str);
+
+ BLI_str_format_int_grouped(num_str, num = 1);
+ EXPECT_STREQ("1", num_str);
+
+ BLI_str_format_int_grouped(num_str, num = -1);
+ EXPECT_STREQ("-1", num_str);
+
+ BLI_str_format_int_grouped(num_str, num = -2147483648);
+ EXPECT_STREQ("-2,147,483,648", num_str);
+
+ BLI_str_format_int_grouped(num_str, num = 2147483647);
+ EXPECT_STREQ("2,147,483,647", num_str);
+
+ BLI_str_format_int_grouped(num_str, num = 1000);
+ EXPECT_STREQ("1,000", num_str);
+
+ BLI_str_format_int_grouped(num_str, num = -1000);
+ EXPECT_STREQ("-1,000", num_str);
+
+ BLI_str_format_int_grouped(num_str, num = 999);
+ EXPECT_STREQ("999", num_str);
+
+ BLI_str_format_int_grouped(num_str, num = -999);
+ EXPECT_STREQ("-999", num_str);
+}
+
+/* BLI_str_format_byte_unit */
+TEST(string, StrFormatByteUnits)
+{
+ char size_str[15];
+ long long int size;
+
+ /* Base 10 */
+ BLI_str_format_byte_unit(size_str, size = 0, true);
+ EXPECT_STREQ("0 B", size_str);
+ BLI_str_format_byte_unit(size_str, size = -0, true);
+ EXPECT_STREQ("0 B", size_str);
+
+ BLI_str_format_byte_unit(size_str, size = 1, true);
+ EXPECT_STREQ("1 B", size_str);
+ BLI_str_format_byte_unit(size_str, size = -1, true);
+ EXPECT_STREQ("-1 B", size_str);
+
+ BLI_str_format_byte_unit(size_str, size = 1000, true);
+ EXPECT_STREQ("1 KB", size_str);
+ BLI_str_format_byte_unit(size_str, size = -1000, true);
+ EXPECT_STREQ("-1 KB", size_str);
+
+ BLI_str_format_byte_unit(size_str, size = 1024, true);
+ EXPECT_STREQ("1 KB", size_str);
+ BLI_str_format_byte_unit(size_str, size = -1024, true);
+ EXPECT_STREQ("-1 KB", size_str);
+
+ /* LLONG_MAX - largest possible value */
+ BLI_str_format_byte_unit(size_str, size = 9223372036854775807, true);
+ EXPECT_STREQ("9223.372 PB", size_str);
+ BLI_str_format_byte_unit(size_str, size = -9223372036854775807, true);
+ EXPECT_STREQ("-9223.372 PB", size_str);
+
+ /* Base 2 */
+ BLI_str_format_byte_unit(size_str, size = 0, false);
+ EXPECT_STREQ("0 B", size_str);
+ BLI_str_format_byte_unit(size_str, size = -0, false);
+ EXPECT_STREQ("0 B", size_str);
+
+ BLI_str_format_byte_unit(size_str, size = 1, false);
+ EXPECT_STREQ("1 B", size_str);
+ BLI_str_format_byte_unit(size_str, size = -1, false);
+ EXPECT_STREQ("-1 B", size_str);
+
+ BLI_str_format_byte_unit(size_str, size = 1000, false);
+ EXPECT_STREQ("1000 B", size_str);
+ BLI_str_format_byte_unit(size_str, size = -1000, false);
+ EXPECT_STREQ("-1000 B", size_str);
+
+ BLI_str_format_byte_unit(size_str, size = 1024, false);
+ EXPECT_STREQ("1 KiB", size_str);
+ BLI_str_format_byte_unit(size_str, size = -1024, false);
+ EXPECT_STREQ("-1 KiB", size_str);
+
+ /* LLONG_MAX - largest possible value */
+ BLI_str_format_byte_unit(size_str, size = 9223372036854775807, false);
+ EXPECT_STREQ("8192.0 PiB", size_str);
+ BLI_str_format_byte_unit(size_str, size = -9223372036854775807, false);
+ EXPECT_STREQ("-8192.0 PiB", size_str);
+
+ /* Test maximum string length. */
+ BLI_str_format_byte_unit(size_str, size = -9223200000000000000, false);
+ EXPECT_STREQ("-8191.8472 PiB", size_str);
+}
+
+struct WordInfo {
+ WordInfo()
+ {
+ }
+ WordInfo(int start, int end) : start(start), end(end)
+ {
+ }
+ bool operator==(const WordInfo &other) const
+ {
+ return start == other.start && end == other.end;
+ }
+ int start, end;
+};
+static std::ostream &operator<<(std::ostream &os, const WordInfo &word_info)
+{
+ os << "start: " << word_info.start << ", end: " << word_info.end;
+ return os;
+}
+
+class StringFindSplitWords : public testing::Test {
+ protected:
+ StringFindSplitWords()
+ {
+ }
+
+ /* If max_words is -1 it will be initialized from the number of expected
+ * words +1. This way there is no need to pass an explicit number of words,
+ * but is also making it possible to catch situation when too many words
+ * are being returned. */
+ void testStringFindSplitWords(const string &str,
+ const size_t max_length,
+ initializer_list<WordInfo> expected_words_info_init,
+ int max_words = -1)
+ {
+ const vector<WordInfo> expected_words_info = expected_words_info_init;
+ if (max_words != -1) {
+ CHECK_LE(max_words, expected_words_info.size() - 1);
+ }
+ /* Since number of word info is used here, this makes it so we allow one
+ * extra word to be collected from the input. This allows to catch possible
+ * issues with word splitting not doing a correct thing. */
+ const int effective_max_words = (max_words == -1) ? expected_words_info.size() : max_words;
+ /* One extra element for the {-1, -1}. */
+ vector<WordInfo> actual_word_info(effective_max_words + 1, WordInfo(-1, -1));
+ const int actual_word_num = BLI_string_find_split_words(
+ str.c_str(),
+ max_length,
+ ' ',
+ reinterpret_cast<int(*)[2]>(actual_word_info.data()),
+ effective_max_words);
+ /* Schrink actual array to an actual number of words, so we can compare
+ * vectors as-is. */
+ EXPECT_LE(actual_word_num, actual_word_info.size() - 1);
+ actual_word_info.resize(actual_word_num + 1);
+ /* Perform actual comparison. */
+ EXPECT_EQ_VECTOR(actual_word_info, expected_words_info);
+ }
+
+ void testStringFindSplitWords(const string &str,
+ initializer_list<WordInfo> expected_words_info_init)
+ {
+ testStringFindSplitWords(str, str.length(), expected_words_info_init);
+ }
+};
+
+/* BLI_string_find_split_words */
+TEST_F(StringFindSplitWords, Simple)
+{
+ testStringFindSplitWords("t", {{0, 1}, {-1, -1}});
+ testStringFindSplitWords("test", {{0, 4}, {-1, -1}});
+}
+TEST_F(StringFindSplitWords, Triple)
+{
+ testStringFindSplitWords("f t w", {{0, 1}, {2, 1}, {4, 1}, {-1, -1}});
+ testStringFindSplitWords("find three words", {{0, 4}, {5, 5}, {11, 5}, {-1, -1}});
+}
+TEST_F(StringFindSplitWords, Spacing)
+{
+ testStringFindSplitWords("# ## ### ####", {{0, 1}, {2, 2}, {5, 3}, {9, 4}, {-1, -1}});
+ testStringFindSplitWords("# # # #", {{0, 1}, {3, 1}, {7, 1}, {12, 1}, {-1, -1}});
+}
+TEST_F(StringFindSplitWords, Trailing_Left)
+{
+ testStringFindSplitWords(" t", {{3, 1}, {-1, -1}});
+ testStringFindSplitWords(" test", {{3, 4}, {-1, -1}});
+}
+TEST_F(StringFindSplitWords, Trailing_Right)
+{
+ testStringFindSplitWords("t ", {{0, 1}, {-1, -1}});
+ testStringFindSplitWords("test ", {{0, 4}, {-1, -1}});
+}
+TEST_F(StringFindSplitWords, Trailing_LeftRight)
+{
+ testStringFindSplitWords(" surrounding space test 123 ",
+ {{3, 11}, {15, 5}, {21, 4}, {28, 3}, {-1, -1}});
+}
+TEST_F(StringFindSplitWords, Blank)
+{
+ testStringFindSplitWords("", {{-1, -1}});
+}
+TEST_F(StringFindSplitWords, Whitespace)
+{
+ testStringFindSplitWords(" ", {{-1, -1}});
+ testStringFindSplitWords(" ", {{-1, -1}});
+}
+TEST_F(StringFindSplitWords, LimitWords)
+{
+ const string words = "too many chars";
+ const int words_len = words.length();
+ testStringFindSplitWords(words, words_len, {{0, 3}, {4, 4}, {9, 5}, {-1, -1}}, 3);
+ testStringFindSplitWords(words, words_len, {{0, 3}, {4, 4}, {-1, -1}}, 2);
+ testStringFindSplitWords(words, words_len, {{0, 3}, {-1, -1}}, 1);
+ testStringFindSplitWords(words, words_len, {{-1, -1}}, 0);
+}
+TEST_F(StringFindSplitWords, LimitChars)
+{
+ const string words = "too many chars";
+ const int words_len = words.length();
+ testStringFindSplitWords(words, words_len, {{0, 3}, {4, 4}, {9, 5}, {-1, -1}});
+ testStringFindSplitWords(words, words_len - 1, {{0, 3}, {4, 4}, {9, 4}, {-1, -1}});
+ testStringFindSplitWords(words, words_len - 5, {{0, 3}, {4, 4}, {-1, -1}});
+ testStringFindSplitWords(words, 1, {{0, 1}, {-1, -1}});
+ testStringFindSplitWords(words, 0, {{-1, -1}});
+}
+
+/* BLI_strncasestr */
+TEST(string, StringStrncasestr)
+{
+ const char *str_test0 = "search here";
+ const char *res;
+
+ res = BLI_strncasestr(str_test0, "", 0);
+ EXPECT_EQ(res, str_test0);
+
+ res = BLI_strncasestr(str_test0, " ", 1);
+ EXPECT_EQ(res, str_test0 + 6);
+
+ res = BLI_strncasestr(str_test0, "her", 3);
+ EXPECT_EQ(res, str_test0 + 7);
+
+ res = BLI_strncasestr(str_test0, "ARCh", 4);
+ EXPECT_EQ(res, str_test0 + 2);
+
+ res = BLI_strncasestr(str_test0, "earcq", 4);
+ EXPECT_EQ(res, str_test0 + 1);
+
+ res = BLI_strncasestr(str_test0, "not there", 9);
+ EXPECT_EQ(res, (void *)NULL);
+}
+
+/* BLI_string_is_decimal */
+TEST(string, StrIsDecimal)
+{
+ EXPECT_FALSE(BLI_string_is_decimal(""));
+ EXPECT_FALSE(BLI_string_is_decimal("je moeder"));
+ EXPECT_FALSE(BLI_string_is_decimal("je møder"));
+ EXPECT_FALSE(BLI_string_is_decimal("Agent 327"));
+ EXPECT_FALSE(BLI_string_is_decimal("Agent\000327"));
+ EXPECT_FALSE(BLI_string_is_decimal("\000327"));
+ EXPECT_FALSE(BLI_string_is_decimal("0x16"));
+ EXPECT_FALSE(BLI_string_is_decimal("16.4"));
+ EXPECT_FALSE(BLI_string_is_decimal("-1"));
+
+ EXPECT_TRUE(BLI_string_is_decimal("0"));
+ EXPECT_TRUE(BLI_string_is_decimal("1"));
+ EXPECT_TRUE(BLI_string_is_decimal("001"));
+ EXPECT_TRUE(BLI_string_is_decimal("11342908713948713498745980171334059871345098713405981734"));
+}
+
+/* BLI_strcasecmp_natural */
+class StringCasecmpNatural : public testing::Test {
+ protected:
+ StringCasecmpNatural() = default;
+
+ using CompareWordsArray = vector<std::array<const char *, 2>>;
+
+ void testReturnsZeroForAll(const CompareWordsArray &items)
+ {
+ for (auto &item : items) {
+ int res = BLI_strcasecmp_natural(item[0], item[1]);
+ EXPECT_EQ(res, 0);
+ }
+ }
+ void testReturnsLessThanZeroForAll(const CompareWordsArray &items)
+ {
+ for (auto &item : items) {
+ int res = BLI_strcasecmp_natural(item[0], item[1]);
+ EXPECT_LT(res, 0);
+ }
+ }
+ void testReturnsMoreThanZeroForAll(const CompareWordsArray &items)
+ {
+ for (auto &item : items) {
+ int res = BLI_strcasecmp_natural(item[0], item[1]);
+ EXPECT_GT(res, 0);
+ }
+ }
+
+ CompareWordsArray copyWithSwappedWords(const CompareWordsArray &items)
+ {
+ CompareWordsArray ret_array;
+
+ /* E.g. {{"a", "b"}, {"ab", "cd"}} becomes {{"b", "a"}, {"cd", "ab"}} */
+
+ ret_array.reserve(items.size());
+ for (auto &item : items) {
+ ret_array.push_back({item[1], item[0]});
+ }
+
+ return ret_array;
+ }
+};
+
+TEST_F(StringCasecmpNatural, Empty)
+{
+ const CompareWordsArray equal{
+ {"", ""},
+ };
+ const CompareWordsArray negative{
+ {"", "a"},
+ {"", "A"},
+ };
+ CompareWordsArray positive = copyWithSwappedWords(negative);
+
+ testReturnsZeroForAll(equal);
+ testReturnsLessThanZeroForAll(negative);
+ testReturnsMoreThanZeroForAll(positive);
+}
+
+TEST_F(StringCasecmpNatural, Whitespace)
+{
+ const CompareWordsArray equal{
+ {" ", " "},
+ {" a", " a"},
+ {" a ", " a "},
+ };
+ const CompareWordsArray negative{
+ {"", " "},
+ {"", " a"},
+ {"", " a "},
+ {" ", " a"},
+ };
+ CompareWordsArray positive = copyWithSwappedWords(negative);
+
+ testReturnsZeroForAll(equal);
+ testReturnsLessThanZeroForAll(negative);
+ testReturnsMoreThanZeroForAll(positive);
+}
+
+TEST_F(StringCasecmpNatural, TextOnlyLowerCase)
+{
+ const CompareWordsArray equal{
+ {"a", "a"},
+ {"aa", "aa"},
+ {"ab", "ab"},
+ {"ba", "ba"},
+ {"je møder", "je møder"},
+ };
+ const CompareWordsArray negative{
+ {"a", "b"},
+ {"a", "aa"},
+ {"a", "ab"},
+ {"aa", "b"},
+ {"je møda", "je møder"},
+ };
+ CompareWordsArray positive = copyWithSwappedWords(negative);
+
+ testReturnsZeroForAll(equal);
+ testReturnsLessThanZeroForAll(negative);
+ testReturnsMoreThanZeroForAll(positive);
+}
+
+TEST_F(StringCasecmpNatural, TextMixedCase)
+{
+ const CompareWordsArray equal{
+ {"A", "A"},
+ {"AA", "AA"},
+ {"AB", "AB"},
+ {"Ab", "Ab"},
+ {"aB", "aB"},
+ };
+ const CompareWordsArray negative{
+ {"A", "a"},
+ {"A", "B"},
+ {"A", "b"},
+ {"a", "B"},
+ {"AA", "aA"},
+ {"AA", "aA"},
+ {"Ab", "ab"},
+ {"AB", "Ab"},
+ /* Different lengths */
+ {"A", "ab"},
+ {"Aa", "b"},
+ {"aA", "b"},
+ {"AA", "b"},
+ {"A", "Ab"},
+ {"A", "aB"},
+ {"Aa", "B"},
+ {"aA", "B"},
+ {"AA", "B"},
+ };
+ CompareWordsArray positive = copyWithSwappedWords(negative);
+
+ testReturnsZeroForAll(equal);
+ testReturnsLessThanZeroForAll(negative);
+ testReturnsMoreThanZeroForAll(positive);
+}
+
+TEST_F(StringCasecmpNatural, Period)
+{
+ const CompareWordsArray equal{
+ {".", "."},
+ {". ", ". "},
+ {" .", " ."},
+ {" . ", " . "},
+ };
+ const CompareWordsArray negative{
+ {".", ". "},
+ {" .", " . "},
+ {"foo.bar", "foo 1.bar"},
+ };
+ CompareWordsArray positive = copyWithSwappedWords(negative);
+
+ testReturnsZeroForAll(equal);
+ testReturnsLessThanZeroForAll(negative);
+ testReturnsMoreThanZeroForAll(positive);
+}
+
+TEST_F(StringCasecmpNatural, OnlyNumbers)
+{
+ const CompareWordsArray equal{
+ {"0", "0"},
+ {"0001", "0001"},
+ {"42", "42"},
+ {"0042", "0042"},
+ };
+ const CompareWordsArray negative{
+ /* If numeric values are equal, number of leading zeros is used as tiebreaker. */
+ {"1", "0001"},
+ {"01", "001"},
+ {"0042", "0043"},
+ {"0042", "43"},
+ };
+ const CompareWordsArray positive = copyWithSwappedWords(negative);
+
+ testReturnsZeroForAll(equal);
+ testReturnsLessThanZeroForAll(negative);
+ testReturnsMoreThanZeroForAll(positive);
+}
+
+TEST_F(StringCasecmpNatural, TextAndNumbers)
+{
+ const CompareWordsArray equal{
+ {"00je møder1", "00je møder1"},
+ {".0 ", ".0 "},
+ {" 1.", " 1."},
+ {" .0 ", " .0 "},
+ };
+ const CompareWordsArray negative{
+ {"00je møder0", "00je møder1"},
+ {"05je møder0", "06je møder1"},
+ {"Cube", "Cube.001"},
+ {"Cube.001", "Cube.002"},
+ {"CUbe.001", "Cube.002"},
+ {"CUbe.002", "Cube.002"},
+ };
+ const CompareWordsArray positive = copyWithSwappedWords(negative);
+
+ testReturnsZeroForAll(equal);
+ testReturnsLessThanZeroForAll(negative);
+ testReturnsMoreThanZeroForAll(positive);
+}
diff --git a/source/blender/blenlib/tests/BLI_string_utf8_test.cc b/source/blender/blenlib/tests/BLI_string_utf8_test.cc
new file mode 100644
index 00000000000..96df4d2b71a
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_string_utf8_test.cc
@@ -0,0 +1,286 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "BLI_string.h"
+#include "BLI_string_utf8.h"
+#include "BLI_utildefines.h"
+
+/* Note that 'common' utf-8 variants of string functions (like copy, etc.) are tested in
+ * BLI_string_test.cc However, tests below are specific utf-8 conformance ones, and since they eat
+ * quite their share of lines, they deserved their own file. */
+
+/* -------------------------------------------------------------------- */
+/* tests */
+
+/* Breaking strings is confusing here, prefer over-long lines. */
+/* clang-format off */
+
+/* Each test is made of a 79 bytes (80 with NULL char) string to test, expected string result after
+ * stripping invalid utf8 bytes, and a single-byte string encoded with expected number of errors.
+ *
+ * Based on utf-8 decoder stress-test (https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt)
+ * by Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/> - 2015-08-28 - CC BY 4.0
+ */
+const char *utf8_invalid_tests[][3] = {
+// 1 Some correct UTF-8 text
+ {"You should see the Greek word 'kosme': \"\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\" |",
+ "You should see the Greek word 'kosme': \"\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\" |", "\x00"},
+
+// 2 Boundary condition test cases
+// Note that those will pass for us, those are not erronéous unicode code points
+// (asside from \x00, which is only valid as string terminator).
+// 2.1 First possible sequence of a certain length
+ {"2.1.1 1 byte (U-00000000): \"\x00\" |",
+ "2.1.1 1 byte (U-00000000): \"\" |", "\x01"},
+ {"2.1.2 2 bytes (U-00000080): \"\xc2\x80\" |",
+ "2.1.2 2 bytes (U-00000080): \"\xc2\x80\" |", "\x00"},
+ {"2.1.3 3 bytes (U-00000800): \"\xe0\xa0\x80\" |",
+ "2.1.3 3 bytes (U-00000800): \"\xe0\xa0\x80\" |", "\x00"},
+ {"2.1.4 4 bytes (U-00010000): \"\xf0\x90\x80\x80\" |",
+ "2.1.4 4 bytes (U-00010000): \"\xf0\x90\x80\x80\" |", "\x00"},
+ {"2.1.5 5 bytes (U-00200000): \"\xf8\x88\x80\x80\x80\" |",
+ "2.1.5 5 bytes (U-00200000): \"\xf8\x88\x80\x80\x80\" |", "\x00"},
+ {"2.1.6 6 bytes (U-04000000): \"\xfc\x84\x80\x80\x80\x80\" |",
+ "2.1.6 6 bytes (U-04000000): \"\xfc\x84\x80\x80\x80\x80\" |", "\x00"},
+// 2.2 Last possible sequence of a certain length
+ {"2.2.1 1 byte (U-0000007F): \"\x7f\" |",
+ "2.2.1 1 byte (U-0000007F): \"\x7f\" |", "\x00"},
+ {"2.2.2 2 bytes (U-000007FF): \"\xdf\xbf\" |",
+ "2.2.2 2 bytes (U-000007FF): \"\xdf\xbf\" |", "\x00"},
+ {"2.2.3 3 bytes (U-0000FFFF): \"\xef\xbf\xbf\" |",
+ "2.2.3 3 bytes (U-0000FFFF): \"\" |", "\x03"}, /* matches one of 5.3 sequences... */
+ {"2.2.4 4 bytes (U-001FFFFF): \"\xf7\xbf\xbf\xbf\" |",
+ "2.2.4 4 bytes (U-001FFFFF): \"\xf7\xbf\xbf\xbf\" |", "\x00"},
+ {"2.2.5 5 bytes (U-03FFFFFF): \"\xfb\xbf\xbf\xbf\xbf\" |",
+ "2.2.5 5 bytes (U-03FFFFFF): \"\xfb\xbf\xbf\xbf\xbf\" |", "\x00"},
+ {"2.2.6 6 bytes (U-7FFFFFFF): \"\xfd\xbf\xbf\xbf\xbf\xbf\" |",
+ "2.2.6 6 bytes (U-7FFFFFFF): \"\xfd\xbf\xbf\xbf\xbf\xbf\" |", "\x00"},
+// 2.3 Other boundary conditions
+ {"2.3.1 U-0000D7FF = ed 9f bf = \"\xed\x9f\xbf\" |",
+ "2.3.1 U-0000D7FF = ed 9f bf = \"\xed\x9f\xbf\" |", "\x00"},
+ {"2.3.2 U-0000E000 = ee 80 80 = \"\xee\x80\x80\" |",
+ "2.3.2 U-0000E000 = ee 80 80 = \"\xee\x80\x80\" |", "\x00"},
+ {"2.3.3 U-0000FFFD = ef bf bd = \"\xef\xbf\xbd\" |",
+ "2.3.3 U-0000FFFD = ef bf bd = \"\xef\xbf\xbd\" |", "\x00"},
+ {"2.3.4 U-0010FFFF = f4 8f bf bf = \"\xf4\x8f\xbf\xbf\" |",
+ "2.3.4 U-0010FFFF = f4 8f bf bf = \"\xf4\x8f\xbf\xbf\" |", "\x00"},
+ {"2.3.5 U-00110000 = f4 90 80 80 = \"\xf4\x90\x80\x80\" |",
+ "2.3.5 U-00110000 = f4 90 80 80 = \"\xf4\x90\x80\x80\" |", "\x00"},
+
+// 3 Malformed sequences
+// 3.1 Unexpected continuation bytes
+// Each unexpected continuation byte should be separately signaled as a malformed sequence of its own.
+ {"3.1.1 First continuation byte 0x80: \"\x80\" |",
+ "3.1.1 First continuation byte 0x80: \"\" |", "\x01"},
+ {"3.1.2 Last continuation byte 0xbf: \"\xbf\" |",
+ "3.1.2 Last continuation byte 0xbf: \"\" |", "\x01"},
+ {"3.1.3 2 continuation bytes: \"\x80\xbf\" |",
+ "3.1.3 2 continuation bytes: \"\" |", "\x02"},
+ {"3.1.4 3 continuation bytes: \"\x80\xbf\x80\" |",
+ "3.1.4 3 continuation bytes: \"\" |", "\x03"},
+ {"3.1.5 4 continuation bytes: \"\x80\xbf\x80\xbf\" |",
+ "3.1.5 4 continuation bytes: \"\" |", "\x04"},
+ {"3.1.6 5 continuation bytes: \"\x80\xbf\x80\xbf\x80\" |",
+ "3.1.6 5 continuation bytes: \"\" |", "\x05"},
+ {"3.1.7 6 continuation bytes: \"\x80\xbf\x80\xbf\x80\xbf\" |",
+ "3.1.7 6 continuation bytes: \"\" |", "\x06"},
+ {"3.1.8 7 continuation bytes: \"\x80\xbf\x80\xbf\x80\xbf\x80\" |",
+ "3.1.8 7 continuation bytes: \"\" |", "\x07"},
+// 3.1.9 Sequence of all 64 possible continuation bytes (0x80-0xbf): |
+ {"3.1.9 \"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
+ "\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
+ "\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
+ "\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\" |",
+ "3.1.9 \"\" |", "\x40"},
+// 3.2 Lonely start characters
+// 3.2.1 All 32 first bytes of 2-byte sequences (0xc0-0xdf), each followed by a space character:
+ {"3.2.1 \"\xc0 \xc1 \xc2 \xc3 \xc4 \xc5 \xc6 \xc7 \xc8 \xc9 \xca \xcb \xcc \xcd \xce \xcf "
+ "\xd0 \xd1 \xd2 \xd3 \xd4 \xd5 \xd6 \xd7 \xd8 \xd9 \xda \xdb \xdc \xdd \xde \xdf \" |",
+ "3.2.1 \" \" |", "\x20"},
+// 3.2.2 All 16 first bytes of 3-byte sequences (0xe0-0xef), each followed by a space character:
+ {"3.2.2 \"\xe0 \xe1 \xe2 \xe3 \xe4 \xe5 \xe6 \xe7 \xe8 \xe9 \xea \xeb \xec \xed \xee \xef \" |",
+ "3.2.2 \" \" |", "\x10"},
+// 3.2.3 All 8 first bytes of 4-byte sequences (0xf0-0xf7), each followed by a space character:
+ {"3.2.3 \"\xf0 \xf1 \xf2 \xf3 \xf4 \xf5 \xf6 \xf7 \" |",
+ "3.2.3 \" \" |", "\x08"},
+// 3.2.4 All 4 first bytes of 5-byte sequences (0xf8-0xfb), each followed by a space character:
+ {"3.2.4 \"\xf8 \xf9 \xfa \xfb \" |",
+ "3.2.4 \" \" |", "\x04"},
+// 3.2.5 All 2 first bytes of 6-byte sequences (0xfc-0xfd), each followed by a space character:
+ {"3.2.4 \"\xfc \xfd \" |",
+ "3.2.4 \" \" |", "\x02"},
+// 3.3 Sequences with last continuation byte missing
+// All bytes of an incomplete sequence should be signaled as a single malformed sequence,
+// i.e., you should see only a single replacement character in each of the next 10 tests.
+// (Characters as in section 2)
+ {"3.3.1 2-byte sequence with last byte missing (U+0000): \"\xc0\" |",
+ "3.3.1 2-byte sequence with last byte missing (U+0000): \"\" |", "\x01"},
+ {"3.3.2 3-byte sequence with last byte missing (U+0000): \"\xe0\x80\" |",
+ "3.3.2 3-byte sequence with last byte missing (U+0000): \"\" |", "\x02"},
+ {"3.3.3 4-byte sequence with last byte missing (U+0000): \"\xf0\x80\x80\" |",
+ "3.3.3 4-byte sequence with last byte missing (U+0000): \"\" |", "\x03"},
+ {"3.3.4 5-byte sequence with last byte missing (U+0000): \"\xf8\x80\x80\x80\" |",
+ "3.3.4 5-byte sequence with last byte missing (U+0000): \"\" |", "\x04"},
+ {"3.3.5 6-byte sequence with last byte missing (U+0000): \"\xfc\x80\x80\x80\x80\" |",
+ "3.3.5 6-byte sequence with last byte missing (U+0000): \"\" |", "\x05"},
+ {"3.3.6 2-byte sequence with last byte missing (U-000007FF): \"\xdf\" |",
+ "3.3.6 2-byte sequence with last byte missing (U-000007FF): \"\" |", "\x01"},
+ {"3.3.7 3-byte sequence with last byte missing (U-0000FFFF): \"\xef\xbf\" |",
+ "3.3.7 3-byte sequence with last byte missing (U-0000FFFF): \"\" |", "\x02"},
+ {"3.3.8 4-byte sequence with last byte missing (U-001FFFFF): \"\xf7\xbf\xbf\" |",
+ "3.3.8 4-byte sequence with last byte missing (U-001FFFFF): \"\" |", "\x03"},
+ {"3.3.9 5-byte sequence with last byte missing (U-03FFFFFF): \"\xfb\xbf\xbf\xbf\" |",
+ "3.3.9 5-byte sequence with last byte missing (U-03FFFFFF): \"\" |", "\x04"},
+ {"3.3.10 6-byte sequence with last byte missing (U-7FFFFFFF): \"\xfd\xbf\xbf\xbf\xbf\" |",
+ "3.3.10 6-byte sequence with last byte missing (U-7FFFFFFF): \"\" |", "\x05"},
+// 3.4 Concatenation of incomplete sequences
+// All the 10 sequences of 3.3 concatenated, you should see 10 malformed sequences being signaled:
+ {"3.4 \"\xc0\xe0\x80\xf0\x80\x80\xf8\x80\x80\x80\xfc\x80\x80\x80\x80"
+ "\xdf\xef\xbf\xf7\xbf\xbf\xfb\xbf\xbf\xbf\xfd\xbf\xbf\xbf\xbf\""
+ " |",
+ "3.4 \"\" |", "\x1e"},
+// 3.5 Impossible bytes
+// The following two bytes cannot appear in a correct UTF-8 string
+ {"3.5.1 fe = \"\xfe\" |",
+ "3.5.1 fe = \"\" |", "\x01"},
+ {"3.5.2 ff = \"\xff\" |",
+ "3.5.2 ff = \"\" |", "\x01"},
+ {"3.5.3 fe fe ff ff = \"\xfe\xfe\xff\xff\" |",
+ "3.5.3 fe fe ff ff = \"\" |", "\x04"},
+
+// 4 Overlong sequences
+// The following sequences are not malformed according to the letter of the Unicode 2.0 standard.
+// However, they are longer then necessary and a correct UTF-8 encoder is not allowed to produce them.
+// A "safe UTF-8 decoder" should reject them just like malformed sequences for two reasons:
+// (1) It helps to debug applications if overlong sequences are not treated as valid representations
+// of characters, because this helps to spot problems more quickly. (2) Overlong sequences provide
+// alternative representations of characters, that could maliciously be used to bypass filters that check
+// only for ASCII characters. For instance, a 2-byte encoded line feed (LF) would not be caught by a
+// line counter that counts only 0x0a bytes, but it would still be processed as a line feed by an unsafe
+// UTF-8 decoder later in the pipeline. From a security point of view, ASCII compatibility of UTF-8
+// sequences means also, that ASCII characters are *only* allowed to be represented by ASCII bytes
+// in the range 0x00-0x7f. To ensure this aspect of ASCII compatibility, use only "safe UTF-8 decoders"
+// that reject overlong UTF-8 sequences for which a shorter encoding exists.
+//
+// 4.1 Examples of an overlong ASCII character
+// With a safe UTF-8 decoder, all of the following five overlong representations of the ASCII character
+// slash ("/") should be rejected like a malformed UTF-8 sequence, for instance by substituting it with
+// a replacement character. If you see a slash below, you do not have a safe UTF-8 decoder!
+ {"4.1.1 U+002F = c0 af = \"\xc0\xaf\" |",
+ "4.1.1 U+002F = c0 af = \"\" |", "\x02"},
+ {"4.1.2 U+002F = e0 80 af = \"\xe0\x80\xaf\" |",
+ "4.1.2 U+002F = e0 80 af = \"\" |", "\x03"},
+ {"4.1.3 U+002F = f0 80 80 af = \"\xf0\x80\x80\xaf\" |",
+ "4.1.3 U+002F = f0 80 80 af = \"\" |", "\x04"},
+ {"4.1.4 U+002F = f8 80 80 80 af = \"\xf8\x80\x80\x80\xaf\" |",
+ "4.1.4 U+002F = f8 80 80 80 af = \"\" |", "\x05"},
+ {"4.1.5 U+002F = fc 80 80 80 80 af = \"\xfc\x80\x80\x80\x80\xaf\" |",
+ "4.1.5 U+002F = fc 80 80 80 80 af = \"\" |", "\x06"},
+// 4.2 Maximum overlong sequences
+// Below you see the highest Unicode value that is still resulting in an overlong sequence if represented
+// with the given number of bytes. This is a boundary test for safe UTF-8 decoders. All five characters
+// should be rejected like malformed UTF-8 sequences.
+ {"4.2.1 U-0000007F = c1 bf = \"\xc1\xbf\" |",
+ "4.2.1 U-0000007F = c1 bf = \"\" |", "\x02"},
+ {"4.2.2 U-000007FF = e0 9f bf = \"\xe0\x9f\xbf\" |",
+ "4.2.2 U-000007FF = e0 9f bf = \"\" |", "\x03"},
+ {"4.2.3 U-0000FFFF = f0 8f bf bf = \"\xf0\x8f\xbf\xbf\" |",
+ "4.2.3 U-0000FFFF = f0 8f bf bf = \"\" |", "\x04"},
+ {"4.2.4 U-001FFFFF = f8 87 bf bf bf = \"\xf8\x87\xbf\xbf\xbf\" |",
+ "4.2.4 U-001FFFFF = f8 87 bf bf bf = \"\" |", "\x05"},
+ {"4.2.5 U+0000 = fc 83 bf bf bf bf = \"\xfc\x83\xbf\xbf\xbf\xbf\" |",
+ "4.2.5 U+0000 = fc 83 bf bf bf bf = \"\" |", "\x06"},
+// 4.3 Overlong representation of the NUL character
+// The following five sequences should also be rejected like malformed UTF-8 sequences and should not be
+// treated like the ASCII NUL character.
+ {"4.3.1 U+0000 = c0 80 = \"\xc0\x80\" |",
+ "4.3.1 U+0000 = c0 80 = \"\" |", "\x02"},
+ {"4.3.2 U+0000 = e0 80 80 = \"\xe0\x80\x80\" |",
+ "4.3.2 U+0000 = e0 80 80 = \"\" |", "\x03"},
+ {"4.3.3 U+0000 = f0 80 80 80 = \"\xf0\x80\x80\x80\" |",
+ "4.3.3 U+0000 = f0 80 80 80 = \"\" |", "\x04"},
+ {"4.3.4 U+0000 = f8 80 80 80 80 = \"\xf8\x80\x80\x80\x80\" |",
+ "4.3.4 U+0000 = f8 80 80 80 80 = \"\" |", "\x05"},
+ {"4.3.5 U+0000 = fc 80 80 80 80 80 = \"\xfc\x80\x80\x80\x80\x80\" |",
+ "4.3.5 U+0000 = fc 80 80 80 80 80 = \"\" |", "\x06"},
+
+// 5 Illegal code positions
+// The following UTF-8 sequences should be rejected like malformed sequences, because they never represent
+// valid ISO 10646 characters and a UTF-8 decoder that accepts them might introduce security problems
+// comparable to overlong UTF-8 sequences.
+// 5.1 Single UTF-16 surrogates
+ {"5.1.1 U+D800 = ed a0 80 = \"\xed\xa0\x80\" |",
+ "5.1.1 U+D800 = ed a0 80 = \"\" |", "\x03"},
+ {"5.1.2 U+DB7F = ed ad bf = \"\xed\xad\xbf\" |",
+ "5.1.2 U+DB7F = ed ad bf = \"\" |", "\x03"},
+ {"5.1.3 U+DB80 = ed ae 80 = \"\xed\xae\x80\" |",
+ "5.1.3 U+DB80 = ed ae 80 = \"\" |", "\x03"},
+ {"5.1.4 U+DBFF = ed af bf = \"\xed\xaf\xbf\" |",
+ "5.1.4 U+DBFF = ed af bf = \"\" |", "\x03"},
+ {"5.1.5 U+DC00 = ed b0 80 = \"\xed\xb0\x80\" |",
+ "5.1.5 U+DC00 = ed b0 80 = \"\" |", "\x03"},
+ {"5.1.6 U+DF80 = ed be 80 = \"\xed\xbe\x80\" |",
+ "5.1.6 U+DF80 = ed be 80 = \"\" |", "\x03"},
+ {"5.1.7 U+DFFF = ed bf bf = \"\xed\xbf\xbf\" |",
+ "5.1.7 U+DFFF = ed bf bf = \"\" |", "\x03"},
+// 5.2 Paired UTF-16 surrogates
+ {"5.2.1 U+D800 U+DC00 = ed a0 80 ed b0 80 = \"\xed\xa0\x80\xed\xb0\x80\" |",
+ "5.2.1 U+D800 U+DC00 = ed a0 80 ed b0 80 = \"\" |", "\x06"},
+ {"5.2.2 U+D800 U+DFFF = ed a0 80 ed bf bf = \"\xed\xa0\x80\xed\xbf\xbf\" |",
+ "5.2.2 U+D800 U+DFFF = ed a0 80 ed bf bf = \"\" |", "\x06"},
+ {"5.2.3 U+DB7F U+DC00 = ed ad bf ed b0 80 = \"\xed\xad\xbf\xed\xb0\x80\" |",
+ "5.2.3 U+DB7F U+DC00 = ed ad bf ed b0 80 = \"\" |", "\x06"},
+ {"5.2.4 U+DB7F U+DFFF = ed ad bf ed bf bf = \"\xed\xad\xbf\xed\xbf\xbf\" |",
+ "5.2.4 U+DB7F U+DFFF = ed ad bf ed bf bf = \"\" |", "\x06"},
+ {"5.2.5 U+DB80 U+DC00 = ed ae 80 ed b0 80 = \"\xed\xae\x80\xed\xb0\x80\" |",
+ "5.2.5 U+DB80 U+DC00 = ed ae 80 ed b0 80 = \"\" |", "\x06"},
+ {"5.2.6 U+DB80 U+DFFF = ed ae 80 ed bf bf = \"\xed\xae\x80\xed\xbf\xbf\" |",
+ "5.2.6 U+DB80 U+DFFF = ed ae 80 ed bf bf = \"\" |", "\x06"},
+ {"5.2.7 U+DBFF U+DC00 = ed af bf ed b0 80 = \"\xed\xaf\xbf\xed\xb0\x80\" |",
+ "5.2.7 U+DBFF U+DC00 = ed af bf ed b0 80 = \"\" |", "\x06"},
+ {"5.2.8 U+DBFF U+DFFF = ed af bf ed bf bf = \"\xed\xaf\xbf\xed\xbf\xbf\" |",
+ "5.2.8 U+DBFF U+DFFF = ed af bf ed bf bf = \"\" |", "\x06"},
+// 5.3 Noncharacter code positions
+// The following "noncharacters" are "reserved for internal use" by applications, and according to older versions
+// of the Unicode Standard "should never be interchanged". Unicode Corrigendum #9 dropped the latter restriction.
+// Nevertheless, their presence in incoming UTF-8 data can remain a potential security risk, depending
+// on what use is made of these codes subsequently. Examples of such internal use:
+// - Some file APIs with 16-bit characters may use the integer value -1 = U+FFFF to signal
+// an end-of-file (EOF) or error condition.
+// - In some UTF-16 receivers, code point U+FFFE might trigger a byte-swap operation
+// (to convert between UTF-16LE and UTF-16BE).
+// With such internal use of noncharacters, it may be desirable and safer to block those code points in
+// UTF-8 decoders, as they should never occur legitimately in incoming UTF-8 data, and could trigger
+// unsafe behavior in subsequent processing.
+//
+// Particularly problematic noncharacters in 16-bit applications:
+ {"5.3.1 U+FFFE = ef bf be = \"\xef\xbf\xbe\" |",
+ "5.3.1 U+FFFE = ef bf be = \"\" |", "\x03"},
+ {"5.3.2 U+FFFF = ef bf bf = \"\xef\xbf\xbf\" |",
+ "5.3.2 U+FFFF = ef bf bf = \"\" |", "\x03"},
+ /* Fo now, we ignore those, they do not seem to be crucial anyway... */
+// 5.3.3 U+FDD0 .. U+FDEF
+// 5.3.4 U+nFFFE U+nFFFF (for n = 1..10)
+ {NULL, NULL, NULL},
+};
+/* clang-format on */
+
+/* BLI_utf8_invalid_strip (and indirectly, BLI_utf8_invalid_byte). */
+TEST(string, Utf8InvalidBytes)
+{
+ for (int i = 0; utf8_invalid_tests[i][0] != NULL; i++) {
+ const char *tst = utf8_invalid_tests[i][0];
+ const char *tst_stripped = utf8_invalid_tests[i][1];
+ const int num_errors = (int)utf8_invalid_tests[i][2][0];
+
+ char buff[80];
+ memcpy(buff, tst, sizeof(buff));
+
+ const int num_errors_found = BLI_utf8_invalid_strip(buff, sizeof(buff) - 1);
+
+ printf("[%02d] -> [%02d] \"%s\" -> \"%s\"\n", num_errors, num_errors_found, tst, buff);
+ EXPECT_EQ(num_errors_found, num_errors);
+ EXPECT_STREQ(buff, tst_stripped);
+ }
+}
diff --git a/source/blender/blenlib/tests/BLI_task_graph_test.cc b/source/blender/blenlib/tests/BLI_task_graph_test.cc
new file mode 100644
index 00000000000..efcbf923625
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_task_graph_test.cc
@@ -0,0 +1,188 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_task.h"
+
+struct TaskData {
+ int value;
+ int store;
+};
+
+static void TaskData_increase_value(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->value += 1;
+}
+static void TaskData_decrease_value(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->value -= 1;
+}
+static void TaskData_multiply_by_two_value(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->value *= 2;
+}
+
+static void TaskData_multiply_by_two_store(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->store *= 2;
+}
+
+static void TaskData_store_value(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->store = data->value;
+}
+
+static void TaskData_square_value(void *taskdata)
+{
+ TaskData *data = (TaskData *)taskdata;
+ data->value *= data->value;
+}
+
+/* Sequential Test for using `BLI_task_graph` */
+TEST(task, GraphSequential)
+{
+ TaskData data = {0};
+ TaskGraph *graph = BLI_task_graph_create();
+
+ /* 0 => 1 */
+ TaskNode *node_a = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+ /* 1 => 2 */
+ TaskNode *node_b = BLI_task_graph_node_create(
+ graph, TaskData_multiply_by_two_value, &data, NULL);
+ /* 2 => 1 */
+ TaskNode *node_c = BLI_task_graph_node_create(graph, TaskData_decrease_value, &data, NULL);
+ /* 2 => 1 */
+ TaskNode *node_d = BLI_task_graph_node_create(graph, TaskData_square_value, &data, NULL);
+ /* 1 => 1 */
+ TaskNode *node_e = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+ /* 1 => 2 */
+ const int expected_value = 2;
+
+ BLI_task_graph_edge_create(node_a, node_b);
+ BLI_task_graph_edge_create(node_b, node_c);
+ BLI_task_graph_edge_create(node_c, node_d);
+ BLI_task_graph_edge_create(node_d, node_e);
+
+ EXPECT_TRUE(BLI_task_graph_node_push_work(node_a));
+ BLI_task_graph_work_and_wait(graph);
+
+ EXPECT_EQ(expected_value, data.value);
+ BLI_task_graph_free(graph);
+}
+
+TEST(task, GraphStartAtAnyNode)
+{
+ TaskData data = {4};
+ TaskGraph *graph = BLI_task_graph_create();
+
+ TaskNode *node_a = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+ TaskNode *node_b = BLI_task_graph_node_create(
+ graph, TaskData_multiply_by_two_value, &data, NULL);
+ TaskNode *node_c = BLI_task_graph_node_create(graph, TaskData_decrease_value, &data, NULL);
+ TaskNode *node_d = BLI_task_graph_node_create(graph, TaskData_square_value, &data, NULL);
+ TaskNode *node_e = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+
+ // ((4 - 1) * (4 - 1)) + 1
+ const int expected_value = 10;
+
+ BLI_task_graph_edge_create(node_a, node_b);
+ BLI_task_graph_edge_create(node_b, node_c);
+ BLI_task_graph_edge_create(node_c, node_d);
+ BLI_task_graph_edge_create(node_d, node_e);
+
+ EXPECT_TRUE(BLI_task_graph_node_push_work(node_c));
+ BLI_task_graph_work_and_wait(graph);
+
+ EXPECT_EQ(expected_value, data.value);
+ BLI_task_graph_free(graph);
+}
+
+TEST(task, GraphSplit)
+{
+ TaskData data = {1};
+
+ TaskGraph *graph = BLI_task_graph_create();
+ TaskNode *node_a = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+ TaskNode *node_b = BLI_task_graph_node_create(graph, TaskData_store_value, &data, NULL);
+ TaskNode *node_c = BLI_task_graph_node_create(graph, TaskData_increase_value, &data, NULL);
+ TaskNode *node_d = BLI_task_graph_node_create(
+ graph, TaskData_multiply_by_two_store, &data, NULL);
+ BLI_task_graph_edge_create(node_a, node_b);
+ BLI_task_graph_edge_create(node_b, node_c);
+ BLI_task_graph_edge_create(node_b, node_d);
+ EXPECT_TRUE(BLI_task_graph_node_push_work(node_a));
+ BLI_task_graph_work_and_wait(graph);
+
+ EXPECT_EQ(3, data.value);
+ EXPECT_EQ(4, data.store);
+ BLI_task_graph_free(graph);
+}
+
+TEST(task, GraphForest)
+{
+ TaskData data1 = {1};
+ TaskData data2 = {3};
+
+ TaskGraph *graph = BLI_task_graph_create();
+
+ {
+ TaskNode *tree1_node_a = BLI_task_graph_node_create(
+ graph, TaskData_increase_value, &data1, NULL);
+ TaskNode *tree1_node_b = BLI_task_graph_node_create(graph, TaskData_store_value, &data1, NULL);
+ TaskNode *tree1_node_c = BLI_task_graph_node_create(
+ graph, TaskData_increase_value, &data1, NULL);
+ TaskNode *tree1_node_d = BLI_task_graph_node_create(
+ graph, TaskData_multiply_by_two_store, &data1, NULL);
+ BLI_task_graph_edge_create(tree1_node_a, tree1_node_b);
+ BLI_task_graph_edge_create(tree1_node_b, tree1_node_c);
+ BLI_task_graph_edge_create(tree1_node_b, tree1_node_d);
+ EXPECT_TRUE(BLI_task_graph_node_push_work(tree1_node_a));
+ }
+
+ {
+ TaskNode *tree2_node_a = BLI_task_graph_node_create(
+ graph, TaskData_increase_value, &data2, NULL);
+ TaskNode *tree2_node_b = BLI_task_graph_node_create(graph, TaskData_store_value, &data2, NULL);
+ TaskNode *tree2_node_c = BLI_task_graph_node_create(
+ graph, TaskData_increase_value, &data2, NULL);
+ TaskNode *tree2_node_d = BLI_task_graph_node_create(
+ graph, TaskData_multiply_by_two_store, &data2, NULL);
+ BLI_task_graph_edge_create(tree2_node_a, tree2_node_b);
+ BLI_task_graph_edge_create(tree2_node_b, tree2_node_c);
+ BLI_task_graph_edge_create(tree2_node_b, tree2_node_d);
+ EXPECT_TRUE(BLI_task_graph_node_push_work(tree2_node_a));
+ }
+
+ BLI_task_graph_work_and_wait(graph);
+
+ EXPECT_EQ(3, data1.value);
+ EXPECT_EQ(4, data1.store);
+ EXPECT_EQ(5, data2.value);
+ EXPECT_EQ(8, data2.store);
+ BLI_task_graph_free(graph);
+}
+
+TEST(task, GraphTaskData)
+{
+ TaskData data = {0};
+ TaskGraph *graph = BLI_task_graph_create();
+ TaskNode *node_a = BLI_task_graph_node_create(
+ graph, TaskData_store_value, &data, TaskData_increase_value);
+ TaskNode *node_b = BLI_task_graph_node_create(graph, TaskData_store_value, &data, NULL);
+ BLI_task_graph_edge_create(node_a, node_b);
+ EXPECT_TRUE(BLI_task_graph_node_push_work(node_a));
+ BLI_task_graph_work_and_wait(graph);
+ EXPECT_EQ(0, data.value);
+ EXPECT_EQ(0, data.store);
+ BLI_task_graph_free(graph);
+ /* data should be freed once */
+ EXPECT_EQ(1, data.value);
+ EXPECT_EQ(0, data.store);
+}
diff --git a/source/blender/blenlib/tests/BLI_task_test.cc b/source/blender/blenlib/tests/BLI_task_test.cc
new file mode 100644
index 00000000000..3abaf6a6c0b
--- /dev/null
+++ b/source/blender/blenlib/tests/BLI_task_test.cc
@@ -0,0 +1,183 @@
+/* Apache License, Version 2.0 */
+
+#include "testing/testing.h"
+#include <string.h>
+
+#include "atomic_ops.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+
+#include "BLI_listbase.h"
+#include "BLI_mempool.h"
+#include "BLI_task.h"
+
+#define NUM_ITEMS 10000
+
+/* *** Parallel iterations over range of integer values. *** */
+
+static void task_range_iter_func(void *userdata, int index, const TaskParallelTLS *__restrict tls)
+{
+ int *data = (int *)userdata;
+ data[index] = index;
+ *((int *)tls->userdata_chunk) += index;
+ // printf("%d, %d, %d\n", index, data[index], *((int *)tls->userdata_chunk));
+}
+
+static void task_range_iter_reduce_func(const void *__restrict UNUSED(userdata),
+ void *__restrict join_v,
+ void *__restrict userdata_chunk)
+{
+ int *join = (int *)join_v;
+ int *chunk = (int *)userdata_chunk;
+ *join += *chunk;
+ // printf("%d, %d\n", data[NUM_ITEMS], *((int *)userdata_chunk));
+}
+
+TEST(task, RangeIter)
+{
+ int data[NUM_ITEMS] = {0};
+ int sum = 0;
+
+ BLI_threadapi_init();
+
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.min_iter_per_thread = 1;
+
+ settings.userdata_chunk = &sum;
+ settings.userdata_chunk_size = sizeof(sum);
+ settings.func_reduce = task_range_iter_reduce_func;
+
+ BLI_task_parallel_range(0, NUM_ITEMS, data, task_range_iter_func, &settings);
+
+ /* Those checks should ensure us all items of the listbase were processed once, and only once
+ * as expected. */
+
+ int expected_sum = 0;
+ for (int i = 0; i < NUM_ITEMS; i++) {
+ EXPECT_EQ(data[i], i);
+ expected_sum += i;
+ }
+ EXPECT_EQ(sum, expected_sum);
+
+ BLI_threadapi_exit();
+}
+
+/* *** Parallel iterations over mempool items. *** */
+
+static void task_mempool_iter_func(void *userdata, MempoolIterData *item)
+{
+ int *data = (int *)item;
+ int *count = (int *)userdata;
+
+ EXPECT_TRUE(data != NULL);
+
+ *data += 1;
+ atomic_sub_and_fetch_uint32((uint32_t *)count, 1);
+}
+
+TEST(task, MempoolIter)
+{
+ int *data[NUM_ITEMS];
+ BLI_threadapi_init();
+ BLI_mempool *mempool = BLI_mempool_create(
+ sizeof(*data[0]), NUM_ITEMS, 32, BLI_MEMPOOL_ALLOW_ITER);
+
+ int i;
+
+ /* 'Randomly' add and remove some items from mempool, to create a non-homogenous one. */
+ int num_items = 0;
+ for (i = 0; i < NUM_ITEMS; i++) {
+ data[i] = (int *)BLI_mempool_alloc(mempool);
+ *data[i] = i - 1;
+ num_items++;
+ }
+
+ for (i = 0; i < NUM_ITEMS; i += 3) {
+ BLI_mempool_free(mempool, data[i]);
+ data[i] = NULL;
+ num_items--;
+ }
+
+ for (i = 0; i < NUM_ITEMS; i += 7) {
+ if (data[i] == NULL) {
+ data[i] = (int *)BLI_mempool_alloc(mempool);
+ *data[i] = i - 1;
+ num_items++;
+ }
+ }
+
+ for (i = 0; i < NUM_ITEMS - 5; i += 23) {
+ for (int j = 0; j < 5; j++) {
+ if (data[i + j] != NULL) {
+ BLI_mempool_free(mempool, data[i + j]);
+ data[i + j] = NULL;
+ num_items--;
+ }
+ }
+ }
+
+ BLI_task_parallel_mempool(mempool, &num_items, task_mempool_iter_func, true);
+
+ /* Those checks should ensure us all items of the mempool were processed once, and only once - as
+ * expected. */
+ EXPECT_EQ(num_items, 0);
+ for (i = 0; i < NUM_ITEMS; i++) {
+ if (data[i] != NULL) {
+ EXPECT_EQ(*data[i], i);
+ }
+ }
+
+ BLI_mempool_destroy(mempool);
+ BLI_threadapi_exit();
+}
+
+/* *** Parallel iterations over double-linked list items. *** */
+
+static void task_listbase_iter_func(void *userdata,
+ void *item,
+ int index,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+{
+ LinkData *data = (LinkData *)item;
+ int *count = (int *)userdata;
+
+ data->data = POINTER_FROM_INT(POINTER_AS_INT(data->data) + index);
+ atomic_sub_and_fetch_uint32((uint32_t *)count, 1);
+}
+
+TEST(task, ListBaseIter)
+{
+ ListBase list = {NULL, NULL};
+ LinkData *items_buffer = (LinkData *)MEM_calloc_arrayN(
+ NUM_ITEMS, sizeof(*items_buffer), __func__);
+ BLI_threadapi_init();
+
+ int i;
+
+ int num_items = 0;
+ for (i = 0; i < NUM_ITEMS; i++) {
+ BLI_addtail(&list, &items_buffer[i]);
+ num_items++;
+ }
+
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+
+ BLI_task_parallel_listbase(&list, &num_items, task_listbase_iter_func, &settings);
+
+ /* Those checks should ensure us all items of the listbase were processed once, and only once -
+ * as expected. */
+ EXPECT_EQ(num_items, 0);
+ LinkData *item;
+ for (i = 0, item = (LinkData *)list.first; i < NUM_ITEMS && item != NULL;
+ i++, item = item->next) {
+ EXPECT_EQ(POINTER_AS_INT(item->data), i);
+ }
+ EXPECT_EQ(NUM_ITEMS, i);
+
+ MEM_freeN(items_buffer);
+ BLI_threadapi_exit();
+}
diff --git a/source/blender/blenlib/tests/performance/BLI_ghash_performance_test.cc b/source/blender/blenlib/tests/performance/BLI_ghash_performance_test.cc
new file mode 100644
index 00000000000..c471b32a39a
--- /dev/null
+++ b/source/blender/blenlib/tests/performance/BLI_ghash_performance_test.cc
@@ -0,0 +1,571 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_ressource_strings.h"
+#include "testing/testing.h"
+
+#define GHASH_INTERNAL_API
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_ghash.h"
+#include "BLI_rand.h"
+#include "BLI_string.h"
+#include "BLI_utildefines.h"
+#include "PIL_time_utildefines.h"
+
+/* Using http://corpora.uni-leipzig.de/downloads/eng_wikipedia_2010_1M-text.tar.gz
+ * (1 million of words, about 122MB of text) from
+ * http://corpora.informatik.uni-leipzig.de/download.html */
+#if 0
+# define TEXT_CORPUS_PATH \
+ "/path/to/Téléchargements/eng_wikipedia_2010_1M-text/eng_wikipedia_2010_1M-sentences.txt"
+#endif
+
+/* Resizing the hash has a huge cost over global filling operation! */
+//#define GHASH_RESERVE
+
+/* Run the longest tests! */
+//#define GHASH_RUN_BIG
+
+/* Size of 'small case' ghash (number of entries). */
+#define TESTCASE_SIZE_SMALL 17
+
+#define PRINTF_GHASH_STATS(_gh) \
+ { \
+ double q, lf, var, pempty, poverloaded; \
+ int bigb; \
+ q = BLI_ghash_calc_quality_ex((_gh), &lf, &var, &pempty, &poverloaded, &bigb); \
+ printf( \
+ "GHash stats (%u entries):\n\t" \
+ "Quality (the lower the better): %f\n\tVariance (the lower the better): %f\n\tLoad: " \
+ "%f\n\t" \
+ "Empty buckets: %.2f%%\n\tOverloaded buckets: %.2f%% (biggest bucket: %d)\n", \
+ BLI_ghash_len(_gh), \
+ q, \
+ var, \
+ lf, \
+ pempty * 100.0, \
+ poverloaded * 100.0, \
+ bigb); \
+ } \
+ void(0)
+
+/* Str: whole text, lines and words from a 'corpus' text. */
+
+static void str_ghash_tests(GHash *ghash, const char *id)
+{
+ printf("\n========== STARTING %s ==========\n", id);
+
+#ifdef TEXT_CORPUS_PATH
+ size_t sz = 0;
+ char *data;
+ {
+ struct stat st;
+ if (stat(TEXT_CORPUS_PATH, &st) == 0)
+ sz = st.st_size;
+ }
+ if (sz != 0) {
+ FILE *f = fopen(TEXT_CORPUS_PATH, "r");
+
+ data = (char *)MEM_mallocN(sz + 1, __func__);
+ if (fread(data, sizeof(*data), sz, f) != sz) {
+ printf("ERROR in reading file %s!", TEXT_CORPUS_PATH);
+ MEM_freeN(data);
+ data = BLI_strdup(words10k);
+ }
+ data[sz] = '\0';
+ fclose(f);
+ }
+ else {
+ data = BLI_strdup(words10k);
+ }
+#else
+ char *data = BLI_strdup(words10k);
+#endif
+ char *data_p = BLI_strdup(data);
+ char *data_w = BLI_strdup(data);
+ char *data_bis = BLI_strdup(data);
+
+ {
+ char *p, *w, *c_p, *c_w;
+
+ TIMEIT_START(string_insert);
+
+#ifdef GHASH_RESERVE
+ BLI_ghash_reserve(ghash, strlen(data) / 32); /* rough estimation... */
+#endif
+
+ BLI_ghash_insert(ghash, data, POINTER_FROM_INT(data[0]));
+
+ for (p = c_p = data_p, w = c_w = data_w; *c_w; c_w++, c_p++) {
+ if (*c_p == '.') {
+ *c_p = *c_w = '\0';
+ if (!BLI_ghash_haskey(ghash, p)) {
+ BLI_ghash_insert(ghash, p, POINTER_FROM_INT(p[0]));
+ }
+ if (!BLI_ghash_haskey(ghash, w)) {
+ BLI_ghash_insert(ghash, w, POINTER_FROM_INT(w[0]));
+ }
+ p = c_p + 1;
+ w = c_w + 1;
+ }
+ else if (*c_w == ' ') {
+ *c_w = '\0';
+ if (!BLI_ghash_haskey(ghash, w)) {
+ BLI_ghash_insert(ghash, w, POINTER_FROM_INT(w[0]));
+ }
+ w = c_w + 1;
+ }
+ }
+
+ TIMEIT_END(string_insert);
+ }
+
+ PRINTF_GHASH_STATS(ghash);
+
+ {
+ char *p, *w, *c;
+ void *v;
+
+ TIMEIT_START(string_lookup);
+
+ v = BLI_ghash_lookup(ghash, data_bis);
+ EXPECT_EQ(POINTER_AS_INT(v), data_bis[0]);
+
+ for (p = w = c = data_bis; *c; c++) {
+ if (*c == '.') {
+ *c = '\0';
+ v = BLI_ghash_lookup(ghash, w);
+ EXPECT_EQ(POINTER_AS_INT(v), w[0]);
+ v = BLI_ghash_lookup(ghash, p);
+ EXPECT_EQ(POINTER_AS_INT(v), p[0]);
+ p = w = c + 1;
+ }
+ else if (*c == ' ') {
+ *c = '\0';
+ v = BLI_ghash_lookup(ghash, w);
+ EXPECT_EQ(POINTER_AS_INT(v), w[0]);
+ w = c + 1;
+ }
+ }
+
+ TIMEIT_END(string_lookup);
+ }
+
+ BLI_ghash_free(ghash, NULL, NULL);
+ MEM_freeN(data);
+ MEM_freeN(data_p);
+ MEM_freeN(data_w);
+ MEM_freeN(data_bis);
+
+ printf("========== ENDED %s ==========\n\n", id);
+}
+
+TEST(ghash, TextGHash)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_strhash_p, BLI_ghashutil_strcmp, __func__);
+
+ str_ghash_tests(ghash, "StrGHash - GHash");
+}
+
+TEST(ghash, TextMurmur2a)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_strhash_p_murmur, BLI_ghashutil_strcmp, __func__);
+
+ str_ghash_tests(ghash, "StrGHash - Murmur");
+}
+
+/* Int: uniform 100M first integers. */
+
+static void int_ghash_tests(GHash *ghash, const char *id, const unsigned int nbr)
+{
+ printf("\n========== STARTING %s ==========\n", id);
+
+ {
+ unsigned int i = nbr;
+
+ TIMEIT_START(int_insert);
+
+#ifdef GHASH_RESERVE
+ BLI_ghash_reserve(ghash, nbr);
+#endif
+
+ while (i--) {
+ BLI_ghash_insert(ghash, POINTER_FROM_UINT(i), POINTER_FROM_UINT(i));
+ }
+
+ TIMEIT_END(int_insert);
+ }
+
+ PRINTF_GHASH_STATS(ghash);
+
+ {
+ unsigned int i = nbr;
+
+ TIMEIT_START(int_lookup);
+
+ while (i--) {
+ void *v = BLI_ghash_lookup(ghash, POINTER_FROM_UINT(i));
+ EXPECT_EQ(POINTER_AS_UINT(v), i);
+ }
+
+ TIMEIT_END(int_lookup);
+ }
+
+ {
+ void *k, *v;
+
+ TIMEIT_START(int_pop);
+
+ GHashIterState pop_state = {0};
+
+ while (BLI_ghash_pop(ghash, &pop_state, &k, &v)) {
+ EXPECT_EQ(k, v);
+ }
+
+ TIMEIT_END(int_pop);
+ }
+ EXPECT_EQ(BLI_ghash_len(ghash), 0);
+
+ BLI_ghash_free(ghash, NULL, NULL);
+
+ printf("========== ENDED %s ==========\n\n", id);
+}
+
+TEST(ghash, IntGHash12000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__);
+
+ int_ghash_tests(ghash, "IntGHash - GHash - 12000", 12000);
+}
+
+#ifdef GHASH_RUN_BIG
+TEST(ghash, IntGHash100000000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__);
+
+ int_ghash_tests(ghash, "IntGHash - GHash - 100000000", 100000000);
+}
+#endif
+
+TEST(ghash, IntMurmur2a12000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p_murmur, BLI_ghashutil_intcmp, __func__);
+
+ int_ghash_tests(ghash, "IntGHash - Murmur - 12000", 12000);
+}
+
+#ifdef GHASH_RUN_BIG
+TEST(ghash, IntMurmur2a100000000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p_murmur, BLI_ghashutil_intcmp, __func__);
+
+ int_ghash_tests(ghash, "IntGHash - Murmur - 100000000", 100000000);
+}
+#endif
+
+/* Int: random 50M integers. */
+
+static void randint_ghash_tests(GHash *ghash, const char *id, const unsigned int nbr)
+{
+ printf("\n========== STARTING %s ==========\n", id);
+
+ unsigned int *data = (unsigned int *)MEM_mallocN(sizeof(*data) * (size_t)nbr, __func__);
+ unsigned int *dt;
+ unsigned int i;
+
+ {
+ RNG *rng = BLI_rng_new(1);
+ for (i = nbr, dt = data; i--; dt++) {
+ *dt = BLI_rng_get_uint(rng);
+ }
+ BLI_rng_free(rng);
+ }
+
+ {
+ TIMEIT_START(int_insert);
+
+#ifdef GHASH_RESERVE
+ BLI_ghash_reserve(ghash, nbr);
+#endif
+
+ for (i = nbr, dt = data; i--; dt++) {
+ BLI_ghash_insert(ghash, POINTER_FROM_UINT(*dt), POINTER_FROM_UINT(*dt));
+ }
+
+ TIMEIT_END(int_insert);
+ }
+
+ PRINTF_GHASH_STATS(ghash);
+
+ {
+ TIMEIT_START(int_lookup);
+
+ for (i = nbr, dt = data; i--; dt++) {
+ void *v = BLI_ghash_lookup(ghash, POINTER_FROM_UINT(*dt));
+ EXPECT_EQ(POINTER_AS_UINT(v), *dt);
+ }
+
+ TIMEIT_END(int_lookup);
+ }
+
+ BLI_ghash_free(ghash, NULL, NULL);
+
+ printf("========== ENDED %s ==========\n\n", id);
+}
+
+TEST(ghash, IntRandGHash12000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__);
+
+ randint_ghash_tests(ghash, "RandIntGHash - GHash - 12000", 12000);
+}
+
+#ifdef GHASH_RUN_BIG
+TEST(ghash, IntRandGHash50000000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__);
+
+ randint_ghash_tests(ghash, "RandIntGHash - GHash - 50000000", 50000000);
+}
+#endif
+
+TEST(ghash, IntRandMurmur2a12000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p_murmur, BLI_ghashutil_intcmp, __func__);
+
+ randint_ghash_tests(ghash, "RandIntGHash - Murmur - 12000", 12000);
+}
+
+#ifdef GHASH_RUN_BIG
+TEST(ghash, IntRandMurmur2a50000000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p_murmur, BLI_ghashutil_intcmp, __func__);
+
+ randint_ghash_tests(ghash, "RandIntGHash - Murmur - 50000000", 50000000);
+}
+#endif
+
+static unsigned int ghashutil_tests_nohash_p(const void *p)
+{
+ return POINTER_AS_UINT(p);
+}
+
+static bool ghashutil_tests_cmp_p(const void *a, const void *b)
+{
+ return a != b;
+}
+
+TEST(ghash, Int4NoHash12000)
+{
+ GHash *ghash = BLI_ghash_new(ghashutil_tests_nohash_p, ghashutil_tests_cmp_p, __func__);
+
+ randint_ghash_tests(ghash, "RandIntGHash - No Hash - 12000", 12000);
+}
+
+#ifdef GHASH_RUN_BIG
+TEST(ghash, Int4NoHash50000000)
+{
+ GHash *ghash = BLI_ghash_new(ghashutil_tests_nohash_p, ghashutil_tests_cmp_p, __func__);
+
+ randint_ghash_tests(ghash, "RandIntGHash - No Hash - 50000000", 50000000);
+}
+#endif
+
+/* Int_v4: 20M of randomly-generated integer vectors. */
+
+static void int4_ghash_tests(GHash *ghash, const char *id, const unsigned int nbr)
+{
+ printf("\n========== STARTING %s ==========\n", id);
+
+ void *data_v = MEM_mallocN(sizeof(unsigned int[4]) * (size_t)nbr, __func__);
+ unsigned int(*data)[4] = (unsigned int(*)[4])data_v;
+ unsigned int(*dt)[4];
+ unsigned int i, j;
+
+ {
+ RNG *rng = BLI_rng_new(1);
+ for (i = nbr, dt = data; i--; dt++) {
+ for (j = 4; j--;) {
+ (*dt)[j] = BLI_rng_get_uint(rng);
+ }
+ }
+ BLI_rng_free(rng);
+ }
+
+ {
+ TIMEIT_START(int_v4_insert);
+
+#ifdef GHASH_RESERVE
+ BLI_ghash_reserve(ghash, nbr);
+#endif
+
+ for (i = nbr, dt = data; i--; dt++) {
+ BLI_ghash_insert(ghash, *dt, POINTER_FROM_UINT(i));
+ }
+
+ TIMEIT_END(int_v4_insert);
+ }
+
+ PRINTF_GHASH_STATS(ghash);
+
+ {
+ TIMEIT_START(int_v4_lookup);
+
+ for (i = nbr, dt = data; i--; dt++) {
+ void *v = BLI_ghash_lookup(ghash, (void *)(*dt));
+ EXPECT_EQ(POINTER_AS_UINT(v), i);
+ }
+
+ TIMEIT_END(int_v4_lookup);
+ }
+
+ BLI_ghash_free(ghash, NULL, NULL);
+ MEM_freeN(data);
+
+ printf("========== ENDED %s ==========\n\n", id);
+}
+
+TEST(ghash, Int4GHash2000)
+{
+ GHash *ghash = BLI_ghash_new(
+ BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__);
+
+ int4_ghash_tests(ghash, "Int4GHash - GHash - 2000", 2000);
+}
+
+#ifdef GHASH_RUN_BIG
+TEST(ghash, Int4GHash20000000)
+{
+ GHash *ghash = BLI_ghash_new(
+ BLI_ghashutil_uinthash_v4_p, BLI_ghashutil_uinthash_v4_cmp, __func__);
+
+ int4_ghash_tests(ghash, "Int4GHash - GHash - 20000000", 20000000);
+}
+#endif
+
+TEST(ghash, Int4Murmur2a2000)
+{
+ GHash *ghash = BLI_ghash_new(
+ BLI_ghashutil_uinthash_v4_p_murmur, BLI_ghashutil_uinthash_v4_cmp, __func__);
+
+ int4_ghash_tests(ghash, "Int4GHash - Murmur - 2000", 2000);
+}
+
+#ifdef GHASH_RUN_BIG
+TEST(ghash, Int4Murmur2a20000000)
+{
+ GHash *ghash = BLI_ghash_new(
+ BLI_ghashutil_uinthash_v4_p_murmur, BLI_ghashutil_uinthash_v4_cmp, __func__);
+
+ int4_ghash_tests(ghash, "Int4GHash - Murmur - 20000000", 20000000);
+}
+#endif
+
+/* GHash inthash_v2 tests */
+TEST(ghash, Int2NoHash12000)
+{
+ GHash *ghash = BLI_ghash_new(ghashutil_tests_nohash_p, ghashutil_tests_cmp_p, __func__);
+
+ randint_ghash_tests(ghash, "RandIntGHash - No Hash - 12000", 12000);
+}
+
+#ifdef GHASH_RUN_BIG
+TEST(ghash, Int2NoHash50000000)
+{
+ GHash *ghash = BLI_ghash_new(ghashutil_tests_nohash_p, ghashutil_tests_cmp_p, __func__);
+
+ randint_ghash_tests(ghash, "RandIntGHash - No Hash - 50000000", 50000000);
+}
+#endif
+
+/* MultiSmall: create and manipulate a lot of very small ghashes
+ * (90% < 10 items, 9% < 100 items, 1% < 1000 items). */
+
+static void multi_small_ghash_tests_one(GHash *ghash, RNG *rng, const unsigned int nbr)
+{
+ unsigned int *data = (unsigned int *)MEM_mallocN(sizeof(*data) * (size_t)nbr, __func__);
+ unsigned int *dt;
+ unsigned int i;
+
+ for (i = nbr, dt = data; i--; dt++) {
+ *dt = BLI_rng_get_uint(rng);
+ }
+
+#ifdef GHASH_RESERVE
+ BLI_ghash_reserve(ghash, nbr);
+#endif
+
+ for (i = nbr, dt = data; i--; dt++) {
+ BLI_ghash_insert(ghash, POINTER_FROM_UINT(*dt), POINTER_FROM_UINT(*dt));
+ }
+
+ for (i = nbr, dt = data; i--; dt++) {
+ void *v = BLI_ghash_lookup(ghash, POINTER_FROM_UINT(*dt));
+ EXPECT_EQ(POINTER_AS_UINT(v), *dt);
+ }
+
+ BLI_ghash_clear(ghash, NULL, NULL);
+}
+
+static void multi_small_ghash_tests(GHash *ghash, const char *id, const unsigned int nbr)
+{
+ printf("\n========== STARTING %s ==========\n", id);
+
+ RNG *rng = BLI_rng_new(1);
+
+ TIMEIT_START(multi_small_ghash);
+
+ unsigned int i = nbr;
+ while (i--) {
+ const int nbr = 1 + (BLI_rng_get_int(rng) % TESTCASE_SIZE_SMALL) *
+ (!(i % 100) ? 100 : (!(i % 10) ? 10 : 1));
+ multi_small_ghash_tests_one(ghash, rng, nbr);
+ }
+
+ TIMEIT_END(multi_small_ghash);
+
+ TIMEIT_START(multi_small2_ghash);
+
+ unsigned int i = nbr;
+ while (i--) {
+ const int nbr = 1 + (BLI_rng_get_int(rng) % TESTCASE_SIZE_SMALL) / 2 *
+ (!(i % 100) ? 100 : (!(i % 10) ? 10 : 1));
+ multi_small_ghash_tests_one(ghash, rng, nbr);
+ }
+
+ TIMEIT_END(multi_small2_ghash);
+
+ BLI_ghash_free(ghash, NULL, NULL);
+ BLI_rng_free(rng);
+
+ printf("========== ENDED %s ==========\n\n", id);
+}
+
+TEST(ghash, MultiRandIntGHash2000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__);
+
+ multi_small_ghash_tests(ghash, "MultiSmall RandIntGHash - GHash - 2000", 2000);
+}
+
+TEST(ghash, MultiRandIntGHash200000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p, BLI_ghashutil_intcmp, __func__);
+
+ multi_small_ghash_tests(ghash, "MultiSmall RandIntGHash - GHash - 200000", 200000);
+}
+
+TEST(ghash, MultiRandIntMurmur2a2000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p_murmur, BLI_ghashutil_intcmp, __func__);
+
+ multi_small_ghash_tests(ghash, "MultiSmall RandIntGHash - Murmur2a - 2000", 2000);
+}
+
+TEST(ghash, MultiRandIntMurmur2a200000)
+{
+ GHash *ghash = BLI_ghash_new(BLI_ghashutil_inthash_p_murmur, BLI_ghashutil_intcmp, __func__);
+
+ multi_small_ghash_tests(ghash, "MultiSmall RandIntGHash - Murmur2a - 200000", 200000);
+}
diff --git a/source/blender/blenlib/tests/performance/BLI_task_performance_test.cc b/source/blender/blenlib/tests/performance/BLI_task_performance_test.cc
new file mode 100644
index 00000000000..208f168b599
--- /dev/null
+++ b/source/blender/blenlib/tests/performance/BLI_task_performance_test.cc
@@ -0,0 +1,210 @@
+/* Apache License, Version 2.0 */
+
+#include "BLI_ressource_strings.h"
+#include "testing/testing.h"
+
+#include "atomic_ops.h"
+
+#define GHASH_INTERNAL_API
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_utildefines.h"
+
+#include "BLI_listbase.h"
+#include "BLI_mempool.h"
+#include "BLI_task.h"
+
+#include "PIL_time.h"
+
+#define NUM_RUN_AVERAGED 100
+
+static uint gen_pseudo_random_number(uint num)
+{
+ /* Note: this is taken from BLI_ghashutil_uinthash(), don't want to depend on external code that
+ * might change here... */
+ num += ~(num << 16);
+ num ^= (num >> 5);
+ num += (num << 3);
+ num ^= (num >> 13);
+ num += ~(num << 9);
+ num ^= (num >> 17);
+
+ /* Make final number in [65 - 16385] range. */
+ return ((num & 255) << 6) + 1;
+}
+
+/* *** Parallel iterations over double-linked list items. *** */
+
+static void task_listbase_light_iter_func(void *UNUSED(userdata),
+ void *item,
+ int index,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+
+{
+ LinkData *data = (LinkData *)item;
+
+ data->data = POINTER_FROM_INT(POINTER_AS_INT(data->data) + index);
+}
+
+static void task_listbase_light_membarrier_iter_func(void *userdata,
+ void *item,
+ int index,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+
+{
+ LinkData *data = (LinkData *)item;
+ int *count = (int *)userdata;
+
+ data->data = POINTER_FROM_INT(POINTER_AS_INT(data->data) + index);
+ atomic_sub_and_fetch_uint32((uint32_t *)count, 1);
+}
+
+static void task_listbase_heavy_iter_func(void *UNUSED(userdata),
+ void *item,
+ int index,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+
+{
+ LinkData *data = (LinkData *)item;
+
+ /* 'Random' number of iterations. */
+ const uint num = gen_pseudo_random_number((uint)index);
+
+ for (uint i = 0; i < num; i++) {
+ data->data = POINTER_FROM_INT(POINTER_AS_INT(data->data) + ((i % 2) ? -index : index));
+ }
+}
+
+static void task_listbase_heavy_membarrier_iter_func(void *userdata,
+ void *item,
+ int index,
+ const TaskParallelTLS *__restrict UNUSED(tls))
+
+{
+ LinkData *data = (LinkData *)item;
+ int *count = (int *)userdata;
+
+ /* 'Random' number of iterations. */
+ const uint num = gen_pseudo_random_number((uint)index);
+
+ for (uint i = 0; i < num; i++) {
+ data->data = POINTER_FROM_INT(POINTER_AS_INT(data->data) + ((i % 2) ? -index : index));
+ }
+ atomic_sub_and_fetch_uint32((uint32_t *)count, 1);
+}
+
+static void task_listbase_test_do(ListBase *list,
+ const int num_items,
+ int *num_items_tmp,
+ const char *id,
+ TaskParallelIteratorFunc func,
+ const bool use_threads,
+ const bool check_num_items_tmp)
+{
+ TaskParallelSettings settings;
+ BLI_parallel_range_settings_defaults(&settings);
+ settings.use_threading = use_threads;
+
+ double averaged_timing = 0.0;
+ for (int i = 0; i < NUM_RUN_AVERAGED; i++) {
+ const double init_time = PIL_check_seconds_timer();
+ BLI_task_parallel_listbase(list, num_items_tmp, func, &settings);
+ averaged_timing += PIL_check_seconds_timer() - init_time;
+
+ /* Those checks should ensure us all items of the listbase were processed once, and only once -
+ * as expected. */
+ if (check_num_items_tmp) {
+ EXPECT_EQ(*num_items_tmp, 0);
+ }
+ LinkData *item;
+ int j;
+ for (j = 0, item = (LinkData *)list->first; j < num_items && item != NULL;
+ j++, item = item->next) {
+ EXPECT_EQ(POINTER_AS_INT(item->data), j);
+ item->data = POINTER_FROM_INT(0);
+ }
+ EXPECT_EQ(num_items, j);
+
+ *num_items_tmp = num_items;
+ }
+
+ printf("\t%s: done in %fs on average over %d runs\n",
+ id,
+ averaged_timing / NUM_RUN_AVERAGED,
+ NUM_RUN_AVERAGED);
+}
+
+static void task_listbase_test(const char *id, const int nbr, const bool use_threads)
+{
+ printf("\n========== STARTING %s ==========\n", id);
+
+ ListBase list = {NULL, NULL};
+ LinkData *items_buffer = (LinkData *)MEM_calloc_arrayN(nbr, sizeof(*items_buffer), __func__);
+
+ BLI_threadapi_init();
+
+ int num_items = 0;
+ for (int i = 0; i < nbr; i++) {
+ BLI_addtail(&list, &items_buffer[i]);
+ num_items++;
+ }
+ int num_items_tmp = num_items;
+
+ task_listbase_test_do(&list,
+ num_items,
+ &num_items_tmp,
+ "Light iter",
+ task_listbase_light_iter_func,
+ use_threads,
+ false);
+
+ task_listbase_test_do(&list,
+ num_items,
+ &num_items_tmp,
+ "Light iter with mem barrier",
+ task_listbase_light_membarrier_iter_func,
+ use_threads,
+ true);
+
+ task_listbase_test_do(&list,
+ num_items,
+ &num_items_tmp,
+ "Heavy iter",
+ task_listbase_heavy_iter_func,
+ use_threads,
+ false);
+
+ task_listbase_test_do(&list,
+ num_items,
+ &num_items_tmp,
+ "Heavy iter with mem barrier",
+ task_listbase_heavy_membarrier_iter_func,
+ use_threads,
+ true);
+
+ MEM_freeN(items_buffer);
+ BLI_threadapi_exit();
+
+ printf("========== ENDED %s ==========\n\n", id);
+}
+
+TEST(task, ListBaseIterNoThread10k)
+{
+ task_listbase_test("ListBase parallel iteration - Single thread - 10000 items", 10000, false);
+}
+
+TEST(task, ListBaseIter10k)
+{
+ task_listbase_test("ListBase parallel iteration - Threaded - 10000 items", 10000, true);
+}
+
+TEST(task, ListBaseIterNoThread100k)
+{
+ task_listbase_test("ListBase parallel iteration - Single thread - 100000 items", 100000, false);
+}
+
+TEST(task, ListBaseIter100k)
+{
+ task_listbase_test("ListBase parallel iteration - Threaded - 100000 items", 100000, true);
+}
diff --git a/source/blender/blenlib/tests/performance/CMakeLists.txt b/source/blender/blenlib/tests/performance/CMakeLists.txt
new file mode 100644
index 00000000000..c7cb65f78b2
--- /dev/null
+++ b/source/blender/blenlib/tests/performance/CMakeLists.txt
@@ -0,0 +1,37 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# The Original Code is Copyright (C) 2014, Blender Foundation
+# All rights reserved.
+# ***** END GPL LICENSE BLOCK *****
+
+set(INC
+ .
+ ..
+ ../../../source/blender/blenlib
+ ../../../source/blender/makesdna
+ ../../../intern/guardedalloc
+ ../../../intern/atomic
+)
+
+setup_libdirs()
+include_directories(${INC})
+
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${PLATFORM_LINKFLAGS}")
+set(CMAKE_EXE_LINKER_FLAGS_DEBUG "${CMAKE_EXE_LINKER_FLAGS_DEBUG} ${PLATFORM_LINKFLAGS_DEBUG}")
+
+BLENDER_TEST_PERFORMANCE(BLI_ghash_performance "bf_blenlib")
+BLENDER_TEST_PERFORMANCE(BLI_task_performance "bf_blenlib")
diff --git a/source/blender/blenloader/CMakeLists.txt b/source/blender/blenloader/CMakeLists.txt
index 7eab0651d97..c56e0b5ad2e 100644
--- a/source/blender/blenloader/CMakeLists.txt
+++ b/source/blender/blenloader/CMakeLists.txt
@@ -98,3 +98,17 @@ blender_add_lib(bf_blenloader "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# needed so writefile.c can use dna_type_offsets.h
add_dependencies(bf_blenloader bf_dna)
+
+if(WITH_GTESTS)
+ set(TEST_SRC
+ tests/blendfile_loading_base_test.cc
+ tests/blendfile_load_test.cc
+ )
+ set(TEST_INC
+ )
+ set(TEST_LIB
+ bf_blenloader
+ )
+ include(GTestTesting)
+ blender_add_test_lib(bf_blenloader_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
+endif()
diff --git a/source/blender/blenloader/tests/blendfile_load_test.cc b/source/blender/blenloader/tests/blendfile_load_test.cc
new file mode 100644
index 00000000000..2ba3e3fcd88
--- /dev/null
+++ b/source/blender/blenloader/tests/blendfile_load_test.cc
@@ -0,0 +1,31 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 by Blender Foundation.
+ */
+#include "blendfile_loading_base_test.h"
+
+class BlendfileLoadingTest : public BlendfileLoadingBaseTest {
+};
+
+TEST_F(BlendfileLoadingTest, CanaryTest)
+{
+ /* Load the smallest blend file we have in the SVN lib/tests directory. */
+ if (!blendfile_load("modifier_stack/array_test.blend")) {
+ return;
+ }
+ depsgraph_create(DAG_EVAL_RENDER);
+ EXPECT_NE(nullptr, this->depsgraph);
+}
diff --git a/source/blender/blenloader/tests/blendfile_loading_base_test.cc b/source/blender/blenloader/tests/blendfile_loading_base_test.cc
new file mode 100644
index 00000000000..d74bab4b31c
--- /dev/null
+++ b/source/blender/blenloader/tests/blendfile_loading_base_test.cc
@@ -0,0 +1,162 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 by Blender Foundation.
+ */
+#include "blendfile_loading_base_test.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "BKE_appdir.h"
+#include "BKE_blender.h"
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_idtype.h"
+#include "BKE_image.h"
+#include "BKE_main.h"
+#include "BKE_modifier.h"
+#include "BKE_node.h"
+#include "BKE_scene.h"
+
+#include "BLI_path_util.h"
+#include "BLI_threads.h"
+
+#include "BLO_readfile.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+
+#include "DNA_genfile.h" /* for DNA_sdna_current_init() */
+#include "DNA_windowmanager_types.h"
+
+#include "IMB_imbuf.h"
+
+#include "RNA_define.h"
+
+#include "WM_api.h"
+#include "wm.h"
+
+BlendfileLoadingBaseTest::~BlendfileLoadingBaseTest()
+{
+}
+
+void BlendfileLoadingBaseTest::SetUpTestCase()
+{
+ testing::Test::SetUpTestCase();
+
+ /* Minimal code to make loading a blendfile and constructing a depsgraph not crash, copied from
+ * main() in creator.c. */
+ BLI_threadapi_init();
+
+ DNA_sdna_current_init();
+ BKE_blender_globals_init();
+
+ BKE_idtype_init();
+ IMB_init();
+ BKE_images_init();
+ BKE_modifier_init();
+ DEG_register_node_types();
+ RNA_init();
+ init_nodesystem();
+
+ G.background = true;
+ G.factory_startup = true;
+
+ /* Allocate a dummy window manager. The real window manager will try and load Python scripts from
+ * the release directory, which it won't be able to find. */
+ ASSERT_EQ(G.main->wm.first, nullptr);
+ G.main->wm.first = MEM_callocN(sizeof(wmWindowManager), __func__);
+}
+
+void BlendfileLoadingBaseTest::TearDownTestCase()
+{
+ if (G.main->wm.first != nullptr) {
+ MEM_freeN(G.main->wm.first);
+ G.main->wm.first = nullptr;
+ }
+
+ /* Copied from WM_exit_ex() in wm_init_exit.c, and cherry-picked those lines that match the
+ * allocation/initialization done in SetUpTestCase(). */
+ BKE_blender_free();
+ RNA_exit();
+
+ DEG_free_node_types();
+ DNA_sdna_current_free();
+ BLI_threadapi_exit();
+
+ BKE_blender_atexit();
+
+ BKE_tempdir_session_purge();
+
+ testing::Test::TearDownTestCase();
+}
+
+void BlendfileLoadingBaseTest::TearDown()
+{
+ depsgraph_free();
+ blendfile_free();
+
+ testing::Test::TearDown();
+}
+
+bool BlendfileLoadingBaseTest::blendfile_load(const char *filepath)
+{
+ const std::string &test_assets_dir = blender::tests::flags_test_asset_dir();
+ if (test_assets_dir.empty()) {
+ return false;
+ }
+
+ char abspath[FILENAME_MAX];
+ BLI_path_join(abspath, sizeof(abspath), test_assets_dir.c_str(), filepath, NULL);
+
+ bfile = BLO_read_from_file(abspath, BLO_READ_SKIP_NONE, NULL /* reports */);
+ if (bfile == nullptr) {
+ ADD_FAILURE() << "Unable to load file '" << filepath << "' from test assets dir '"
+ << test_assets_dir << "'";
+ return false;
+ }
+ return true;
+}
+
+void BlendfileLoadingBaseTest::blendfile_free()
+{
+ if (bfile == nullptr) {
+ return;
+ }
+
+ wmWindowManager *wm = static_cast<wmWindowManager *>(bfile->main->wm.first);
+ if (wm != nullptr) {
+ wm_close_and_free(NULL, wm);
+ }
+ BLO_blendfiledata_free(bfile);
+ bfile = nullptr;
+}
+
+void BlendfileLoadingBaseTest::depsgraph_create(eEvaluationMode depsgraph_evaluation_mode)
+{
+ depsgraph = DEG_graph_new(
+ bfile->main, bfile->curscene, bfile->cur_view_layer, depsgraph_evaluation_mode);
+ DEG_graph_build_from_view_layer(depsgraph, bfile->main, bfile->curscene, bfile->cur_view_layer);
+ BKE_scene_graph_update_tagged(depsgraph, bfile->main);
+}
+
+void BlendfileLoadingBaseTest::depsgraph_free()
+{
+ if (depsgraph == nullptr) {
+ return;
+ }
+ DEG_graph_free(depsgraph);
+ depsgraph = nullptr;
+}
diff --git a/source/blender/blenloader/tests/blendfile_loading_base_test.h b/source/blender/blenloader/tests/blendfile_loading_base_test.h
new file mode 100644
index 00000000000..a5e75ef6df8
--- /dev/null
+++ b/source/blender/blenloader/tests/blendfile_loading_base_test.h
@@ -0,0 +1,64 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2019 by Blender Foundation.
+ */
+#ifndef __BLENDFILE_LOADING_BASE_TEST_H__
+#define __BLENDFILE_LOADING_BASE_TEST_H__
+
+#include "DEG_depsgraph.h"
+#include "testing/testing.h"
+
+struct BlendFileData;
+struct Depsgraph;
+
+class BlendfileLoadingBaseTest : public testing::Test {
+ protected:
+ struct BlendFileData *bfile = nullptr;
+ struct Depsgraph *depsgraph = nullptr;
+
+ public:
+ virtual ~BlendfileLoadingBaseTest();
+
+ /* Sets up Blender just enough to not crash on loading
+ * a blendfile and constructing a depsgraph. */
+ static void SetUpTestCase();
+ static void TearDownTestCase();
+
+ protected:
+ /* Frees the depsgraph & blendfile. */
+ virtual void TearDown();
+
+ /* Loads a blend file from the lib/tests directory from SVN.
+ * Returns 'ok' flag (true=good, false=bad) and sets this->bfile.
+ * Fails the test if the file cannot be loaded (still returns though).
+ * Requires the CLI argument --test-asset-dir to point to ../../lib/tests.
+ *
+ * WARNING: only files saved with Blender 2.80+ can be loaded. Since Blender
+ * is only partially initialized (most importantly, without window manager),
+ * the space types are not registered, so any versioning code that handles
+ * those will SEGFAULT.
+ */
+ bool blendfile_load(const char *filepath);
+ /* Free bfile if it is not nullptr. */
+ void blendfile_free();
+
+ /* Create a depsgraph. Assumes a blend file has been loaded to this->bfile. */
+ void depsgraph_create(eEvaluationMode depsgraph_evaluation_mode);
+ /* Free the depsgraph if it's not nullptr. */
+ void depsgraph_free();
+};
+
+#endif /* __BLENDFILE_LOADING_BASE_TEST_H__ */
diff --git a/source/blender/bmesh/CMakeLists.txt b/source/blender/bmesh/CMakeLists.txt
index fca411e594f..b97b5cc95f2 100644
--- a/source/blender/bmesh/CMakeLists.txt
+++ b/source/blender/bmesh/CMakeLists.txt
@@ -204,3 +204,16 @@ if(WITH_FREESTYLE)
endif()
blender_add_lib(bf_bmesh "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
+
+if(WITH_GTESTS)
+ set(TEST_SRC
+ tests/bmesh_core_test.cc
+ )
+ set(TEST_INC
+ )
+ set(TEST_LIB
+ bf_bmesh
+ )
+ include(GTestTesting)
+ blender_add_test_lib(bf_bmesh_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
+endif()
diff --git a/source/blender/bmesh/tests/bmesh_core_test.cc b/source/blender/bmesh/tests/bmesh_core_test.cc
new file mode 100644
index 00000000000..afbc11e0722
--- /dev/null
+++ b/source/blender/bmesh/tests/bmesh_core_test.cc
@@ -0,0 +1,40 @@
+#include "testing/testing.h"
+
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+#include "bmesh.h"
+
+TEST(bmesh_core, BMVertCreate)
+{
+ BMesh *bm;
+ BMVert *bv1, *bv2, *bv3;
+ const float co1[3] = {1.0f, 2.0f, 0.0f};
+
+ BMeshCreateParams bm_params;
+ bm_params.use_toolflags = true;
+ bm = BM_mesh_create(&bm_mesh_allocsize_default, &bm_params);
+ EXPECT_EQ(bm->totvert, 0);
+ /* make a custom layer so we can see if it is copied properly */
+ BM_data_layer_add(bm, &bm->vdata, CD_PROP_FLOAT);
+ bv1 = BM_vert_create(bm, co1, NULL, BM_CREATE_NOP);
+ ASSERT_TRUE(bv1 != NULL);
+ EXPECT_EQ(bv1->co[0], 1.0f);
+ EXPECT_EQ(bv1->co[1], 2.0f);
+ EXPECT_EQ(bv1->co[2], 0.0f);
+ EXPECT_TRUE(is_zero_v3(bv1->no));
+ EXPECT_EQ(bv1->head.htype, (char)BM_VERT);
+ EXPECT_EQ(bv1->head.hflag, 0);
+ EXPECT_EQ(bv1->head.api_flag, 0);
+ bv2 = BM_vert_create(bm, NULL, NULL, BM_CREATE_NOP);
+ ASSERT_TRUE(bv2 != NULL);
+ EXPECT_TRUE(is_zero_v3(bv2->co));
+ /* create with example should copy custom data but not select flag */
+ BM_vert_select_set(bm, bv2, true);
+ BM_elem_float_data_set(&bm->vdata, bv2, CD_PROP_FLOAT, 1.5f);
+ bv3 = BM_vert_create(bm, co1, bv2, BM_CREATE_NOP);
+ ASSERT_TRUE(bv3 != NULL);
+ EXPECT_FALSE(BM_elem_flag_test((BMElem *)bv3, BM_ELEM_SELECT));
+ EXPECT_EQ(BM_elem_float_data_get(&bm->vdata, bv3, CD_PROP_FLOAT), 1.5f);
+ EXPECT_EQ(BM_mesh_elem_count(bm, BM_VERT), 3);
+ BM_mesh_free(bm);
+}
diff --git a/source/blender/io/alembic/CMakeLists.txt b/source/blender/io/alembic/CMakeLists.txt
index 681d6d04acd..de99a2c9d65 100644
--- a/source/blender/io/alembic/CMakeLists.txt
+++ b/source/blender/io/alembic/CMakeLists.txt
@@ -110,3 +110,17 @@ list(APPEND LIB
)
blender_add_lib(bf_alembic "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
+
+if(WITH_GTESTS)
+ set(TEST_SRC
+ tests/abc_export_test.cc
+ tests/abc_matrix_test.cc
+ )
+ set(TEST_INC
+ )
+ set(TEST_LIB
+ bf_alembic
+ )
+ include(GTestTesting)
+ blender_add_test_lib(bf_io_alembic_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB};${TEST_LIB}")
+endif()
diff --git a/source/blender/io/alembic/tests/abc_export_test.cc b/source/blender/io/alembic/tests/abc_export_test.cc
new file mode 100644
index 00000000000..5c2b505958e
--- /dev/null
+++ b/source/blender/io/alembic/tests/abc_export_test.cc
@@ -0,0 +1,161 @@
+#include "testing/testing.h"
+
+// Keep first since utildefines defines AT which conflicts with STL
+#include "exporter/abc_archive.h"
+#include "intern/abc_util.h"
+
+#include "BKE_main.h"
+#include "BLI_fileops.h"
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+#include "DNA_scene_types.h"
+
+#include "DEG_depsgraph.h"
+
+namespace blender {
+namespace io {
+namespace alembic {
+
+class AlembicExportTest : public testing::Test {
+ protected:
+ ABCArchive *abc_archive;
+
+ AlembicExportParams params;
+ Scene scene;
+ Depsgraph *depsgraph;
+ Main *bmain;
+
+ virtual void SetUp()
+ {
+ abc_archive = nullptr;
+
+ /* Fake a 25 FPS scene with a nonzero base (because that's sometimes forgotten) */
+ scene.r.frs_sec = 50;
+ scene.r.frs_sec_base = 2;
+ strcpy(scene.id.name, "SCTestScene");
+
+ bmain = BKE_main_new();
+
+ /* TODO(sergey): Pass scene layer somehow? */
+ ViewLayer *view_layer = (ViewLayer *)scene.view_layers.first;
+ depsgraph = DEG_graph_new(bmain, &scene, view_layer, DAG_EVAL_RENDER);
+ }
+
+ virtual void TearDown()
+ {
+ BKE_main_free(bmain);
+ DEG_graph_free(depsgraph);
+ deleteArchive();
+ }
+
+ // Call after setting up the parameters.
+ void createArchive()
+ {
+ if (abc_archive != nullptr) {
+ deleteArchive();
+ }
+ abc_archive = new ABCArchive(bmain, &scene, params, "somefile.abc");
+ }
+
+ void deleteArchive()
+ {
+ delete abc_archive;
+ if (BLI_exists("somefile.abc")) {
+ BLI_delete("somefile.abc", false, false);
+ }
+ abc_archive = nullptr;
+ }
+};
+
+TEST_F(AlembicExportTest, TimeSamplesFullShutterUniform)
+{
+ /* Test 5 samples per frame, for 2 frames. */
+ params.shutter_open = 0.0;
+ params.shutter_close = 1.0;
+ params.frame_start = 31.0;
+ params.frame_end = 32.0;
+ params.frame_samples_xform = params.frame_samples_shape = 5;
+ createArchive();
+ std::vector<double> frames(abc_archive->frames_begin(), abc_archive->frames_end());
+ EXPECT_EQ(10, frames.size());
+ EXPECT_NEAR(31.0, frames[0], 1e-5);
+ EXPECT_NEAR(31.2, frames[1], 1e-5);
+ EXPECT_NEAR(31.4, frames[2], 1e-5);
+ EXPECT_NEAR(31.6, frames[3], 1e-5);
+ EXPECT_NEAR(31.8, frames[4], 1e-5);
+ EXPECT_NEAR(32.0, frames[5], 1e-5);
+ EXPECT_NEAR(32.2, frames[6], 1e-5);
+ EXPECT_NEAR(32.4, frames[7], 1e-5);
+ EXPECT_NEAR(32.6, frames[8], 1e-5);
+ EXPECT_NEAR(32.8, frames[9], 1e-5);
+
+ for (double frame : frames) {
+ EXPECT_TRUE(abc_archive->is_xform_frame(frame));
+ EXPECT_TRUE(abc_archive->is_shape_frame(frame));
+ }
+}
+
+TEST_F(AlembicExportTest, TimeSamplesFullShutterDifferent)
+{
+ /* Test 3 samples per frame for transforms, and 2 per frame for shapes, for 2 frames. */
+ params.shutter_open = 0.0;
+ params.shutter_close = 1.0;
+ params.frame_start = 31.0;
+ params.frame_end = 32.0;
+ params.frame_samples_xform = 3;
+ params.frame_samples_shape = 2;
+ createArchive();
+ std::vector<double> frames(abc_archive->frames_begin(), abc_archive->frames_end());
+ EXPECT_EQ(8, frames.size());
+ EXPECT_NEAR(31.0, frames[0], 1e-5); // transform + shape
+ EXPECT_TRUE(abc_archive->is_xform_frame(frames[0]));
+ EXPECT_TRUE(abc_archive->is_shape_frame(frames[0]));
+ EXPECT_NEAR(31.33333, frames[1], 1e-5); // transform
+ EXPECT_TRUE(abc_archive->is_xform_frame(frames[1]));
+ EXPECT_FALSE(abc_archive->is_shape_frame(frames[1]));
+ EXPECT_NEAR(31.5, frames[2], 1e-5); // shape
+ EXPECT_FALSE(abc_archive->is_xform_frame(frames[2]));
+ EXPECT_TRUE(abc_archive->is_shape_frame(frames[2]));
+ EXPECT_NEAR(31.66666, frames[3], 1e-5); // transform
+ EXPECT_TRUE(abc_archive->is_xform_frame(frames[3]));
+ EXPECT_FALSE(abc_archive->is_shape_frame(frames[3]));
+ EXPECT_NEAR(32.0, frames[4], 1e-5); // transform + shape
+ EXPECT_TRUE(abc_archive->is_xform_frame(frames[4]));
+ EXPECT_TRUE(abc_archive->is_shape_frame(frames[4]));
+ EXPECT_NEAR(32.33333, frames[5], 1e-5); // transform
+ EXPECT_TRUE(abc_archive->is_xform_frame(frames[5]));
+ EXPECT_FALSE(abc_archive->is_shape_frame(frames[5]));
+ EXPECT_NEAR(32.5, frames[6], 1e-5); // shape
+ EXPECT_FALSE(abc_archive->is_xform_frame(frames[6]));
+ EXPECT_TRUE(abc_archive->is_shape_frame(frames[6]));
+ EXPECT_NEAR(32.66666, frames[7], 1e-5); // transform
+ EXPECT_TRUE(abc_archive->is_xform_frame(frames[7]));
+ EXPECT_FALSE(abc_archive->is_shape_frame(frames[7]));
+}
+
+TEST_F(AlembicExportTest, TimeSamples180degShutter)
+{
+ /* Test 5 samples per frame, for 2 frames. */
+ params.shutter_open = -0.25;
+ params.shutter_close = 0.25;
+ params.frame_start = 31.0;
+ params.frame_end = 32.0;
+ params.frame_samples_xform = params.frame_samples_shape = 5;
+ createArchive();
+ std::vector<double> frames(abc_archive->frames_begin(), abc_archive->frames_end());
+ EXPECT_EQ(10, frames.size());
+ EXPECT_NEAR(31 - 0.25, frames[0], 1e-5);
+ EXPECT_NEAR(31 - 0.15, frames[1], 1e-5);
+ EXPECT_NEAR(31 - 0.05, frames[2], 1e-5);
+ EXPECT_NEAR(31 + 0.05, frames[3], 1e-5);
+ EXPECT_NEAR(31 + 0.15, frames[4], 1e-5);
+ EXPECT_NEAR(32 - 0.25, frames[5], 1e-5);
+ EXPECT_NEAR(32 - 0.15, frames[6], 1e-5);
+ EXPECT_NEAR(32 - 0.05, frames[7], 1e-5);
+ EXPECT_NEAR(32 + 0.05, frames[8], 1e-5);
+ EXPECT_NEAR(32 + 0.15, frames[9], 1e-5);
+}
+
+} // namespace alembic
+} // namespace io
+} // namespace blender
diff --git a/source/blender/io/alembic/tests/abc_matrix_test.cc b/source/blender/io/alembic/tests/abc_matrix_test.cc
new file mode 100644
index 00000000000..b58e989b1a1
--- /dev/null
+++ b/source/blender/io/alembic/tests/abc_matrix_test.cc
@@ -0,0 +1,292 @@
+#include "testing/testing.h"
+
+// Keep first since utildefines defines AT which conflicts with STL
+#include "intern/abc_axis_conversion.h"
+
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+
+namespace blender {
+namespace io {
+namespace alembic {
+
+TEST(abc_matrix, CreateRotationMatrixY_YfromZ)
+{
+ // Input variables
+ float rot_x_mat[3][3];
+ float rot_y_mat[3][3];
+ float rot_z_mat[3][3];
+ float euler[3] = {0.f, M_PI_4, 0.f};
+
+ // Construct expected matrices
+ float unit[3][3];
+ float rot_z_min_quart_pi[3][3]; // rotation of -pi/4 radians over z-axis
+
+ unit_m3(unit);
+ unit_m3(rot_z_min_quart_pi);
+ rot_z_min_quart_pi[0][0] = M_SQRT1_2;
+ rot_z_min_quart_pi[0][1] = -M_SQRT1_2;
+ rot_z_min_quart_pi[1][0] = M_SQRT1_2;
+ rot_z_min_quart_pi[1][1] = M_SQRT1_2;
+
+ // Run tests
+ create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, ABC_YUP_FROM_ZUP);
+
+ EXPECT_M3_NEAR(rot_x_mat, unit, 1e-5f);
+ EXPECT_M3_NEAR(rot_y_mat, unit, 1e-5f);
+ EXPECT_M3_NEAR(rot_z_mat, rot_z_min_quart_pi, 1e-5f);
+}
+
+TEST(abc_matrix, CreateRotationMatrixZ_YfromZ)
+{
+ // Input variables
+ float rot_x_mat[3][3];
+ float rot_y_mat[3][3];
+ float rot_z_mat[3][3];
+ float euler[3] = {0.f, 0.f, M_PI_4};
+
+ // Construct expected matrices
+ float unit[3][3];
+ float rot_y_quart_pi[3][3]; // rotation of pi/4 radians over y-axis
+
+ unit_m3(unit);
+ unit_m3(rot_y_quart_pi);
+ rot_y_quart_pi[0][0] = M_SQRT1_2;
+ rot_y_quart_pi[0][2] = -M_SQRT1_2;
+ rot_y_quart_pi[2][0] = M_SQRT1_2;
+ rot_y_quart_pi[2][2] = M_SQRT1_2;
+
+ // Run tests
+ create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, ABC_YUP_FROM_ZUP);
+
+ EXPECT_M3_NEAR(rot_x_mat, unit, 1e-5f);
+ EXPECT_M3_NEAR(rot_y_mat, rot_y_quart_pi, 1e-5f);
+ EXPECT_M3_NEAR(rot_z_mat, unit, 1e-5f);
+}
+
+TEST(abc_matrix, CreateRotationMatrixXYZ_YfromZ)
+{
+ // Input variables
+ float rot_x_mat[3][3];
+ float rot_y_mat[3][3];
+ float rot_z_mat[3][3];
+ // in degrees: X=10, Y=20, Z=30
+ float euler[3] = {0.17453292012214f, 0.34906581044197f, 0.52359879016876f};
+
+ // Construct expected matrices
+ float rot_x_p10[3][3]; // rotation of +10 degrees over x-axis
+ float rot_y_p30[3][3]; // rotation of +30 degrees over y-axis
+ float rot_z_m20[3][3]; // rotation of -20 degrees over z-axis
+
+ unit_m3(rot_x_p10);
+ rot_x_p10[1][1] = 0.9848077297210693f;
+ rot_x_p10[1][2] = 0.1736481785774231f;
+ rot_x_p10[2][1] = -0.1736481785774231f;
+ rot_x_p10[2][2] = 0.9848077297210693f;
+
+ unit_m3(rot_y_p30);
+ rot_y_p30[0][0] = 0.8660253882408142f;
+ rot_y_p30[0][2] = -0.5f;
+ rot_y_p30[2][0] = 0.5f;
+ rot_y_p30[2][2] = 0.8660253882408142f;
+
+ unit_m3(rot_z_m20);
+ rot_z_m20[0][0] = 0.9396926164627075f;
+ rot_z_m20[0][1] = -0.3420201241970062f;
+ rot_z_m20[1][0] = 0.3420201241970062f;
+ rot_z_m20[1][1] = 0.9396926164627075f;
+
+ // Run tests
+ create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, ABC_YUP_FROM_ZUP);
+
+ EXPECT_M3_NEAR(rot_x_mat, rot_x_p10, 1e-5f);
+ EXPECT_M3_NEAR(rot_y_mat, rot_y_p30, 1e-5f);
+ EXPECT_M3_NEAR(rot_z_mat, rot_z_m20, 1e-5f);
+}
+
+TEST(abc_matrix, CreateRotationMatrixXYZ_ZfromY)
+{
+ // Input variables
+ float rot_x_mat[3][3];
+ float rot_y_mat[3][3];
+ float rot_z_mat[3][3];
+ // in degrees: X=10, Y=20, Z=30
+ float euler[3] = {0.1745329201221466f, 0.3490658104419708f, 0.5235987901687622f};
+
+ // Construct expected matrices
+ float rot_x_p10[3][3]; // rotation of +10 degrees over x-axis
+ float rot_y_m30[3][3]; // rotation of -30 degrees over y-axis
+ float rot_z_p20[3][3]; // rotation of +20 degrees over z-axis
+
+ unit_m3(rot_x_p10);
+ rot_x_p10[1][1] = 0.9848077297210693f;
+ rot_x_p10[1][2] = 0.1736481785774231f;
+ rot_x_p10[2][1] = -0.1736481785774231f;
+ rot_x_p10[2][2] = 0.9848077297210693f;
+
+ unit_m3(rot_y_m30);
+ rot_y_m30[0][0] = 0.8660253882408142f;
+ rot_y_m30[0][2] = 0.5f;
+ rot_y_m30[2][0] = -0.5f;
+ rot_y_m30[2][2] = 0.8660253882408142f;
+
+ unit_m3(rot_z_p20);
+ rot_z_p20[0][0] = 0.9396926164627075f;
+ rot_z_p20[0][1] = 0.3420201241970062f;
+ rot_z_p20[1][0] = -0.3420201241970062f;
+ rot_z_p20[1][1] = 0.9396926164627075f;
+
+ // Run tests
+ create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, ABC_ZUP_FROM_YUP);
+
+ EXPECT_M3_NEAR(rot_x_mat, rot_x_p10, 1e-5f);
+ EXPECT_M3_NEAR(rot_y_mat, rot_y_m30, 1e-5f);
+ EXPECT_M3_NEAR(rot_z_mat, rot_z_p20, 1e-5f);
+}
+
+TEST(abc_matrix, CopyM44AxisSwap_YfromZ)
+{
+ float result[4][4];
+
+ /* Construct an input matrix that performs a rotation like the tests
+ * above. This matrix was created by rotating a cube in Blender over
+ * (X=10, Y=20, Z=30 degrees in XYZ order) and translating over (1, 2, 3) */
+ float input[4][4] = {
+ {0.81379765272f, 0.4698463380336f, -0.342020124197f, 0.f},
+ {-0.44096961617f, 0.8825641274452f, 0.163175910711f, 0.f},
+ {0.37852230668f, 0.0180283170193f, 0.925416588783f, 0.f},
+ {1.f, 2.f, 3.f, 1.f},
+ };
+
+ copy_m44_axis_swap(result, input, ABC_YUP_FROM_ZUP);
+
+ /* Check the resulting rotation & translation. */
+ float trans[4] = {1.f, 3.f, -2.f, 1.f};
+ EXPECT_V4_NEAR(trans, result[3], 1e-5f);
+
+ /* This matrix was created by rotating a cube in Blender over
+ * (X=10, Y=30, Z=-20 degrees in XZY order) and translating over (1, 3, -2) */
+ float expect[4][4] = {
+ {0.813797652721f, -0.342020124197f, -0.469846338033f, 0.f},
+ {0.378522306680f, 0.925416588783f, -0.018028317019f, 0.f},
+ {0.440969616174f, -0.163175910711f, 0.882564127445f, 0.f},
+ {1.f, 3.f, -2.f, 1.f},
+ };
+ EXPECT_M4_NEAR(expect, result, 1e-5f);
+}
+
+TEST(abc_matrix, CopyM44AxisSwapWithScale_YfromZ)
+{
+ float result[4][4];
+
+ /* Construct an input matrix that performs a rotation like the tests
+ * above. This matrix was created by rotating a cube in Blender over
+ * (X=10, Y=20, Z=30 degrees in XYZ order), translating over (1, 2, 3),
+ * and scaling by (4, 5, 6). */
+ float input[4][4] = {
+ {3.25519061088f, 1.8793853521347f, -1.368080496788f, 0.f},
+ {-2.20484805107f, 4.4128208160400f, 0.815879583358f, 0.f},
+ {2.27113389968f, 0.1081698983907f, 5.552499771118f, 0.f},
+ {1.f, 2.f, 3.f, 1.f},
+ };
+
+ copy_m44_axis_swap(result, input, ABC_YUP_FROM_ZUP);
+
+ /* This matrix was created by rotating a cube in Blender over
+ * (X=10, Y=30, Z=-20 degrees in XZY order), translating over (1, 3, -2)
+ * and scaling over (4, 6, 5). */
+ float expect[4][4] = {
+ {3.255190610885f, -1.368080496788f, -1.879385352134f, 0.f},
+ {2.271133899688f, 5.552499771118f, -0.108169898390f, 0.f},
+ {2.204848051071f, -0.815879583358f, 4.412820816040f, 0.f},
+ {1.f, 3.f, -2.f, 1.f},
+ };
+ EXPECT_M4_NEAR(expect, result, 1e-5f);
+}
+
+TEST(abc_matrix, CopyM44AxisSwap_ZfromY)
+{
+ float result[4][4];
+
+ /* This matrix was created by rotating a cube in Blender over
+ * (X=10, Y=30, Z=-20 degrees in XZY order) and translating over (1, 3, -2) */
+ float input[4][4] = {
+ {0.813797652721f, -0.342020124197f, -0.469846338033f, 0.f},
+ {0.378522306680f, 0.925416588783f, -0.018028317019f, 0.f},
+ {0.440969616174f, -0.163175910711f, 0.882564127445f, 0.f},
+ {1.f, 3.f, -2.f, 1.f},
+ };
+
+ copy_m44_axis_swap(result, input, ABC_ZUP_FROM_YUP);
+
+ /* This matrix was created by rotating a cube in Blender over
+ * (X=10, Y=20, Z=30 degrees in XYZ order) and translating over (1, 2, 3) */
+ float expect[4][4] = {
+ {0.813797652721f, 0.469846338033f, -0.342020124197f, 0.f},
+ {-0.44096961617f, 0.882564127445f, 0.163175910711f, 0.f},
+ {0.378522306680f, 0.018028317019f, 0.925416588783f, 0.f},
+ {1.f, 2.f, 3.f, 1.f},
+ };
+
+ EXPECT_M4_NEAR(expect, result, 1e-5f);
+}
+
+TEST(abc_matrix, CopyM44AxisSwapWithScale_ZfromY)
+{
+ float result[4][4];
+
+ /* This matrix was created by rotating a cube in Blender over
+ * (X=10, Y=30, Z=-20 degrees in XZY order), translating over (1, 3, -2)
+ * and scaling over (4, 6, 5). */
+ float input[4][4] = {
+ {3.2551906108f, -1.36808049678f, -1.879385352134f, 0.f},
+ {2.2711338996f, 5.55249977111f, -0.108169898390f, 0.f},
+ {2.2048480510f, -0.81587958335f, 4.412820816040f, 0.f},
+ {1.f, 3.f, -2.f, 1.f},
+ };
+
+ copy_m44_axis_swap(result, input, ABC_ZUP_FROM_YUP);
+
+ /* This matrix was created by rotating a cube in Blender over
+ * (X=10, Y=20, Z=30 degrees in XYZ order), translating over (1, 2, 3),
+ * and scaling by (4, 5, 6). */
+ float expect[4][4] = {
+ {3.25519061088f, 1.879385352134f, -1.36808049678f, 0.f},
+ {-2.2048480510f, 4.412820816040f, 0.81587958335f, 0.f},
+ {2.27113389968f, 0.108169898390f, 5.55249977111f, 0.f},
+ {1.f, 2.f, 3.f, 1.f},
+ };
+
+ EXPECT_M4_NEAR(expect, result, 1e-5f);
+}
+
+TEST(abc_matrix, CopyM44AxisSwapWithScale_gimbal_ZfromY)
+{
+ float result[4][4];
+
+ /* This matrix represents a rotation over (-90, -0, -0) degrees,
+ * and a translation over (-0, -0.1, -0). It is in Y=up. */
+ float input[4][4] = {
+ {1.000f, 0.000f, 0.000f, 0.000f},
+ {0.000f, 0.000f, -1.000f, 0.000f},
+ {0.000f, 1.000f, 0.000f, 0.000f},
+ {-0.000f, -0.100f, -0.000f, 1.000f},
+ };
+
+ copy_m44_axis_swap(result, input, ABC_ZUP_FROM_YUP);
+
+ /* Since the rotation is only over the X-axis, it should not change.
+ * The translation does change. */
+ float expect[4][4] = {
+ {1.000f, 0.000f, 0.000f, 0.000f},
+ {0.000f, 0.000f, -1.000f, 0.000f},
+ {0.000f, 1.000f, 0.000f, 0.000f},
+ {-0.000f, 0.000f, -0.100f, 1.000f},
+ };
+
+ EXPECT_M4_NEAR(expect, result, 1e-5f);
+}
+
+} // namespace alembic
+} // namespace io
+} // namespace blender
diff --git a/source/blender/io/common/CMakeLists.txt b/source/blender/io/common/CMakeLists.txt
index d3341767f8b..7e39af32f11 100644
--- a/source/blender/io/common/CMakeLists.txt
+++ b/source/blender/io/common/CMakeLists.txt
@@ -55,8 +55,11 @@ if(WITH_GTESTS)
intern/hierarchy_context_order_test.cc
intern/object_identifier_test.cc
)
+ set(TEST_INC
+ ../../blenloader
+ )
set(TEST_LIB
- bf_blenloader_test
+ bf_blenloader_tests
bf_io_common
)
include(GTestTesting)
diff --git a/source/blender/io/common/intern/abstract_hierarchy_iterator_test.cc b/source/blender/io/common/intern/abstract_hierarchy_iterator_test.cc
index 2bc7560e177..cbb2e63a99f 100644
--- a/source/blender/io/common/intern/abstract_hierarchy_iterator_test.cc
+++ b/source/blender/io/common/intern/abstract_hierarchy_iterator_test.cc
@@ -17,7 +17,8 @@
* All rights reserved.
*/
#include "IO_abstract_hierarchy_iterator.h"
-#include "blenloader/blendfile_loading_base_test.h"
+
+#include "tests/blendfile_loading_base_test.h"
#include "BLI_math.h"
#include "DEG_depsgraph.h"