From 53d203dea8230da4e80f3cc61468a4e24ff6759c Mon Sep 17 00:00:00 2001 From: Brecht Van Lommel Date: Fri, 7 Aug 2020 16:43:42 +0200 Subject: 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 --- source/blender/blenlib/CMakeLists.txt | 34 +- .../blender/blenlib/tests/BLI_array_store_test.cc | 884 ++++++++++ .../blender/blenlib/tests/BLI_array_utils_test.cc | 191 +++ .../blender/blenlib/tests/BLI_delaunay_2d_test.cc | 1762 ++++++++++++++++++++ .../blenlib/tests/BLI_expr_pylike_eval_test.cc | 363 ++++ source/blender/blenlib/tests/BLI_ghash_test.cc | 209 +++ source/blender/blenlib/tests/BLI_hash_mm2a_test.cc | 74 + .../blender/blenlib/tests/BLI_heap_simple_test.cc | 121 ++ source/blender/blenlib/tests/BLI_heap_test.cc | 207 +++ source/blender/blenlib/tests/BLI_kdopbvh_test.cc | 134 ++ .../blenlib/tests/BLI_linklist_lockfree_test.cc | 111 ++ source/blender/blenlib/tests/BLI_listbase_test.cc | 255 +++ source/blender/blenlib/tests/BLI_math_base_test.cc | 115 ++ source/blender/blenlib/tests/BLI_math_bits_test.cc | 50 + .../blender/blenlib/tests/BLI_math_color_test.cc | 76 + source/blender/blenlib/tests/BLI_math_geom_test.cc | 19 + .../blender/blenlib/tests/BLI_math_matrix_test.cc | 99 ++ .../blender/blenlib/tests/BLI_math_vector_test.cc | 47 + source/blender/blenlib/tests/BLI_memiter_test.cc | 277 +++ source/blender/blenlib/tests/BLI_path_util_test.cc | 606 +++++++ .../blender/blenlib/tests/BLI_polyfill_2d_test.cc | 751 +++++++++ .../blender/blenlib/tests/BLI_ressource_strings.h | 610 +++++++ .../blender/blenlib/tests/BLI_session_uuid_test.cc | 20 + source/blender/blenlib/tests/BLI_stack_test.cc | 216 +++ source/blender/blenlib/tests/BLI_string_test.cc | 794 +++++++++ .../blender/blenlib/tests/BLI_string_utf8_test.cc | 286 ++++ .../blender/blenlib/tests/BLI_task_graph_test.cc | 188 +++ source/blender/blenlib/tests/BLI_task_test.cc | 183 ++ .../performance/BLI_ghash_performance_test.cc | 571 +++++++ .../tests/performance/BLI_task_performance_test.cc | 210 +++ .../blenlib/tests/performance/CMakeLists.txt | 37 + source/blender/blenloader/CMakeLists.txt | 14 + .../blenloader/tests/blendfile_load_test.cc | 31 + .../tests/blendfile_loading_base_test.cc | 162 ++ .../blenloader/tests/blendfile_loading_base_test.h | 64 + source/blender/bmesh/CMakeLists.txt | 13 + source/blender/bmesh/tests/bmesh_core_test.cc | 40 + source/blender/io/alembic/CMakeLists.txt | 14 + source/blender/io/alembic/tests/abc_export_test.cc | 161 ++ source/blender/io/alembic/tests/abc_matrix_test.cc | 292 ++++ source/blender/io/common/CMakeLists.txt | 5 +- .../intern/abstract_hierarchy_iterator_test.cc | 3 +- 42 files changed, 10296 insertions(+), 3 deletions(-) create mode 100644 source/blender/blenlib/tests/BLI_array_store_test.cc create mode 100644 source/blender/blenlib/tests/BLI_array_utils_test.cc create mode 100644 source/blender/blenlib/tests/BLI_delaunay_2d_test.cc create mode 100644 source/blender/blenlib/tests/BLI_expr_pylike_eval_test.cc create mode 100644 source/blender/blenlib/tests/BLI_ghash_test.cc create mode 100644 source/blender/blenlib/tests/BLI_hash_mm2a_test.cc create mode 100644 source/blender/blenlib/tests/BLI_heap_simple_test.cc create mode 100644 source/blender/blenlib/tests/BLI_heap_test.cc create mode 100644 source/blender/blenlib/tests/BLI_kdopbvh_test.cc create mode 100644 source/blender/blenlib/tests/BLI_linklist_lockfree_test.cc create mode 100644 source/blender/blenlib/tests/BLI_listbase_test.cc create mode 100644 source/blender/blenlib/tests/BLI_math_base_test.cc create mode 100644 source/blender/blenlib/tests/BLI_math_bits_test.cc create mode 100644 source/blender/blenlib/tests/BLI_math_color_test.cc create mode 100644 source/blender/blenlib/tests/BLI_math_geom_test.cc create mode 100644 source/blender/blenlib/tests/BLI_math_matrix_test.cc create mode 100644 source/blender/blenlib/tests/BLI_math_vector_test.cc create mode 100644 source/blender/blenlib/tests/BLI_memiter_test.cc create mode 100644 source/blender/blenlib/tests/BLI_path_util_test.cc create mode 100644 source/blender/blenlib/tests/BLI_polyfill_2d_test.cc create mode 100644 source/blender/blenlib/tests/BLI_ressource_strings.h create mode 100644 source/blender/blenlib/tests/BLI_session_uuid_test.cc create mode 100644 source/blender/blenlib/tests/BLI_stack_test.cc create mode 100644 source/blender/blenlib/tests/BLI_string_test.cc create mode 100644 source/blender/blenlib/tests/BLI_string_utf8_test.cc create mode 100644 source/blender/blenlib/tests/BLI_task_graph_test.cc create mode 100644 source/blender/blenlib/tests/BLI_task_test.cc create mode 100644 source/blender/blenlib/tests/performance/BLI_ghash_performance_test.cc create mode 100644 source/blender/blenlib/tests/performance/BLI_task_performance_test.cc create mode 100644 source/blender/blenlib/tests/performance/CMakeLists.txt create mode 100644 source/blender/blenloader/tests/blendfile_load_test.cc create mode 100644 source/blender/blenloader/tests/blendfile_loading_base_test.cc create mode 100644 source/blender/blenloader/tests/blendfile_loading_base_test.h create mode 100644 source/blender/bmesh/tests/bmesh_core_test.cc create mode 100644 source/blender/io/alembic/tests/abc_export_test.cc create mode 100644 source/blender/io/alembic/tests/abc_matrix_test.cc (limited to 'source') 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 +#include +#include + +#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 + * [#verts lines) + * [#edges lines] + * ... [#faces lines] + */ +static void fill_input_from_string(CDT_input *r_input, const char *spec) +{ + std::string line; + std::vector> 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()); + 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 + +#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 + +#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 + +#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 + +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 + +#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 +#include +#include // NOLINT +#include +#include +#include + +#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 expected_words_info_init, + int max_words = -1) + { + const vector 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 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(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 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>; + + 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 - 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 + +#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 = ∑ + 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(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 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 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 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" -- cgit v1.2.3