diff options
Diffstat (limited to 'tests')
41 files changed, 3417 insertions, 481 deletions
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fa0b86a2637..64326f34377 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,8 @@ # Python CTests -add_subdirectory(python) +if(WITH_BLENDER) + add_subdirectory(python) +endif() # GTest add_subdirectory(gtests) - diff --git a/tests/check_deprecated.py b/tests/check_deprecated.py index cf8f8e0cc35..6e07f8fdb31 100644 --- a/tests/check_deprecated.py +++ b/tests/check_deprecated.py @@ -53,10 +53,8 @@ def is_source_any(filename): def source_list(path, filename_check=None): for dirpath, dirnames, filenames in os.walk(path): - - # skip '.svn' - if dirpath.startswith("."): - continue + # skip '.git' + dirnames[:] = [d for d in dirnames if not d.startswith(".")] for filename in filenames: if filename_check is None or filename_check(filename): diff --git a/tests/gtests/CMakeLists.txt b/tests/gtests/CMakeLists.txt index a3860ce3e67..781da7bf452 100644 --- a/tests/gtests/CMakeLists.txt +++ b/tests/gtests/CMakeLists.txt @@ -4,6 +4,10 @@ if(WITH_GTESTS) Include(GTestTesting) + add_definitions(${GFLAGS_DEFINES}) + add_definitions(${GLOG_DEFINES}) + add_definitions(-DBLENDER_GFLAGS_NAMESPACE=${GFLAGS_NAMESPACE}) + # Otherwise we get warnings here that we cant fix in external projects remove_strict_flags() @@ -11,5 +15,7 @@ if(WITH_GTESTS) add_subdirectory(blenlib) add_subdirectory(guardedalloc) add_subdirectory(bmesh) + if(WITH_ALEMBIC) + add_subdirectory(alembic) + endif() endif() - diff --git a/tests/gtests/alembic/CMakeLists.txt b/tests/gtests/alembic/CMakeLists.txt new file mode 100644 index 00000000000..fadf549e212 --- /dev/null +++ b/tests/gtests/alembic/CMakeLists.txt @@ -0,0 +1,52 @@ +# ***** 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. +# +# Contributor(s): Sybren A. Stüvel +# +# ***** END GPL LICENSE BLOCK ***** + +set(INC + . + .. + ../../../source/blender/blenlib + ../../../source/blender/alembic + ../../../source/blender/makesdna + ${ALEMBIC_INCLUDE_DIRS} + ${BOOST_INCLUDE_DIR} + ${HDF5_INCLUDE_DIRS} + ${OPENEXR_INCLUDE_DIRS} +) + +include_directories(${INC}) + +setup_libdirs() +get_property(BLENDER_SORTED_LIBS GLOBAL PROPERTY BLENDER_SORTED_LIBS_PROP) + +if(WITH_BUILDINFO) + set(_buildinfo_src "$<TARGET_OBJECTS:buildinfoobj>") +else() + set(_buildinfo_src "") +endif() + +# For motivation on doubling BLENDER_SORTED_LIBS, see ../bmesh/CMakeLists.txt +BLENDER_SRC_GTEST(alembic "abc_matrix_test.cc;abc_export_test.cc;${_buildinfo_src}" "${BLENDER_SORTED_LIBS};${BLENDER_SORTED_LIBS}") + +unset(_buildinfo_src) + +setup_liblinks(alembic_test) diff --git a/tests/gtests/alembic/abc_export_test.cc b/tests/gtests/alembic/abc_export_test.cc new file mode 100644 index 00000000000..63c1d179e51 --- /dev/null +++ b/tests/gtests/alembic/abc_export_test.cc @@ -0,0 +1,120 @@ +#include "testing/testing.h" + +// Keep first since utildefines defines AT which conflicts with fucking STL +#include "intern/abc_util.h" +#include "intern/abc_exporter.h" + +extern "C" { +#include "BLI_utildefines.h" +#include "BLI_math.h" +#include "DNA_scene_types.h" +} + +class TestableAbcExporter : public AbcExporter { +public: + TestableAbcExporter(Scene *scene, const char *filename, ExportSettings &settings) + : AbcExporter(scene, filename, settings) + {} + + void getShutterSamples(unsigned int nr_of_samples, + bool time_relative, + std::vector<double> &samples) + { + AbcExporter::getShutterSamples(nr_of_samples, time_relative, samples); + } + + void getFrameSet(unsigned int nr_of_samples, + std::set<double> &frames) { + AbcExporter::getFrameSet(nr_of_samples, frames); + } + +}; + + +TEST(abc_export, TimeSamplesFullShutter) { + ExportSettings settings; + settings.frame_start = 31.0; + settings.frame_end = 223.0; + settings.shutter_open = 0.0; + settings.shutter_close = 1.0; + + /* Fake a 25 FPS scene with a nonzero base (because that's sometimes forgotten) */ + Scene scene; + scene.r.frs_sec = 50; + scene.r.frs_sec_base = 2; + + TestableAbcExporter exporter(&scene, "somefile.abc", settings); + std::vector<double> samples; + + /* test 5 samples per frame */ + exporter.getShutterSamples(5, true, samples); + EXPECT_EQ(5, samples.size()); + EXPECT_NEAR(1.240, samples[0], 1e-5f); + EXPECT_NEAR(1.248, samples[1], 1e-5f); + EXPECT_NEAR(1.256, samples[2], 1e-5f); + EXPECT_NEAR(1.264, samples[3], 1e-5f); + EXPECT_NEAR(1.272, samples[4], 1e-5f); + + /* test same, but using frame number offset instead of time */ + exporter.getShutterSamples(5, false, samples); + EXPECT_EQ(5, samples.size()); + EXPECT_NEAR(0.0, samples[0], 1e-5f); + EXPECT_NEAR(0.2, samples[1], 1e-5f); + EXPECT_NEAR(0.4, samples[2], 1e-5f); + EXPECT_NEAR(0.6, samples[3], 1e-5f); + EXPECT_NEAR(0.8, samples[4], 1e-5f); + + /* use the same setup to test getFrameSet() */ + std::set<double> frames; + exporter.getFrameSet(5, frames); + EXPECT_EQ(965, frames.size()); + EXPECT_EQ(1, frames.count(31.0)); + EXPECT_EQ(1, frames.count(31.2)); + EXPECT_EQ(1, frames.count(31.4)); + EXPECT_EQ(1, frames.count(31.6)); + EXPECT_EQ(1, frames.count(31.8)); +} + + +TEST(abc_export, TimeSamples180degShutter) { + ExportSettings settings; + settings.frame_start = 31.0; + settings.frame_end = 223.0; + settings.shutter_open = -0.25; + settings.shutter_close = 0.25; + + /* Fake a 25 FPS scene with a nonzero base (because that's sometimes forgotten) */ + Scene scene; + scene.r.frs_sec = 50; + scene.r.frs_sec_base = 2; + + TestableAbcExporter exporter(&scene, "somefile.abc", settings); + std::vector<double> samples; + + /* test 5 samples per frame */ + exporter.getShutterSamples(5, true, samples); + EXPECT_EQ(5, samples.size()); + EXPECT_NEAR(1.230, samples[0], 1e-5f); + EXPECT_NEAR(1.234, samples[1], 1e-5f); + EXPECT_NEAR(1.238, samples[2], 1e-5f); + EXPECT_NEAR(1.242, samples[3], 1e-5f); + EXPECT_NEAR(1.246, samples[4], 1e-5f); + + /* test same, but using frame number offset instead of time */ + exporter.getShutterSamples(5, false, samples); + EXPECT_EQ(5, samples.size()); + EXPECT_NEAR(-0.25, samples[0], 1e-5f); + EXPECT_NEAR(-0.15, samples[1], 1e-5f); + EXPECT_NEAR(-0.05, samples[2], 1e-5f); + EXPECT_NEAR( 0.05, samples[3], 1e-5f); + EXPECT_NEAR( 0.15, samples[4], 1e-5f); + + /* Use the same setup to test getFrameSet(). + * Here only a few numbers are tested, due to rounding issues. */ + std::set<double> frames; + exporter.getFrameSet(5, frames); + EXPECT_EQ(965, frames.size()); + EXPECT_EQ(1, frames.count(30.75)); + EXPECT_EQ(1, frames.count(30.95)); + EXPECT_EQ(1, frames.count(31.15)); +} diff --git a/tests/gtests/alembic/abc_matrix_test.cc b/tests/gtests/alembic/abc_matrix_test.cc new file mode 100644 index 00000000000..08bce1ed50f --- /dev/null +++ b/tests/gtests/alembic/abc_matrix_test.cc @@ -0,0 +1,282 @@ +#include "testing/testing.h" + +// Keep first since utildefines defines AT which conflicts with fucking STL +#include "intern/abc_util.h" + +extern "C" { +#include "BLI_utildefines.h" +#include "BLI_math.h" +} + + +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); +} diff --git a/tests/gtests/blenlib/BLI_array_store_test.cc b/tests/gtests/blenlib/BLI_array_store_test.cc index b71dc4575f1..370a4111bae 100644 --- a/tests/gtests/blenlib/BLI_array_store_test.cc +++ b/tests/gtests/blenlib/BLI_array_store_test.cc @@ -36,15 +36,15 @@ static void print_mem_saved(const char *id, const BArrayStore *bs) /* -------------------------------------------------------------------- */ /* Test Chunks (building data from list of chunks) */ -typedef struct TestChunnk { - struct TestChunnk *next, *prev; +typedef struct TestChunk { + struct TestChunk *next, *prev; const void *data; size_t data_len; -} TestChunnk; +} TestChunk; -static TestChunnk *testchunk_list_add(ListBase *lb, const void *data, size_t data_len) +static TestChunk *testchunk_list_add(ListBase *lb, const void *data, size_t data_len) { - TestChunnk *tc = (TestChunnk *)MEM_mallocN(sizeof(*tc), __func__); + TestChunk *tc = (TestChunk *)MEM_mallocN(sizeof(*tc), __func__); tc->data = data; tc->data_len = data_len; BLI_addtail(lb, tc); @@ -53,7 +53,7 @@ static TestChunnk *testchunk_list_add(ListBase *lb, const void *data, size_t dat } #if 0 -static TestChunnk *testchunk_list_add_copydata(ListBase *lb, const void *data, size_t data_len) +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); @@ -63,7 +63,7 @@ static TestChunnk *testchunk_list_add_copydata(ListBase *lb, const void *data, s static void testchunk_list_free(ListBase *lb) { - for (TestChunnk *tc = (TestChunnk *)lb->first, *tb_next; tc; tc = tb_next) { + for (TestChunk *tc = (TestChunk *)lb->first, *tb_next; tc; tc = tb_next) { tb_next = tc->next; MEM_freeN((void *)tc->data); MEM_freeN(tc); @@ -77,12 +77,12 @@ static char *testchunk_as_data( size_t *r_data_len) { size_t data_len = 0; - for (TestChunnk *tc = (TestChunnk *)lb->first; tc; tc = tc->next) { + 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 (TestChunnk *tc = (TestChunnk *)lb->first; tc; tc = tc->next) { + 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; @@ -95,7 +95,7 @@ static char *testchunk_as_data( #endif static char *testchunk_as_data_array( - TestChunnk **tc_array, int tc_array_len, + TestChunk **tc_array, int tc_array_len, size_t *r_data_len) { size_t data_len = 0; @@ -105,7 +105,7 @@ static char *testchunk_as_data_array( char *data = (char *)MEM_mallocN(data_len, __func__); size_t i = 0; for (int tc_index = 0; tc_index < tc_array_len; tc_index++) { - TestChunnk *tc = tc_array[tc_index]; + TestChunk *tc = tc_array[tc_index]; memcpy(&data[i], tc->data, tc->data_len); i += tc->data_len; } @@ -280,8 +280,8 @@ static void testbuffer_run_tests_single( BArrayStore *bs, ListBase *lb) { testbuffer_list_store_populate(bs, lb); - EXPECT_EQ(true, testbuffer_list_validate(lb)); - EXPECT_EQ(true, BLI_array_store_is_valid(bs)); + EXPECT_TRUE(testbuffer_list_validate(lb)); + EXPECT_TRUE(BLI_array_store_is_valid(bs)); #ifdef DEBUG_PRINT print_mem_saved("data", bs); #endif @@ -326,7 +326,7 @@ 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(sizeof(data) - 1, BLI_array_store_state_size_get(state)); + EXPECT_EQ(BLI_array_store_state_size_get(state), sizeof(data) - 1); BLI_array_store_state_remove(bs, state); BLI_array_store_destroy(bs); } @@ -340,7 +340,7 @@ TEST(array_store, Single) 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(sizeof(data_src), data_dst_len); + EXPECT_EQ(data_dst_len, sizeof(data_src)); BLI_array_store_destroy(bs); MEM_freeN((void *)data_dst); } @@ -354,8 +354,8 @@ TEST(array_store, DoubleNop) 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(sizeof(data_src), BLI_array_store_calc_size_compacted_get(bs)); - EXPECT_EQ(sizeof(data_src) * 2, BLI_array_store_calc_size_expanded_get(bs)); + 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; @@ -367,7 +367,7 @@ TEST(array_store, DoubleNop) EXPECT_STREQ(data_src, data_dst); MEM_freeN((void *)data_dst); - EXPECT_EQ(sizeof(data_src), data_dst_len); + EXPECT_EQ(data_dst_len, sizeof(data_src)); BLI_array_store_destroy(bs); } @@ -382,8 +382,8 @@ TEST(array_store, DoubleDiff) 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(sizeof(data_src_a) * 2, BLI_array_store_calc_size_compacted_get(bs)); - EXPECT_EQ(sizeof(data_src_a) * 2, BLI_array_store_calc_size_expanded_get(bs)); + 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); @@ -423,19 +423,19 @@ TEST(array_store, TextDupeIncreaseDecrease) /* forward */ testbuffer_list_store_populate(bs, &lb); - EXPECT_EQ(true, testbuffer_list_validate(&lb)); - EXPECT_EQ(true, BLI_array_store_is_valid(bs)); - EXPECT_EQ(strlen(D), BLI_array_store_calc_size_compacted_get(bs)); + 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_EQ(true, testbuffer_list_validate(&lb)); - EXPECT_EQ(true, BLI_array_store_is_valid(bs)); + EXPECT_TRUE(testbuffer_list_validate(&lb)); + EXPECT_TRUE(BLI_array_store_is_valid(bs)); /* larger since first block doesn't de-duplicate */ - EXPECT_EQ(strlen(D) * 4, BLI_array_store_calc_size_compacted_get(bs)); + EXPECT_EQ(BLI_array_store_calc_size_compacted_get(bs), strlen(D) * 4); #undef D testbuffer_list_free(&lb); \ @@ -677,9 +677,9 @@ static void random_chunk_mutate_helper( ListBase random_chunks; BLI_listbase_clear(&random_chunks); random_chunk_generate(&random_chunks, chunks_per_buffer, stride, chunk_count, random_seed); - TestChunnk **chunks_array = (TestChunnk **)MEM_mallocN(chunks_per_buffer * sizeof(TestChunnk *), __func__); + TestChunk **chunks_array = (TestChunk **)MEM_mallocN(chunks_per_buffer * sizeof(TestChunk *), __func__); { - TestChunnk *tc = (TestChunnk *)random_chunks.first; + TestChunk *tc = (TestChunk *)random_chunks.first; for (int i = 0; i < chunks_per_buffer; i++, tc = tc->next) { chunks_array[i] = tc; } @@ -692,7 +692,7 @@ static void random_chunk_mutate_helper( { RNG *rng = BLI_rng_new(random_seed); for (int i = 0; i < items_total; i++) { - BLI_rng_shuffle_array(rng, chunks_array, sizeof(TestChunnk *), chunks_per_buffer); + 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); @@ -708,7 +708,7 @@ static void random_chunk_mutate_helper( testbuffer_run_tests_single(bs, &lb); size_t expected_size = chunks_per_buffer * chunk_count * stride; - EXPECT_EQ(expected_size, BLI_array_store_calc_size_compacted_get(bs)); + EXPECT_EQ(BLI_array_store_calc_size_compacted_get(bs), expected_size); BLI_array_store_destroy(bs); @@ -782,8 +782,8 @@ TEST(array_store, PlainTextFiles) /* forwards */ testbuffer_list_store_populate(bs, &lb); - EXPECT_EQ(true, testbuffer_list_validate(&lb)); - EXPECT_EQ(true, BLI_array_store_is_valid(bs)); + 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 @@ -793,8 +793,8 @@ TEST(array_store, PlainTextFiles) /* backwards */ testbuffer_list_store_populate(bs, &lb); - EXPECT_EQ(true, testbuffer_list_validate(&lb)); - EXPECT_EQ(true, BLI_array_store_is_valid(bs)); + 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 diff --git a/tests/gtests/blenlib/BLI_array_utils_test.cc b/tests/gtests/blenlib/BLI_array_utils_test.cc index eabf5bc72cf..c4601e00fbd 100644 --- a/tests/gtests/blenlib/BLI_array_utils_test.cc +++ b/tests/gtests/blenlib/BLI_array_utils_test.cc @@ -5,7 +5,7 @@ extern "C" { #include "BLI_utildefines.h" #include "BLI_array_utils.h" -#include "BLI_stackdefines.h" +#include "BLI_utildefines_stack.h" } /* -------------------------------------------------------------------- */ @@ -45,50 +45,50 @@ TEST(array_utils, ReverseInt4) TEST(array_utils, FindIndexStringEmpty) { char data[] = "", find = '0'; - EXPECT_EQ(-1, BLI_array_findindex(data, ARRAY_SIZE(data) - 1, &find)); - EXPECT_EQ(-1, BLI_array_rfindindex(data, ARRAY_SIZE(data) - 1, &find)); + 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(0, BLI_array_findindex(data, ARRAY_SIZE(data) - 1, &find)); - EXPECT_EQ(0, BLI_array_rfindindex(data, ARRAY_SIZE(data) - 1, &find)); + 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(-1, BLI_array_findindex(data, ARRAY_SIZE(data) - 1, &find)); - EXPECT_EQ(-1, BLI_array_rfindindex(data, ARRAY_SIZE(data) - 1, &find)); + 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(3, BLI_array_findindex(data, ARRAY_SIZE(data) - 1, &find)); - EXPECT_EQ(3, BLI_array_rfindindex(data, ARRAY_SIZE(data) - 1, &find)); + 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(3, BLI_array_findindex(data, ARRAY_SIZE(data), &find)); - EXPECT_EQ(3, BLI_array_rfindindex(data, ARRAY_SIZE(data), &find)); + 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(0, BLI_array_findindex(data, ARRAY_SIZE(data), &find)); - EXPECT_EQ(3, BLI_array_rfindindex(data, ARRAY_SIZE(data), &find)); + 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(1, BLI_array_findindex(data, ARRAY_SIZE(data), &find)); - EXPECT_EQ(2, BLI_array_rfindindex(data, ARRAY_SIZE(data), &find)); + 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) @@ -102,18 +102,18 @@ TEST(array_utils, FindIndexPointer) #define STACK_PUSH_AND_CHECK_FORWARD(v, i) { \ STACK_PUSH(data, v); \ - EXPECT_EQ(i, BLI_array_findindex(data, STACK_SIZE(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(i, BLI_array_rfindindex(data, STACK_SIZE(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(i, BLI_array_findindex(data, STACK_SIZE(data), &(v))); \ - EXPECT_EQ(i, BLI_array_rfindindex(data, STACK_SIZE(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); diff --git a/tests/gtests/blenlib/BLI_ghash_performance_test.cc b/tests/gtests/blenlib/BLI_ghash_performance_test.cc index fb32cb3f0a5..924c84d72d0 100644 --- a/tests/gtests/blenlib/BLI_ghash_performance_test.cc +++ b/tests/gtests/blenlib/BLI_ghash_performance_test.cc @@ -118,21 +118,21 @@ static void str_ghash_tests(GHash *ghash, const char *id) TIMEIT_START(string_lookup); v = BLI_ghash_lookup(ghash, data_bis); - EXPECT_EQ(data_bis[0], GET_INT_FROM_POINTER(v)); + EXPECT_EQ(GET_INT_FROM_POINTER(v), data_bis[0]); for (p = w = c = data_bis; *c; c++) { if (*c == '.') { *c = '\0'; v = BLI_ghash_lookup(ghash, w); - EXPECT_EQ(w[0], GET_INT_FROM_POINTER(v)); + EXPECT_EQ(GET_INT_FROM_POINTER(v), w[0]); v = BLI_ghash_lookup(ghash, p); - EXPECT_EQ(p[0], GET_INT_FROM_POINTER(v)); + EXPECT_EQ(GET_INT_FROM_POINTER(v), p[0]); p = w = c + 1; } else if (*c == ' ') { *c = '\0'; v = BLI_ghash_lookup(ghash, w); - EXPECT_EQ(w[0], GET_INT_FROM_POINTER(v)); + EXPECT_EQ(GET_INT_FROM_POINTER(v), w[0]); w = c + 1; } } @@ -195,7 +195,7 @@ static void int_ghash_tests(GHash *ghash, const char *id, const unsigned int nbr while (i--) { void *v = BLI_ghash_lookup(ghash, SET_UINT_IN_POINTER(i)); - EXPECT_EQ(i, GET_UINT_FROM_POINTER(v)); + EXPECT_EQ(GET_UINT_FROM_POINTER(v), i); } TIMEIT_END(int_lookup); @@ -214,7 +214,7 @@ static void int_ghash_tests(GHash *ghash, const char *id, const unsigned int nbr TIMEIT_END(int_pop); } - EXPECT_EQ(0, BLI_ghash_size(ghash)); + EXPECT_EQ(BLI_ghash_size(ghash), 0); BLI_ghash_free(ghash, NULL, NULL); @@ -292,7 +292,7 @@ static void randint_ghash_tests(GHash *ghash, const char *id, const unsigned int for (i = nbr, dt = data; i--; dt++) { void *v = BLI_ghash_lookup(ghash, SET_UINT_IN_POINTER(*dt)); - EXPECT_EQ(*dt, GET_UINT_FROM_POINTER(v)); + EXPECT_EQ(GET_UINT_FROM_POINTER(v), *dt); } TIMEIT_END(int_lookup); @@ -403,7 +403,7 @@ static void int4_ghash_tests(GHash *ghash, const char *id, const unsigned int nb for (i = nbr, dt = data; i--; dt++) { void *v = BLI_ghash_lookup(ghash, (void *)(*dt)); - EXPECT_EQ(i, GET_UINT_FROM_POINTER(v)); + EXPECT_EQ(GET_UINT_FROM_POINTER(v), i); } TIMEIT_END(int_v4_lookup); @@ -469,7 +469,7 @@ static void multi_small_ghash_tests_one(GHash *ghash, RNG *rng, const unsigned i for (i = nbr, dt = data; i--; dt++) { void *v = BLI_ghash_lookup(ghash, SET_UINT_IN_POINTER(*dt)); - EXPECT_EQ(*dt, GET_UINT_FROM_POINTER(v)); + EXPECT_EQ(GET_UINT_FROM_POINTER(v), *dt); } BLI_ghash_clear(ghash, NULL, NULL); diff --git a/tests/gtests/blenlib/BLI_ghash_test.cc b/tests/gtests/blenlib/BLI_ghash_test.cc index ffbe5f5547f..6d075e29114 100644 --- a/tests/gtests/blenlib/BLI_ghash_test.cc +++ b/tests/gtests/blenlib/BLI_ghash_test.cc @@ -62,11 +62,11 @@ TEST(ghash, InsertLookup) BLI_ghash_insert(ghash, SET_UINT_IN_POINTER(*k), SET_UINT_IN_POINTER(*k)); } - EXPECT_EQ(TESTCASE_SIZE, BLI_ghash_size(ghash)); + EXPECT_EQ(BLI_ghash_size(ghash), TESTCASE_SIZE); for (i = TESTCASE_SIZE, k = keys; i--; k++) { void *v = BLI_ghash_lookup(ghash, SET_UINT_IN_POINTER(*k)); - EXPECT_EQ(*k, GET_UINT_FROM_POINTER(v)); + EXPECT_EQ(GET_UINT_FROM_POINTER(v), *k); } BLI_ghash_free(ghash, NULL, NULL); @@ -85,16 +85,16 @@ TEST(ghash, InsertRemove) BLI_ghash_insert(ghash, SET_UINT_IN_POINTER(*k), SET_UINT_IN_POINTER(*k)); } - EXPECT_EQ(TESTCASE_SIZE, BLI_ghash_size(ghash)); + EXPECT_EQ(BLI_ghash_size(ghash), TESTCASE_SIZE); bkt_size = BLI_ghash_buckets_size(ghash); for (i = TESTCASE_SIZE, k = keys; i--; k++) { void *v = BLI_ghash_popkey(ghash, SET_UINT_IN_POINTER(*k), NULL); - EXPECT_EQ(*k, GET_UINT_FROM_POINTER(v)); + EXPECT_EQ(GET_UINT_FROM_POINTER(v), *k); } - EXPECT_EQ(0, BLI_ghash_size(ghash)); - EXPECT_EQ(bkt_size, BLI_ghash_buckets_size(ghash)); + EXPECT_EQ(BLI_ghash_size(ghash), 0); + EXPECT_EQ(BLI_ghash_buckets_size(ghash), bkt_size); BLI_ghash_free(ghash, NULL, NULL); } @@ -113,15 +113,15 @@ TEST(ghash, InsertRemoveShrink) BLI_ghash_insert(ghash, SET_UINT_IN_POINTER(*k), SET_UINT_IN_POINTER(*k)); } - EXPECT_EQ(TESTCASE_SIZE, BLI_ghash_size(ghash)); + EXPECT_EQ(BLI_ghash_size(ghash), TESTCASE_SIZE); bkt_size = BLI_ghash_buckets_size(ghash); for (i = TESTCASE_SIZE, k = keys; i--; k++) { void *v = BLI_ghash_popkey(ghash, SET_UINT_IN_POINTER(*k), NULL); - EXPECT_EQ(*k, GET_UINT_FROM_POINTER(v)); + EXPECT_EQ(GET_UINT_FROM_POINTER(v), *k); } - EXPECT_EQ(0, BLI_ghash_size(ghash)); + EXPECT_EQ(BLI_ghash_size(ghash), 0); EXPECT_LT(BLI_ghash_buckets_size(ghash), bkt_size); BLI_ghash_free(ghash, NULL, NULL); @@ -141,16 +141,16 @@ TEST(ghash, Copy) BLI_ghash_insert(ghash, SET_UINT_IN_POINTER(*k), SET_UINT_IN_POINTER(*k)); } - EXPECT_EQ(TESTCASE_SIZE, BLI_ghash_size(ghash)); + EXPECT_EQ(BLI_ghash_size(ghash), TESTCASE_SIZE); ghash_copy = BLI_ghash_copy(ghash, NULL, NULL); - EXPECT_EQ(TESTCASE_SIZE, BLI_ghash_size(ghash_copy)); - EXPECT_EQ(BLI_ghash_buckets_size(ghash), BLI_ghash_buckets_size(ghash_copy)); + EXPECT_EQ(BLI_ghash_size(ghash_copy), TESTCASE_SIZE); + EXPECT_EQ(BLI_ghash_buckets_size(ghash_copy), BLI_ghash_buckets_size(ghash)); for (i = TESTCASE_SIZE, k = keys; i--; k++) { void *v = BLI_ghash_lookup(ghash_copy, SET_UINT_IN_POINTER(*k)); - EXPECT_EQ(*k, GET_UINT_FROM_POINTER(v)); + EXPECT_EQ(GET_UINT_FROM_POINTER(v), *k); } BLI_ghash_free(ghash, NULL, NULL); @@ -171,7 +171,7 @@ TEST(ghash, Pop) BLI_ghash_insert(ghash, SET_UINT_IN_POINTER(*k), SET_UINT_IN_POINTER(*k)); } - EXPECT_EQ(TESTCASE_SIZE, BLI_ghash_size(ghash)); + EXPECT_EQ(BLI_ghash_size(ghash), TESTCASE_SIZE); GHashIterState pop_state = {0}; @@ -179,14 +179,14 @@ TEST(ghash, Pop) void *k, *v; bool success = BLI_ghash_pop(ghash, &pop_state, &k, &v); EXPECT_EQ(k, v); - EXPECT_EQ(success, true); + EXPECT_TRUE(success); if (i % 2) { BLI_ghash_insert(ghash, SET_UINT_IN_POINTER(i * 4), SET_UINT_IN_POINTER(i * 4)); } } - EXPECT_EQ((TESTCASE_SIZE - TESTCASE_SIZE / 2 + TESTCASE_SIZE / 4), BLI_ghash_size(ghash)); + EXPECT_EQ(BLI_ghash_size(ghash), (TESTCASE_SIZE - TESTCASE_SIZE / 2 + TESTCASE_SIZE / 4)); { void *k, *v; @@ -194,7 +194,7 @@ TEST(ghash, Pop) EXPECT_EQ(k, v); } } - EXPECT_EQ(0, BLI_ghash_size(ghash)); + EXPECT_EQ(BLI_ghash_size(ghash), 0); BLI_ghash_free(ghash, NULL, NULL); } diff --git a/tests/gtests/blenlib/BLI_hash_mm2a_test.cc b/tests/gtests/blenlib/BLI_hash_mm2a_test.cc index b35a1a809d6..109c925af4c 100644 --- a/tests/gtests/blenlib/BLI_hash_mm2a_test.cc +++ b/tests/gtests/blenlib/BLI_hash_mm2a_test.cc @@ -19,9 +19,9 @@ TEST(hash_mm2a, MM2ABasic) BLI_hash_mm2a_init(&mm2, 0); BLI_hash_mm2a_add(&mm2, (const unsigned char *)data, strlen(data)); #ifdef __LITTLE_ENDIAN__ - EXPECT_EQ(1633988145, BLI_hash_mm2a_end(&mm2)); + EXPECT_EQ(BLI_hash_mm2a_end(&mm2), 1633988145); #else - EXPECT_EQ(959283772, BLI_hash_mm2a_end(&mm2)); + EXPECT_EQ(BLI_hash_mm2a_end(&mm2), 959283772); #endif } @@ -43,11 +43,11 @@ TEST(hash_mm2a, MM2AConcatenateStrings) BLI_hash_mm2a_init(&mm2, 0); BLI_hash_mm2a_add(&mm2, (const unsigned char *)data123, strlen(data123)); #ifdef __LITTLE_ENDIAN__ - EXPECT_EQ(1545105348, hash); + EXPECT_EQ(hash, 1545105348); #else - EXPECT_EQ(2604964730, hash); + EXPECT_EQ(hash, 2604964730); #endif - EXPECT_EQ(hash, BLI_hash_mm2a_end(&mm2)); + EXPECT_EQ(BLI_hash_mm2a_end(&mm2), hash); } TEST(hash_mm2a, MM2AIntegers) @@ -67,9 +67,9 @@ TEST(hash_mm2a, MM2AIntegers) 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(405493096, hash); + EXPECT_EQ(hash, 405493096); #else - EXPECT_EQ(405493096, hash); + EXPECT_EQ(hash, 405493096); #endif - EXPECT_EQ(hash, BLI_hash_mm2a_end(&mm2)); + EXPECT_EQ(BLI_hash_mm2a_end(&mm2), hash); } diff --git a/tests/gtests/blenlib/BLI_kdopbvh_test.cc b/tests/gtests/blenlib/BLI_kdopbvh_test.cc new file mode 100644 index 00000000000..74db7cf20a0 --- /dev/null +++ b/tests/gtests/blenlib/BLI_kdopbvh_test.cc @@ -0,0 +1,97 @@ +/* Apache License, Version 2.0 */ + +#include "testing/testing.h" + +/* TODO: ray intersection, overlap ... etc.*/ + +extern "C" { +#include "BLI_compiler_attrs.h" +#include "BLI_kdopbvh.h" +#include "BLI_rand.h" +#include "BLI_math_vector.h" +#include "MEM_guardedalloc.h" +} + +#include "stubs/bf_intern_eigen_stubs.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_size(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_size(tree), 1); + + BLI_bvhtree_balance(tree); + BLI_bvhtree_free(tree); +} + +/** + * 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) +{ + 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 */ + for (int i = 0; i < points_len; i++) { + const int j = BLI_bvhtree_find_nearest(tree, points[i], NULL, NULL, NULL); + 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); } diff --git a/tests/gtests/blenlib/BLI_listbase_test.cc b/tests/gtests/blenlib/BLI_listbase_test.cc index 994b8f74541..4dac2d05bd8 100644 --- a/tests/gtests/blenlib/BLI_listbase_test.cc +++ b/tests/gtests/blenlib/BLI_listbase_test.cc @@ -74,25 +74,25 @@ TEST(listbase, FindLinkOrIndex) /* Empty list */ BLI_listbase_clear(&lb); - EXPECT_EQ(NULL, BLI_findlink(&lb, -1)); - EXPECT_EQ(NULL, BLI_findlink(&lb, 0)); - EXPECT_EQ(NULL, BLI_findlink(&lb, 1)); - EXPECT_EQ(NULL, BLI_rfindlink(&lb, -1)); - EXPECT_EQ(NULL, BLI_rfindlink(&lb, 0)); - EXPECT_EQ(NULL, BLI_rfindlink(&lb, 1)); - EXPECT_EQ(-1, BLI_findindex(&lb, link1)); + 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(link1, BLI_findlink(&lb, 0)); - EXPECT_EQ(link1, BLI_rfindlink(&lb, 0)); - EXPECT_EQ(0, BLI_findindex(&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(link2, BLI_findlink(&lb, 1)); - EXPECT_EQ(link2, BLI_rfindlink(&lb, 0)); - EXPECT_EQ(1, BLI_findindex(&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); } diff --git a/tests/gtests/blenlib/BLI_math_geom_test.cc b/tests/gtests/blenlib/BLI_math_geom_test.cc index cd15a4eb8ff..92e2532392e 100644 --- a/tests/gtests/blenlib/BLI_math_geom_test.cc +++ b/tests/gtests/blenlib/BLI_math_geom_test.cc @@ -4,6 +4,8 @@ #include "BLI_math.h" +#include "stubs/bf_intern_eigen_stubs.h" + TEST(math_geom, DistToLine2DSimple) { float p[2] = {5.0f, 1.0f}, diff --git a/tests/gtests/blenlib/BLI_path_util_test.cc b/tests/gtests/blenlib/BLI_path_util_test.cc index c80987c3586..41fad661ea9 100644 --- a/tests/gtests/blenlib/BLI_path_util_test.cc +++ b/tests/gtests/blenlib/BLI_path_util_test.cc @@ -5,6 +5,7 @@ extern "C" { #include "BLI_fileops.h" #include "BLI_path_util.h" +#include "BLI_string.h" #include "../../../source/blender/imbuf/IMB_imbuf.h" #ifdef _WIN32 @@ -40,7 +41,7 @@ const char *GHOST_getSystemDir(int version, const char *versionstr) struct ImBuf; void IMB_freeImBuf(struct ImBuf *ibuf) {} -struct ImBuf *IMB_dupImBuf(struct ImBuf *ibuf) {return NULL;} +struct ImBuf *IMB_dupImBuf(const ImBuf *ibuf) {return NULL;} #ifdef __linux__ char *zLhm65070058860608_br_find_exe(const char *default_exe) @@ -57,7 +58,7 @@ char *zLhm65070058860608_br_find_exe(const char *default_exe) /* BLI_cleanup_path */ #ifndef _WIN32 -TEST(path_util, PathUtilClean) +TEST(path_util, Clean) { /* "/./" -> "/" */ { @@ -113,50 +114,280 @@ TEST(path_util, PathUtilClean) } #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_EQ(ret, false); \ + } \ + else { \ + EXPECT_EQ(ret, true); \ + 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, PathUtilFrame) +TEST(path_util, Frame) { bool ret; { char path[FILE_MAX] = ""; ret = BLI_path_frame(path, 123, 1); - EXPECT_EQ(1, ret); + EXPECT_EQ(ret, 1); EXPECT_STREQ("123", path); } { char path[FILE_MAX] = ""; ret = BLI_path_frame(path, 123, 12); - EXPECT_EQ(1, ret); + EXPECT_EQ(ret, 1); EXPECT_STREQ("000000000123", path); } { char path[FILE_MAX] = "test_"; ret = BLI_path_frame(path, 123, 1); - EXPECT_EQ(1, ret); + EXPECT_EQ(ret, 1); EXPECT_STREQ("test_123", path); } { char path[FILE_MAX] = "test_"; ret = BLI_path_frame(path, 1, 12); - EXPECT_EQ(1, ret); + EXPECT_EQ(ret, 1); EXPECT_STREQ("test_000000000001", path); } { char path[FILE_MAX] = "test_############"; ret = BLI_path_frame(path, 1, 0); - EXPECT_EQ(1, ret); + EXPECT_EQ(ret, 1); EXPECT_STREQ("test_000000000001", path); } { char path[FILE_MAX] = "test_#_#_middle"; ret = BLI_path_frame(path, 123, 0); - EXPECT_EQ(1, ret); + EXPECT_EQ(ret, 1); EXPECT_STREQ("test_#_123_middle", path); } @@ -164,20 +395,20 @@ TEST(path_util, PathUtilFrame) { char path[FILE_MAX] = ""; ret = BLI_path_frame(path, 123, 0); - EXPECT_EQ(0, ret); + EXPECT_EQ(ret, 0); EXPECT_STREQ("", path); } { char path[FILE_MAX] = "test_middle"; ret = BLI_path_frame(path, 123, 0); - EXPECT_EQ(0, ret); + EXPECT_EQ(ret, 0); EXPECT_STREQ("test_middle", path); } } /* BLI_split_dirfile */ -TEST(path_util, PathUtilSplitDirfile) +TEST(path_util, SplitDirfile) { { const char *path = ""; diff --git a/tests/gtests/blenlib/BLI_polyfill2d_test.cc b/tests/gtests/blenlib/BLI_polyfill2d_test.cc index a4ed70fbec9..df98ead4cb9 100644 --- a/tests/gtests/blenlib/BLI_polyfill2d_test.cc +++ b/tests/gtests/blenlib/BLI_polyfill2d_test.cc @@ -27,6 +27,8 @@ extern "C" { #endif } +#include "stubs/bf_intern_eigen_stubs.h" + static void polyfill_to_obj( const char *id, const float poly[][2], const unsigned int poly_tot, @@ -98,14 +100,14 @@ static void test_polyfill_topology( } } } - EXPECT_EQ(poly_tot + (poly_tot - 3), BLI_edgehash_size(edgehash)); + EXPECT_EQ(BLI_edgehash_size(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_EQ(1, (void *)p != NULL); - EXPECT_EQ(1, (intptr_t)*p); + EXPECT_EQ((void *)p != NULL, 1); + EXPECT_EQ((intptr_t)*p, 1); } for (ehi = BLI_edgehashIterator_new(edgehash), i = 0; @@ -113,7 +115,7 @@ static void test_polyfill_topology( BLI_edgehashIterator_step(ehi), i++) { void **p = BLI_edgehashIterator_getValue_p(ehi); - EXPECT_EQ(true, ELEM((intptr_t)*p, 1, 2)); + EXPECT_TRUE(ELEM((intptr_t)*p, 1, 2)); } BLI_edgehashIterator_free(ehi); @@ -135,7 +137,7 @@ static void test_polyfill_winding( count[winding_test < 0.0f] += 1; } } - EXPECT_EQ(true, ELEM(0, count[0], count[1])); + EXPECT_TRUE(ELEM(0, count[0], count[1])); } /** @@ -208,6 +210,26 @@ static void test_polyfill_template( #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, @@ -230,7 +252,7 @@ static void test_polyfill_template_main( 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(id, is_degenerate, poly, poly_tot, tris, tris_tot); + test_polyfill_template_flip_sign(id, is_degenerate, poly, poly_tot, tris, tris_tot); /* cycle */ copy_v2_v2(tmp, poly_copy[0]); @@ -247,7 +269,7 @@ static void test_polyfill_template_main( const float poly[][2], const unsigned int poly_tot, unsigned int tris[][3], const unsigned int tris_tot) { - test_polyfill_template(id, is_degenerate, poly, poly_tot, tris, tris_tot); + test_polyfill_template_flip_sign(id, is_degenerate, poly, poly_tot, tris, tris_tot); } #endif /* USE_COMBINATIONS_ALL */ @@ -307,6 +329,43 @@ static void polyfill_to_obj( /* -------------------------------------------------------------------- */ /* 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) @@ -517,3 +576,17 @@ TEST(polyfill2d, IssueT41986_axis_align) 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); +} diff --git a/tests/gtests/blenlib/BLI_stack_test.cc b/tests/gtests/blenlib/BLI_stack_test.cc index 4c0b95f4b6b..18188937355 100644 --- a/tests/gtests/blenlib/BLI_stack_test.cc +++ b/tests/gtests/blenlib/BLI_stack_test.cc @@ -24,7 +24,7 @@ TEST(stack, Empty) BLI_Stack *stack; stack = BLI_stack_new(sizeof(int), __func__); - EXPECT_EQ(BLI_stack_is_empty(stack), true); + EXPECT_TRUE(BLI_stack_is_empty(stack)); EXPECT_EQ(BLI_stack_count(stack), 0); BLI_stack_free(stack); } @@ -37,11 +37,11 @@ TEST(stack, One) stack = BLI_stack_new(sizeof(in), __func__); BLI_stack_push(stack, (void *)&in); - EXPECT_EQ(BLI_stack_is_empty(stack), false); + EXPECT_FALSE(BLI_stack_is_empty(stack)); EXPECT_EQ(BLI_stack_count(stack), 1); BLI_stack_pop(stack, (void *)&out); - EXPECT_EQ(in, out); - EXPECT_EQ(BLI_stack_is_empty(stack), true); + EXPECT_EQ(out, in); + EXPECT_TRUE(BLI_stack_is_empty(stack)); EXPECT_EQ(BLI_stack_count(stack), 0); BLI_stack_free(stack); } @@ -59,12 +59,12 @@ TEST(stack, Range) } for (in = tot - 1; in >= 0; in--) { - EXPECT_EQ(BLI_stack_is_empty(stack), false); + EXPECT_FALSE(BLI_stack_is_empty(stack)); BLI_stack_pop(stack, (void *)&out); - EXPECT_EQ(in, out); + EXPECT_EQ(out, in); } - EXPECT_EQ(BLI_stack_is_empty(stack), true); + EXPECT_TRUE(BLI_stack_is_empty(stack)); BLI_stack_free(stack); } @@ -86,12 +86,12 @@ TEST(stack, String) } for (i = tot - 1; i >= 0; i--) { - EXPECT_EQ(BLI_stack_is_empty(stack), false); + EXPECT_FALSE(BLI_stack_is_empty(stack)); *((int *)in) = i; BLI_stack_pop(stack, (void *)&out); EXPECT_STREQ(in, out); } - EXPECT_EQ(BLI_stack_is_empty(stack), true); + EXPECT_TRUE(BLI_stack_is_empty(stack)); BLI_stack_free(stack); } @@ -115,7 +115,7 @@ TEST(stack, Peek) EXPECT_EQ(*ret, in[i % ARRAY_SIZE(in)]); } - EXPECT_EQ(BLI_stack_is_empty(stack), true); + EXPECT_TRUE(BLI_stack_is_empty(stack)); BLI_stack_free(stack); } @@ -140,7 +140,7 @@ TEST(stack, Clear) } BLI_stack_clear(stack); - EXPECT_EQ(BLI_stack_is_empty(stack), true); + EXPECT_TRUE(BLI_stack_is_empty(stack)); /* and again, this time check its valid */ for (in = 0; in < tot; in++) { @@ -148,12 +148,12 @@ TEST(stack, Clear) } for (in = tot - 1; in >= 0; in--) { - EXPECT_EQ(BLI_stack_is_empty(stack), false); + EXPECT_FALSE(BLI_stack_is_empty(stack)); BLI_stack_pop(stack, (void *)&out); - EXPECT_EQ(in, out); + EXPECT_EQ(out, in); } - EXPECT_EQ(BLI_stack_is_empty(stack), true); + EXPECT_TRUE(BLI_stack_is_empty(stack)); /* without this, we wont test case when mixed free/used */ tot /= 2; @@ -204,10 +204,10 @@ TEST(stack, Reuse) while (!BLI_stack_is_empty(stack)) { i--; BLI_stack_pop(stack, (void *)&sizes_test[i]); - EXPECT_EQ(sizes[i], sizes_test[i]); + EXPECT_EQ(sizes_test[i], sizes[i]); EXPECT_GT(i, -1); } - EXPECT_EQ(i, 0); + EXPECT_EQ(0, i); EXPECT_EQ(memcmp(sizes, sizes_test, sizeof(sizes) - sizeof(int)), 0); diff --git a/tests/gtests/blenlib/BLI_string_test.cc b/tests/gtests/blenlib/BLI_string_test.cc index 17a4b5e82b9..f6f7e17c8ca 100644 --- a/tests/gtests/blenlib/BLI_string_test.cc +++ b/tests/gtests/blenlib/BLI_string_test.cc @@ -44,7 +44,7 @@ TEST(string, StrPartition) /* "mat.e-r_ial" -> "mat", '.', "e-r_ial", 3 */ pre_ln = BLI_str_partition(str, delim, &sep, &suf); - EXPECT_EQ(3, pre_ln); + EXPECT_EQ(pre_ln, 3); EXPECT_EQ(&str[3], sep); EXPECT_STREQ("e-r_ial", suf); } @@ -55,7 +55,7 @@ TEST(string, StrPartition) /* ".mate-rial--" -> "", '.', "mate-rial--", 0 */ pre_ln = BLI_str_partition(str, delim, &sep, &suf); - EXPECT_EQ(0, pre_ln); + EXPECT_EQ(pre_ln, 0); EXPECT_EQ(&str[0], sep); EXPECT_STREQ("mate-rial--", suf); } @@ -65,7 +65,7 @@ TEST(string, StrPartition) /* ".__.--_" -> "", '.', "__.--_", 0 */ pre_ln = BLI_str_partition(str, delim, &sep, &suf); - EXPECT_EQ(0, pre_ln); + EXPECT_EQ(pre_ln, 0); EXPECT_EQ(&str[0], sep); EXPECT_STREQ("__.--_", suf); } @@ -75,9 +75,9 @@ TEST(string, StrPartition) /* "" -> "", NULL, NULL, 0 */ pre_ln = BLI_str_partition(str, delim, &sep, &suf); - EXPECT_EQ(0, pre_ln); - EXPECT_EQ(NULL, sep); - EXPECT_EQ(NULL, suf); + EXPECT_EQ(pre_ln, 0); + EXPECT_EQ(sep, (void*)NULL); + EXPECT_EQ(suf, (void*)NULL); } { @@ -85,9 +85,9 @@ TEST(string, StrPartition) /* "material" -> "material", NULL, NULL, 8 */ pre_ln = BLI_str_partition(str, delim, &sep, &suf); - EXPECT_EQ(8, pre_ln); - EXPECT_EQ(NULL, sep); - EXPECT_EQ(NULL, suf); + EXPECT_EQ(pre_ln, 8); + EXPECT_EQ(sep, (void*)NULL); + EXPECT_EQ(suf, (void*)NULL); } } @@ -103,7 +103,7 @@ TEST(string, StrRPartition) /* "mat.e-r_ial" -> "mat.e-r", '_', "ial", 7 */ pre_ln = BLI_str_rpartition(str, delim, &sep, &suf); - EXPECT_EQ(7, pre_ln); + EXPECT_EQ(pre_ln, 7); EXPECT_EQ(&str[7], sep); EXPECT_STREQ("ial", suf); } @@ -114,7 +114,7 @@ TEST(string, StrRPartition) /* ".mate-rial--" -> ".mate-rial-", '-', "", 11 */ pre_ln = BLI_str_rpartition(str, delim, &sep, &suf); - EXPECT_EQ(11, pre_ln); + EXPECT_EQ(pre_ln, 11); EXPECT_EQ(&str[11], sep); EXPECT_STREQ("", suf); } @@ -124,7 +124,7 @@ TEST(string, StrRPartition) /* ".__.--_" -> ".__.--", '_', "", 6 */ pre_ln = BLI_str_rpartition(str, delim, &sep, &suf); - EXPECT_EQ(6, pre_ln); + EXPECT_EQ(pre_ln, 6); EXPECT_EQ(&str[6], sep); EXPECT_STREQ("", suf); } @@ -134,9 +134,9 @@ TEST(string, StrRPartition) /* "" -> "", NULL, NULL, 0 */ pre_ln = BLI_str_rpartition(str, delim, &sep, &suf); - EXPECT_EQ(0, pre_ln); - EXPECT_EQ(NULL, sep); - EXPECT_EQ(NULL, suf); + EXPECT_EQ(pre_ln, 0); + EXPECT_EQ(sep, (void*)NULL); + EXPECT_EQ(suf, (void*)NULL); } { @@ -144,9 +144,9 @@ TEST(string, StrRPartition) /* "material" -> "material", NULL, NULL, 8 */ pre_ln = BLI_str_rpartition(str, delim, &sep, &suf); - EXPECT_EQ(8, pre_ln); - EXPECT_EQ(NULL, sep); - EXPECT_EQ(NULL, suf); + EXPECT_EQ(pre_ln, 8); + EXPECT_EQ(sep, (void*)NULL); + EXPECT_EQ(suf, (void*)NULL); } } @@ -164,7 +164,7 @@ TEST(string, StrPartitionEx) /* "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(5, pre_ln); + EXPECT_EQ(pre_ln, 5); EXPECT_EQ(&str[5], sep); EXPECT_STREQ("r_ia.l", suf); } @@ -175,9 +175,9 @@ TEST(string, StrPartitionEx) /* "mate.rial" over "mate" -> "mate.rial", NULL, NULL, 4 */ pre_ln = BLI_str_partition_ex(str, str + 4, delim, &sep, &suf, true); - EXPECT_EQ(4, pre_ln); - EXPECT_EQ(NULL, sep); - EXPECT_EQ(NULL, suf); + EXPECT_EQ(pre_ln, 4); + EXPECT_EQ(sep, (void*)NULL); + EXPECT_EQ(suf, (void*)NULL); } } @@ -193,7 +193,7 @@ TEST(string, StrPartitionUtf8) /* "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(2, pre_ln); + EXPECT_EQ(pre_ln, 2); EXPECT_EQ(&str[2], sep); EXPECT_STREQ("te-r\xe2\x98\xafial", suf); } @@ -204,7 +204,7 @@ TEST(string, StrPartitionUtf8) /* "\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(0, pre_ln); + EXPECT_EQ(pre_ln, 0); EXPECT_EQ(&str[0], sep); EXPECT_STREQ("mate-rial-\xc3\xb1", suf); } @@ -214,7 +214,7 @@ TEST(string, StrPartitionUtf8) /* "\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(0, pre_ln); + EXPECT_EQ(pre_ln, 0); EXPECT_EQ(&str[0], sep); EXPECT_STREQ(".\xc3\xb1_.--\xc3\xb1", suf); } @@ -224,9 +224,9 @@ TEST(string, StrPartitionUtf8) /* "" -> "", NULL, NULL, 0 */ pre_ln = BLI_str_partition_utf8(str, delim, &sep, &suf); - EXPECT_EQ(0, pre_ln); - EXPECT_EQ(NULL, sep); - EXPECT_EQ(NULL, suf); + EXPECT_EQ(pre_ln, 0); + EXPECT_EQ(sep, (void*)NULL); + EXPECT_EQ(suf, (void*)NULL); } { @@ -234,9 +234,9 @@ TEST(string, StrPartitionUtf8) /* "material" -> "material", NULL, NULL, 8 */ pre_ln = BLI_str_partition_utf8(str, delim, &sep, &suf); - EXPECT_EQ(8, pre_ln); - EXPECT_EQ(NULL, sep); - EXPECT_EQ(NULL, suf); + EXPECT_EQ(pre_ln, 8); + EXPECT_EQ(sep, (void*)NULL); + EXPECT_EQ(suf, (void*)NULL); } } @@ -252,7 +252,7 @@ TEST(string, StrRPartitionUtf8) /* "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(8, pre_ln); + EXPECT_EQ(pre_ln, 8); EXPECT_EQ(&str[8], sep); EXPECT_STREQ("ial", suf); } @@ -263,7 +263,7 @@ TEST(string, StrRPartitionUtf8) /* "\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(13, pre_ln); + EXPECT_EQ(pre_ln, 13); EXPECT_EQ(&str[13], sep); EXPECT_STREQ("", suf); } @@ -273,7 +273,7 @@ TEST(string, StrRPartitionUtf8) /* "\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(10, pre_ln); + EXPECT_EQ(pre_ln, 10); EXPECT_EQ(&str[10], sep); EXPECT_STREQ("", suf); } @@ -283,9 +283,9 @@ TEST(string, StrRPartitionUtf8) /* "" -> "", NULL, NULL, 0 */ pre_ln = BLI_str_rpartition_utf8(str, delim, &sep, &suf); - EXPECT_EQ(0, pre_ln); - EXPECT_EQ(NULL, sep); - EXPECT_EQ(NULL, suf); + EXPECT_EQ(pre_ln, 0); + EXPECT_EQ(sep, (void*)NULL); + EXPECT_EQ(suf, (void*)NULL); } { @@ -293,9 +293,9 @@ TEST(string, StrRPartitionUtf8) /* "material" -> "material", NULL, NULL, 8 */ pre_ln = BLI_str_rpartition_utf8(str, delim, &sep, &suf); - EXPECT_EQ(8, pre_ln); - EXPECT_EQ(NULL, sep); - EXPECT_EQ(NULL, suf); + EXPECT_EQ(pre_ln, 8); + EXPECT_EQ(sep, (void*)NULL); + EXPECT_EQ(suf, (void*)NULL); } } @@ -313,7 +313,7 @@ TEST(string, StrPartitionExUtf8) /* "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(2, pre_ln); + EXPECT_EQ(pre_ln, 2); EXPECT_EQ(&str[2], sep); EXPECT_STREQ("te-r\xe2\x98\xafial", suf); } @@ -324,9 +324,9 @@ TEST(string, StrPartitionExUtf8) /* "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(4, pre_ln); - EXPECT_EQ(NULL, sep); - EXPECT_EQ(NULL, suf); + EXPECT_EQ(pre_ln, 4); + EXPECT_EQ(sep, (void*)NULL); + EXPECT_EQ(suf, (void*)NULL); } } @@ -373,8 +373,8 @@ TEST(string, StrFormatIntGrouped) const int word_cmp_size = ARRAY_SIZE(word_cmp); \ const int word_num = BLI_string_find_split_words( \ word_str_src, word_str_src_len, ' ', word_info, word_cmp_size_input); \ - EXPECT_EQ(word_num, word_cmp_size - 1); \ - EXPECT_EQ_ARRAY_ND(word_cmp, word_info, word_cmp_size, 2); \ + EXPECT_EQ(word_cmp_size - 1, word_num); \ + EXPECT_EQ_ARRAY_ND<const int[2]>(word_cmp, word_info, word_cmp_size, 2); \ } ((void)0) #define STRING_FIND_SPLIT_WORDS(word_str_src, ...) \ @@ -449,20 +449,20 @@ TEST(string, StringStrncasestr) const char *res; res = BLI_strncasestr(str_test0, "", 0); - EXPECT_EQ(str_test0, res); + EXPECT_EQ(res, str_test0); res = BLI_strncasestr(str_test0, " ", 1); - EXPECT_EQ(str_test0 + 6, res); + EXPECT_EQ(res, str_test0 + 6); res = BLI_strncasestr(str_test0, "her", 3); - EXPECT_EQ(str_test0 + 7, res); + EXPECT_EQ(res, str_test0 + 7); res = BLI_strncasestr(str_test0, "ARCh", 4); - EXPECT_EQ(str_test0 + 2, res); + EXPECT_EQ(res, str_test0 + 2); res = BLI_strncasestr(str_test0, "earcq", 4); - EXPECT_EQ(str_test0 + 1, res); + EXPECT_EQ(res, str_test0 + 1); res = BLI_strncasestr(str_test0, "not there", 9); - EXPECT_EQ(NULL, res); + EXPECT_EQ(res, (void*)NULL); } diff --git a/tests/gtests/blenlib/BLI_string_utf8_test.cc b/tests/gtests/blenlib/BLI_string_utf8_test.cc new file mode 100644 index 00000000000..95d73b4242f --- /dev/null +++ b/tests/gtests/blenlib/BLI_string_utf8_test.cc @@ -0,0 +1,304 @@ +/* Apache License, Version 2.0 */ + +#include "testing/testing.h" + +extern "C" { +#include "BLI_utildefines.h" +#include "BLI_string.h" +#include "BLI_string_utf8.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. */ + +/* -------------------------------------------------------------------- */ +/* stubs */ + +extern "C" { + +int mk_wcwidth(wchar_t ucs); +int mk_wcswidth(const wchar_t *pwcs, size_t n); + +int mk_wcwidth(wchar_t ucs) +{ + return 0; +} + +int mk_wcswidth(const wchar_t *pwcs, size_t n) +{ + return 0; +} + +} + +/* -------------------------------------------------------------------- */ +/* tests */ + +/* Each test is made of a 79 bytes (80 with NULL char) string to test, expected string result after + * stripping invalid utf8 bytes, and a single-byte string encoded with expected number of errors. + * + * Based on utf-8 decoder stress-test (https://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt) + * by Markus Kuhn <http://www.cl.cam.ac.uk/~mgk25/> - 2015-08-28 - CC BY 4.0 + */ +const char *utf8_invalid_tests[][3] = { +// 1 Some correct UTF-8 text + {"You should see the Greek word 'kosme': \"\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\" |", + "You should see the Greek word 'kosme': \"\xce\xba\xe1\xbd\xb9\xcf\x83\xce\xbc\xce\xb5\" |", "\x00"}, + +// 2 Boundary condition test cases +// Note that those will pass for us, those are not erronéous unicode code points +// (asside from \x00, which is only valid as string terminator). +// 2.1 First possible sequence of a certain length + {"2.1.1 1 byte (U-00000000): \"\x00\" |", + "2.1.1 1 byte (U-00000000): \"\" |", "\x01"}, + {"2.1.2 2 bytes (U-00000080): \"\xc2\x80\" |", + "2.1.2 2 bytes (U-00000080): \"\xc2\x80\" |", "\x00"}, + {"2.1.3 3 bytes (U-00000800): \"\xe0\xa0\x80\" |", + "2.1.3 3 bytes (U-00000800): \"\xe0\xa0\x80\" |", "\x00"}, + {"2.1.4 4 bytes (U-00010000): \"\xf0\x90\x80\x80\" |", + "2.1.4 4 bytes (U-00010000): \"\xf0\x90\x80\x80\" |", "\x00"}, + {"2.1.5 5 bytes (U-00200000): \"\xf8\x88\x80\x80\x80\" |", + "2.1.5 5 bytes (U-00200000): \"\xf8\x88\x80\x80\x80\" |", "\x00"}, + {"2.1.6 6 bytes (U-04000000): \"\xfc\x84\x80\x80\x80\x80\" |", + "2.1.6 6 bytes (U-04000000): \"\xfc\x84\x80\x80\x80\x80\" |", "\x00"}, +// 2.2 Last possible sequence of a certain length + {"2.2.1 1 byte (U-0000007F): \"\x7f\" |", + "2.2.1 1 byte (U-0000007F): \"\x7f\" |", "\x00"}, + {"2.2.2 2 bytes (U-000007FF): \"\xdf\xbf\" |", + "2.2.2 2 bytes (U-000007FF): \"\xdf\xbf\" |", "\x00"}, + {"2.2.3 3 bytes (U-0000FFFF): \"\xef\xbf\xbf\" |", + "2.2.3 3 bytes (U-0000FFFF): \"\" |", "\x03"}, /* matches one of 5.3 sequences... */ + {"2.2.4 4 bytes (U-001FFFFF): \"\xf7\xbf\xbf\xbf\" |", + "2.2.4 4 bytes (U-001FFFFF): \"\xf7\xbf\xbf\xbf\" |", "\x00"}, + {"2.2.5 5 bytes (U-03FFFFFF): \"\xfb\xbf\xbf\xbf\xbf\" |", + "2.2.5 5 bytes (U-03FFFFFF): \"\xfb\xbf\xbf\xbf\xbf\" |", "\x00"}, + {"2.2.6 6 bytes (U-7FFFFFFF): \"\xfd\xbf\xbf\xbf\xbf\xbf\" |", + "2.2.6 6 bytes (U-7FFFFFFF): \"\xfd\xbf\xbf\xbf\xbf\xbf\" |", "\x00"}, +// 2.3 Other boundary conditions + {"2.3.1 U-0000D7FF = ed 9f bf = \"\xed\x9f\xbf\" |", + "2.3.1 U-0000D7FF = ed 9f bf = \"\xed\x9f\xbf\" |", "\x00"}, + {"2.3.2 U-0000E000 = ee 80 80 = \"\xee\x80\x80\" |", + "2.3.2 U-0000E000 = ee 80 80 = \"\xee\x80\x80\" |", "\x00"}, + {"2.3.3 U-0000FFFD = ef bf bd = \"\xef\xbf\xbd\" |", + "2.3.3 U-0000FFFD = ef bf bd = \"\xef\xbf\xbd\" |", "\x00"}, + {"2.3.4 U-0010FFFF = f4 8f bf bf = \"\xf4\x8f\xbf\xbf\" |", + "2.3.4 U-0010FFFF = f4 8f bf bf = \"\xf4\x8f\xbf\xbf\" |", "\x00"}, + {"2.3.5 U-00110000 = f4 90 80 80 = \"\xf4\x90\x80\x80\" |", + "2.3.5 U-00110000 = f4 90 80 80 = \"\xf4\x90\x80\x80\" |", "\x00"}, + +// 3 Malformed sequences +// 3.1 Unexpected continuation bytes +// Each unexpected continuation byte should be separately signalled 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 signalled 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 signalled: + {"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 behaviour 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} +}; + +/* 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/tests/gtests/blenlib/CMakeLists.txt b/tests/gtests/blenlib/CMakeLists.txt index 12112e7a481..ffdb8d08d31 100644 --- a/tests/gtests/blenlib/CMakeLists.txt +++ b/tests/gtests/blenlib/CMakeLists.txt @@ -34,22 +34,27 @@ 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}") +if(WIN32) + set(BLI_path_util_extra_libs "bf_blenlib;bf_intern_utfconv;extern_wcwidth;${ZLIB_LIBRARIES}") +else() + set(BLI_path_util_extra_libs "bf_blenlib;extern_wcwidth;${ZLIB_LIBRARIES}") +endif() BLENDER_TEST(BLI_array_store "bf_blenlib") BLENDER_TEST(BLI_array_utils "bf_blenlib") -BLENDER_TEST(BLI_stack "bf_blenlib") -BLENDER_TEST(BLI_math_color "bf_blenlib") -BLENDER_TEST(BLI_math_geom "bf_blenlib;bf_intern_eigen") +BLENDER_TEST(BLI_ghash "bf_blenlib") +BLENDER_TEST(BLI_hash_mm2a "bf_blenlib") +BLENDER_TEST(BLI_kdopbvh "bf_blenlib") +BLENDER_TEST(BLI_listbase "bf_blenlib") BLENDER_TEST(BLI_math_base "bf_blenlib") +BLENDER_TEST(BLI_math_color "bf_blenlib") +BLENDER_TEST(BLI_math_geom "bf_blenlib") +BLENDER_TEST(BLI_path_util "${BLI_path_util_extra_libs}") +BLENDER_TEST(BLI_polyfill2d "bf_blenlib") +BLENDER_TEST(BLI_stack "bf_blenlib") BLENDER_TEST(BLI_string "bf_blenlib") -if(WIN32) - BLENDER_TEST(BLI_path_util "bf_blenlib;bf_intern_utfconv;extern_wcwidth;${ZLIB_LIBRARIES}") -else() - BLENDER_TEST(BLI_path_util "bf_blenlib;extern_wcwidth;${ZLIB_LIBRARIES}") -endif() -BLENDER_TEST(BLI_polyfill2d "bf_blenlib;bf_intern_eigen") -BLENDER_TEST(BLI_listbase "bf_blenlib") -BLENDER_TEST(BLI_hash_mm2a "bf_blenlib") -BLENDER_TEST(BLI_ghash "bf_blenlib") +BLENDER_TEST(BLI_string_utf8 "bf_blenlib") BLENDER_TEST_PERFORMANCE(BLI_ghash_performance "bf_blenlib") + +unset(BLI_path_util_extra_libs) diff --git a/tests/gtests/blenlib/stubs/bf_intern_eigen_stubs.h b/tests/gtests/blenlib/stubs/bf_intern_eigen_stubs.h new file mode 100644 index 00000000000..dad77a257d5 --- /dev/null +++ b/tests/gtests/blenlib/stubs/bf_intern_eigen_stubs.h @@ -0,0 +1,18 @@ +/* Apache License, Version 2.0 */ + +extern "C" { + +bool EIG_self_adjoint_eigen_solve(const int size, const float *matrix, float *r_eigen_values, float *r_eigen_vectors) +{ + BLI_assert(0); + UNUSED_VARS(size, matrix, r_eigen_values, r_eigen_vectors); + return false; +} + +void EIG_svd_square_matrix(const int size, const float *matrix, float *r_U, float *r_S, float *r_V) +{ + BLI_assert(0); + UNUSED_VARS(size, matrix, r_U, r_S, r_V); +} + +} diff --git a/tests/gtests/bmesh/bmesh_core_test.cc b/tests/gtests/bmesh/bmesh_core_test.cc index f386abc0b2b..b2cb1a4e8a3 100644 --- a/tests/gtests/bmesh/bmesh_core_test.cc +++ b/tests/gtests/bmesh/bmesh_core_test.cc @@ -12,18 +12,18 @@ TEST(bmesh_core, BMVertCreate) { BMeshCreateParams bm_params; bm_params.use_toolflags = true; bm = BM_mesh_create(&bm_mesh_allocsize_default, &bm_params); - EXPECT_EQ(0, bm->totvert); + 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_FLT); bv1 = BM_vert_create(bm, co1, NULL, BM_CREATE_NOP); ASSERT_TRUE(bv1 != NULL); - EXPECT_EQ(1.0f, bv1->co[0]); - EXPECT_EQ(2.0f, bv1->co[1]); - EXPECT_EQ(0.0f, bv1->co[2]); + 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((char)BM_VERT, bv1->head.htype); - EXPECT_EQ(0, bv1->head.hflag); - EXPECT_EQ(0, bv1->head.api_flag); + 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)); @@ -33,7 +33,7 @@ TEST(bmesh_core, BMVertCreate) { 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(1.5f, BM_elem_float_data_get(&bm->vdata, bv3, CD_PROP_FLT)); - EXPECT_EQ(3, BM_mesh_elem_count(bm, BM_VERT)); + EXPECT_EQ(BM_elem_float_data_get(&bm->vdata, bv3, CD_PROP_FLT), 1.5f); + EXPECT_EQ(BM_mesh_elem_count(bm, BM_VERT), 3); BM_mesh_free(bm); } diff --git a/tests/gtests/guardedalloc/guardedalloc_alignment_test.cc b/tests/gtests/guardedalloc/guardedalloc_alignment_test.cc index 345c3824b63..01ff38f0528 100644 --- a/tests/gtests/guardedalloc/guardedalloc_alignment_test.cc +++ b/tests/gtests/guardedalloc/guardedalloc_alignment_test.cc @@ -8,7 +8,7 @@ extern "C" { #include "MEM_guardedalloc.h" -#define CHECK_ALIGNMENT(ptr, align) EXPECT_EQ(0, (size_t)ptr % align) +#define CHECK_ALIGNMENT(ptr, align) EXPECT_EQ((size_t)ptr % align, 0) namespace { diff --git a/tests/gtests/testing/CMakeLists.txt b/tests/gtests/testing/CMakeLists.txt index 1eb60e7f3b5..95ac59e6dce 100644 --- a/tests/gtests/testing/CMakeLists.txt +++ b/tests/gtests/testing/CMakeLists.txt @@ -24,20 +24,11 @@ set(INC . .. - ../../../extern/gflags/src + ${GLOG_INCLUDE_DIRS} + ${GFLAGS_INCLUDE_DIRS} ../../../extern/gtest/include ) -if(WIN32) - list(APPEND INC - ../../../extern/glog/src/windows - ) -else() - list(APPEND INC - ../../../extern/glog/src - ) -endif() - set(INC_SYS ) @@ -47,7 +38,4 @@ set(SRC testing.h ) -add_definitions(${GFLAGS_DEFINES}) -add_definitions(${GLOG_DEFINES}) - blender_add_lib(bf_testing_main "${SRC}" "${INC}" "${INC_SYS}") diff --git a/tests/gtests/testing/testing.h b/tests/gtests/testing/testing.h index 1594ed3926c..d5a7b076970 100644 --- a/tests/gtests/testing/testing.h +++ b/tests/gtests/testing/testing.h @@ -12,6 +12,29 @@ EXPECT_NEAR(a[2], b[2], eps); \ } (void) 0 +#define EXPECT_V4_NEAR(a, b, eps) \ +{ \ + EXPECT_NEAR(a[0], b[0], eps); \ + EXPECT_NEAR(a[1], b[1], eps); \ + EXPECT_NEAR(a[2], b[2], eps); \ + EXPECT_NEAR(a[3], b[3], eps); \ + } (void) 0 + +#define EXPECT_M3_NEAR(a, b, eps) \ +do { \ + EXPECT_V3_NEAR(a[0], b[0], eps); \ + EXPECT_V3_NEAR(a[1], b[1], eps); \ + EXPECT_V3_NEAR(a[2], b[2], eps); \ +} while(false); + +#define EXPECT_M4_NEAR(a, b, eps) \ +do { \ + EXPECT_V3_NEAR(a[0], b[0], eps); \ + EXPECT_V3_NEAR(a[1], b[1], eps); \ + EXPECT_V3_NEAR(a[2], b[2], eps); \ + EXPECT_V4_NEAR(a[3], b[3], eps); \ +} while(false); + #define EXPECT_MATRIX_NEAR(a, b, tolerance) \ do { \ bool dims_match = (a.rows() == b.rows()) && (a.cols() == b.cols()); \ diff --git a/tests/gtests/testing/testing_main.cc b/tests/gtests/testing/testing_main.cc index b2dcc445aca..e8104a2a026 100644 --- a/tests/gtests/testing/testing_main.cc +++ b/tests/gtests/testing/testing_main.cc @@ -28,7 +28,7 @@ int main(int argc, char **argv) { testing::InitGoogleTest(&argc, argv); - gflags::ParseCommandLineFlags(&argc, &argv, true); + BLENDER_GFLAGS_NAMESPACE::ParseCommandLineFlags(&argc, &argv, true); google::InitGoogleLogging(argv[0]); return RUN_ALL_TESTS(); diff --git a/tests/python/CMakeLists.txt b/tests/python/CMakeLists.txt index a29b612e0ef..7e42f36c6e4 100644 --- a/tests/python/CMakeLists.txt +++ b/tests/python/CMakeLists.txt @@ -18,7 +18,7 @@ # # ***** END GPL LICENSE BLOCK ***** -# --env-system-scripts allows to run without the install target. +# --env-system-scripts allows to run without the install target. # Use '--write-blend=/tmp/test.blend' to view output @@ -39,83 +39,132 @@ execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${TEST_OUT_DIR}) # all calls to blender use this if(APPLE) if(${CMAKE_GENERATOR} MATCHES "Xcode") - set(TEST_BLENDER_EXE ${EXECUTABLE_OUTPUT_PATH}/Debug/blender.app/Contents/MacOS/blender) + set(TEST_BLENDER_EXE_PARAMS --background -noaudio --factory-startup) else() - set(TEST_BLENDER_EXE ${EXECUTABLE_OUTPUT_PATH}/blender.app/Contents/MacOS/blender) + set(TEST_BLENDER_EXE_PARAMS --background -noaudio --factory-startup --env-system-scripts ${CMAKE_SOURCE_DIR}/release/scripts) endif() else() - set(TEST_BLENDER_EXE ${EXECUTABLE_OUTPUT_PATH}/blender) + set(TEST_BLENDER_EXE_PARAMS --background -noaudio --factory-startup --env-system-scripts ${CMAKE_SOURCE_DIR}/release/scripts) endif() -# for testing with valgrind prefix: valgrind --track-origins=yes --error-limit=no -set(TEST_BLENDER_EXE_BARE ${TEST_BLENDER_EXE}) -set(TEST_BLENDER_EXE ${TEST_BLENDER_EXE} --background -noaudio --factory-startup --env-system-scripts ${CMAKE_SOURCE_DIR}/release/scripts) +# for testing with valgrind prefix: valgrind --track-origins=yes --error-limit=no +# set(TEST_BLENDER_EXE_BARE ${TEST_BLENDER_EXE}) +# set(TEST_BLENDER_EXE ${TEST_BLENDER_EXE} ${TEST_BLENDER_EXE_PARAMS} ) # ------------------------------------------------------------------------------ # GENERAL PYTHON CORRECTNESS TESTS -add_test(script_load_keymap ${TEST_BLENDER_EXE} +add_test( + NAME script_load_keymap + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_keymap_completeness.py ) -add_test(script_load_addons ${TEST_BLENDER_EXE} +add_test( + NAME script_load_addons + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_load_addons.py ) -add_test(script_load_modules ${TEST_BLENDER_EXE} +add_test( + NAME script_load_modules + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_load_py_modules.py ) # test running operators doesn't segfault under various conditions if(USE_EXPERIMENTAL_TESTS) - add_test(script_run_operators ${TEST_BLENDER_EXE} + add_test( + NAME script_run_operators + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_run_operators.py ) endif() # ------------------------------------------------------------------------------ # PY API TESTS -add_test(script_pyapi_bpy_path ${TEST_BLENDER_EXE} +add_test( + NAME script_pyapi_bpy_path + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_bpy_path.py ) -add_test(script_pyapi_bpy_utils_units ${TEST_BLENDER_EXE} +add_test( + NAME script_pyapi_bpy_utils_units + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_bpy_utils_units.py ) -# test running mathutils testing script -add_test(script_pyapi_mathutils ${TEST_BLENDER_EXE} +add_test( + NAME script_pyapi_mathutils + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_mathutils.py ) +add_test( + NAME script_pyapi_idprop + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} + --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_idprop.py +) + +add_test( + NAME script_pyapi_idprop_datablock + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} + --python ${CMAKE_CURRENT_LIST_DIR}/bl_pyapi_idprop_datablock.py +) + # ------------------------------------------------------------------------------ # MODELING TESTS -add_test(bevel ${TEST_BLENDER_EXE} +add_test( + NAME bevel + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/modeling/bevel_regression.blend --python-text run_tests ) +add_test( + NAME split_faces + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} + ${TEST_SRC_DIR}/modeling/split_faces_test.blend + --python-text run_tests +) + +# ------------------------------------------------------------------------------ +# MODIFIERS TESTS +add_test( + NAME modifier_array + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} + ${TEST_SRC_DIR}/modifier_stack/array_test.blend + --python-text run_tests +) + # ------------------------------------------------------------------------------ # IO TESTS # OBJ Import tests # disabled until updated & working if(FALSE) -add_test(import_obj_cube ${TEST_BLENDER_EXE} +add_test( + NAME import_obj_cube + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_scene.obj\(filepath='${TEST_SRC_DIR}/io_tests/obj/cube.obj'\) --md5=39cce4bacac2d1b18fc470380279bc15 --md5_method=SCENE --write-blend=${TEST_OUT_DIR}/import_obj_cube.blend ) -add_test(import_obj_nurbs_cyclic ${TEST_BLENDER_EXE} +add_test( + NAME import_obj_nurbs_cyclic + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_scene.obj\(filepath='${TEST_SRC_DIR}/io_tests/obj/nurbs_cyclic.obj'\) --md5=ad3c307e5883224a0492378cd32691ab --md5_method=SCENE --write-blend=${TEST_OUT_DIR}/import_obj_nurbs_cyclic.blend ) -add_test(import_obj_makehuman ${TEST_BLENDER_EXE} +add_test( + NAME import_obj_makehuman + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_scene.obj\(filepath='${TEST_SRC_DIR}/io_tests/obj/makehuman.obj'\) --md5=c9f78b185e58358daa4ecaecfa75464e --md5_method=SCENE @@ -124,7 +173,9 @@ add_test(import_obj_makehuman ${TEST_BLENDER_EXE} endif() # OBJ Export tests -add_test(export_obj_cube ${TEST_BLENDER_EXE} +add_test( + NAME export_obj_cube + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/all_quads.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.obj\(filepath='${TEST_OUT_DIR}/export_obj_cube.obj',use_selection=False\) @@ -133,7 +184,9 @@ add_test(export_obj_cube ${TEST_BLENDER_EXE} --md5=e80660437ad9bfe082849641c361a233 --md5_method=FILE ) -add_test(export_obj_nurbs ${TEST_BLENDER_EXE} +add_test( + NAME export_obj_nurbs + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/nurbs.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.obj\(filepath='${TEST_OUT_DIR}/export_obj_nurbs.obj',use_selection=False,use_nurbs=True\) @@ -144,7 +197,9 @@ add_test(export_obj_nurbs ${TEST_BLENDER_EXE} # disabled until updated & working if(FALSE) -add_test(export_obj_all_objects ${TEST_BLENDER_EXE} +add_test( + NAME export_obj_all_objects + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_scene/all_objects.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.obj\(filepath='${TEST_OUT_DIR}/export_obj_all_objects.obj',use_selection=False,use_nurbs=True\) @@ -157,21 +212,27 @@ endif() # PLY Import tests -add_test(import_ply_cube ${TEST_BLENDER_EXE} +add_test( + NAME import_ply_cube + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_mesh.ply\(filepath='${TEST_SRC_DIR}/io_tests/ply/cube_ascii.ply'\) --md5=527134343c27fc0ea73115b85fbfd3ac --md5_method=SCENE --write-blend=${TEST_OUT_DIR}/import_ply_cube.blend ) -add_test(import_ply_bunny ${TEST_BLENDER_EXE} +add_test( + NAME import_ply_bunny + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_mesh.ply\(filepath='${TEST_SRC_DIR}/io_tests/ply/bunny2.ply'\) --md5=6ea5b8533400a17accf928b8fd024eaa --md5_method=SCENE --write-blend=${TEST_OUT_DIR}/import_ply_bunny.blend ) -add_test(import_ply_small_holes ${TEST_BLENDER_EXE} +add_test( + NAME import_ply_small_holes + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_mesh.ply\(filepath='${TEST_SRC_DIR}/io_tests/ply/many_small_holes.ply'\) --md5=c3093e26ecae5b6d59fbbcf2a0d0b39f --md5_method=SCENE @@ -181,7 +242,9 @@ add_test(import_ply_small_holes ${TEST_BLENDER_EXE} # PLY Export # disabled until updated & working if(FALSE) -add_test(export_ply_cube_all_data ${TEST_BLENDER_EXE} +add_test( + NAME export_ply_cube_all_data + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/cube_all_data.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_mesh.ply\(filepath='${TEST_OUT_DIR}/export_ply_cube_all_data.ply'\) @@ -189,7 +252,9 @@ add_test(export_ply_cube_all_data ${TEST_BLENDER_EXE} --md5=6adc3748ceae8298496f99d0e7e76c15 --md5_method=FILE ) -add_test(export_ply_suzanne_all_data ${TEST_BLENDER_EXE} +add_test( + NAME export_ply_suzanne_all_data + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/suzanne_all_data.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_mesh.ply\(filepath='${TEST_OUT_DIR}/export_ply_suzanne_all_data.ply'\) @@ -198,7 +263,9 @@ add_test(export_ply_suzanne_all_data ${TEST_BLENDER_EXE} ) endif() -add_test(export_ply_vertices ${TEST_BLENDER_EXE} # lame, add a better one +add_test( + NAME export_ply_vertices # lame, add a better one + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/vertices.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_mesh.ply\(filepath='${TEST_OUT_DIR}/export_ply_vertices.ply'\) @@ -210,21 +277,27 @@ add_test(export_ply_vertices ${TEST_BLENDER_EXE} # lame, add a better one # STL Import tests # disabled until updated & working if(FALSE) -add_test(import_stl_cube ${TEST_BLENDER_EXE} +add_test( + NAME import_stl_cube + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_mesh.stl\(filepath='${TEST_SRC_DIR}/io_tests/stl/cube.stl'\) --md5=8ceb5bb7e1cb5f4342fa1669988c66b4 --md5_method=SCENE --write-blend=${TEST_OUT_DIR}/import_stl_cube.blend ) -add_test(import_stl_conrod ${TEST_BLENDER_EXE} +add_test( + NAME import_stl_conrod + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_mesh.stl\(filepath='${TEST_SRC_DIR}/io_tests/stl/conrod.stl'\) --md5=690a4b8eb9002dcd8631c5a575ea7348 --md5_method=SCENE --write-blend=${TEST_OUT_DIR}/import_stl_conrod.blend ) -add_test(import_stl_knot_max_simplified ${TEST_BLENDER_EXE} +add_test( + NAME import_stl_knot_max_simplified + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_mesh.stl\(filepath='${TEST_SRC_DIR}/io_tests/stl/knot_max_simplified.stl'\) --md5=baf82803f45a84ec4ddbad9cef57dd3e --md5_method=SCENE @@ -235,7 +308,9 @@ endif() # STL Export # disabled until updated & working if(FALSE) -add_test(export_stl_cube_all_data ${TEST_BLENDER_EXE} +add_test( + NAME export_stl_cube_all_data + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/cube_all_data.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_mesh.stl\(filepath='${TEST_OUT_DIR}/export_stl_cube_all_data.stl'\) @@ -243,7 +318,9 @@ add_test(export_stl_cube_all_data ${TEST_BLENDER_EXE} --md5=64cb97c0cabb015e1c3f76369835075a --md5_method=FILE ) -add_test(export_stl_suzanne_all_data ${TEST_BLENDER_EXE} +add_test( + NAME export_stl_suzanne_all_data + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/suzanne_all_data.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_mesh.stl\(filepath='${TEST_OUT_DIR}/export_stl_suzanne_all_data.stl'\) @@ -251,7 +328,9 @@ add_test(export_stl_suzanne_all_data ${TEST_BLENDER_EXE} --md5=e9b23c97c139ad64961c635105bb9192 --md5_method=FILE ) -add_test(export_stl_vertices ${TEST_BLENDER_EXE} # lame, add a better one +add_test( + NAME export_stl_vertices # lame, add a better one + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/vertices.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_mesh.stl\(filepath='${TEST_OUT_DIR}/export_stl_vertices.stl'\) @@ -264,21 +343,27 @@ endif() # X3D Import # disabled until updated & working if(FALSE) -add_test(import_x3d_cube ${TEST_BLENDER_EXE} +add_test( + NAME import_x3d_cube + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_scene.x3d\(filepath='${TEST_SRC_DIR}/io_tests/x3d/color_cube.x3d'\) --md5=3fae9be004199c145941cd3f9f80ad7b --md5_method=SCENE --write-blend=${TEST_OUT_DIR}/import_x3d_cube.blend ) -add_test(import_x3d_teapot ${TEST_BLENDER_EXE} +add_test( + NAME import_x3d_teapot + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_scene.x3d\(filepath='${TEST_SRC_DIR}/io_tests/x3d/teapot.x3d'\) --md5=8ee196c71947dce4199d55698501691e --md5_method=SCENE --write-blend=${TEST_OUT_DIR}/import_x3d_teapot.blend ) -add_test(import_x3d_suzanne_material ${TEST_BLENDER_EXE} +add_test( + NAME import_x3d_suzanne_material + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_scene.x3d\(filepath='${TEST_SRC_DIR}/io_tests/x3d/suzanne_material.x3d'\) --md5=3edea1353257d8b5a5f071942f417be6 --md5_method=SCENE @@ -286,7 +371,9 @@ add_test(import_x3d_suzanne_material ${TEST_BLENDER_EXE} ) # X3D Export -add_test(export_x3d_cube ${TEST_BLENDER_EXE} +add_test( + NAME export_x3d_cube + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/all_quads.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.x3d\(filepath='${TEST_OUT_DIR}/export_x3d_cube.x3d',use_selection=False\) @@ -294,7 +381,9 @@ add_test(export_x3d_cube ${TEST_BLENDER_EXE} --md5=05312d278fe41da33560fdfb9bdb268f --md5_method=FILE ) -add_test(export_x3d_nurbs ${TEST_BLENDER_EXE} +add_test( + NAME export_x3d_nurbs + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/nurbs.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.x3d\(filepath='${TEST_OUT_DIR}/export_x3d_nurbs.x3d',use_selection=False\) @@ -302,7 +391,9 @@ add_test(export_x3d_nurbs ${TEST_BLENDER_EXE} --md5=4286d4a2aa507ef78b22ddcbdcc88481 --md5_method=FILE ) -add_test(export_x3d_all_objects ${TEST_BLENDER_EXE} +add_test( + NAME export_x3d_all_objects + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_scene/all_objects.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.x3d\(filepath='${TEST_OUT_DIR}/export_x3d_all_objects.x3d',use_selection=False\) @@ -316,21 +407,27 @@ endif() # 3DS Import # disabled until updated & working if(FALSE) -add_test(import_3ds_cube ${TEST_BLENDER_EXE} +add_test( + NAME import_3ds_cube + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_scene.autodesk_3ds\(filepath='${TEST_SRC_DIR}/io_tests/3ds/cube.3ds'\) --md5=cb5a45c35a343c3f5beca2a918472951 --md5_method=SCENE --write-blend=${TEST_OUT_DIR}/import_3ds_cube.blend ) -add_test(import_3ds_hierarchy_lara ${TEST_BLENDER_EXE} +add_test( + NAME import_3ds_hierarchy_lara + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_scene.autodesk_3ds\(filepath='${TEST_SRC_DIR}/io_tests/3ds/hierarchy_lara.3ds'\) --md5=766c873d9fdb5f190e43796cfbae63b6 --md5_method=SCENE --write-blend=${TEST_OUT_DIR}/import_3ds_hierarchy_lara.blend ) -add_test(import_3ds_hierarchy_greek_trireme ${TEST_BLENDER_EXE} +add_test( + NAME import_3ds_hierarchy_greek_trireme + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.import_scene.autodesk_3ds\(filepath='${TEST_SRC_DIR}/io_tests/3ds/hierarchy_greek_trireme.3ds'\) --md5=b62ee30101e8999cb91ef4f8a8760056 --md5_method=SCENE @@ -341,7 +438,9 @@ endif() # 3DS Export # disabled until updated & working if(FALSE) -add_test(export_3ds_cube ${TEST_BLENDER_EXE} +add_test( + NAME export_3ds_cube + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/all_quads.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.autodesk_3ds\(filepath='${TEST_OUT_DIR}/export_3ds_cube.3ds',use_selection=False\) @@ -349,7 +448,9 @@ add_test(export_3ds_cube ${TEST_BLENDER_EXE} --md5=a31f5071b6c6dc7445b9099cdc7f63b3 --md5_method=FILE ) -add_test(export_3ds_nurbs ${TEST_BLENDER_EXE} +add_test( + NAME export_3ds_nurbs + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/nurbs.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.autodesk_3ds\(filepath='${TEST_OUT_DIR}/export_3ds_nurbs.3ds',use_selection=False\) @@ -357,7 +458,9 @@ add_test(export_3ds_nurbs ${TEST_BLENDER_EXE} --md5=5bdd21be3c80d814fbc83cb25edb08c2 --md5_method=FILE ) -add_test(export_3ds_all_objects ${TEST_BLENDER_EXE} +add_test( + NAME export_3ds_all_objects + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_scene/all_objects.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.autodesk_3ds\(filepath='${TEST_OUT_DIR}/export_3ds_all_objects.3ds',use_selection=False\) @@ -372,7 +475,9 @@ endif() # 'use_metadata=False' for reliable md5's # disabled until updated & working if(FALSE) -add_test(export_fbx_cube ${TEST_BLENDER_EXE} +add_test( + NAME export_fbx_cube + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/all_quads.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.fbx\(filepath='${TEST_OUT_DIR}/export_fbx_cube.fbx',use_selection=False,use_metadata=False\) @@ -380,7 +485,9 @@ add_test(export_fbx_cube ${TEST_BLENDER_EXE} --md5=59a35577462f95f9a0b4e6035226ce9b --md5_method=FILE ) -add_test(export_fbx_nurbs ${TEST_BLENDER_EXE} +add_test( + NAME export_fbx_nurbs + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_geometry/nurbs.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.fbx\(filepath='${TEST_OUT_DIR}/export_fbx_nurbs.fbx',use_selection=False,use_metadata=False\) @@ -388,7 +495,9 @@ add_test(export_fbx_nurbs ${TEST_BLENDER_EXE} --md5=d31875f18f613fa0c3b16e978f87f6f8 --md5_method=FILE ) -add_test(export_fbx_all_objects ${TEST_BLENDER_EXE} +add_test( + NAME export_fbx_all_objects + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/io_tests/blend_scene/all_objects.blend --python ${CMAKE_CURRENT_LIST_DIR}/bl_test.py -- --run={'FINISHED'}&bpy.ops.export_scene.fbx\(filepath='${TEST_OUT_DIR}/export_fbx_all_objects.fbx',use_selection=False,use_metadata=False\) @@ -399,25 +508,86 @@ endif() if(WITH_CYCLES) if(OPENIMAGEIO_IDIFF AND EXISTS "${TEST_SRC_DIR}/cycles/ctests/shader") - add_test(cycles_reports_test - ${CMAKE_CURRENT_LIST_DIR}/cycles_render_tests.py - -blender "${TEST_BLENDER_EXE_BARE}" - -testdir "${TEST_SRC_DIR}/cycles/ctests/reports" - -idiff "${OPENIMAGEIO_IDIFF}" - ) - add_test(cycles_render_test - ${CMAKE_CURRENT_LIST_DIR}/cycles_render_tests.py - -blender "${TEST_BLENDER_EXE_BARE}" - -testdir "${TEST_SRC_DIR}/cycles/ctests/render" - -idiff "${OPENIMAGEIO_IDIFF}" - ) - add_test(cycles_shaders_test - ${CMAKE_CURRENT_LIST_DIR}/cycles_render_tests.py - -blender "${TEST_BLENDER_EXE_BARE}" - -testdir "${TEST_SRC_DIR}/cycles/ctests/shader" - -idiff "${OPENIMAGEIO_IDIFF}" - ) + macro(add_cycles_render_test subject) + if(MSVC) + add_test( + NAME cycles_${subject}_test + COMMAND + "$<TARGET_FILE_DIR:blender>/${BLENDER_VERSION_MAJOR}.${BLENDER_VERSION_MINOR}/python/bin/python$<$<CONFIG:Debug>:_d>" + ${CMAKE_CURRENT_LIST_DIR}/cycles_render_tests.py + -blender "$<TARGET_FILE:blender>" + -testdir "${TEST_SRC_DIR}/cycles/ctests/${subject}" + -idiff "${OPENIMAGEIO_IDIFF}" + -outdir "${TEST_OUT_DIR}/cycles" + ) + else() + add_test( + NAME cycles_${subject}_test + COMMAND ${CMAKE_CURRENT_LIST_DIR}/cycles_render_tests.py + -blender "$<TARGET_FILE:blender>" + -testdir "${TEST_SRC_DIR}/cycles/ctests/${subject}" + -idiff "${OPENIMAGEIO_IDIFF}" + -outdir "${TEST_OUT_DIR}/cycles" + ) + endif() + endmacro() + if(WITH_OPENGL_TESTS) + add_cycles_render_test(opengl) + endif() + add_cycles_render_test(bake) + add_cycles_render_test(denoise) + add_cycles_render_test(displacement) + add_cycles_render_test(image_data_types) + add_cycles_render_test(image_mapping) + add_cycles_render_test(image_texture_limit) + add_cycles_render_test(light) + add_cycles_render_test(mblur) + add_cycles_render_test(reports) + add_cycles_render_test(render) + add_cycles_render_test(shader) + add_cycles_render_test(shader_tangent) + add_cycles_render_test(shadow_catcher) + add_cycles_render_test(volume) else() MESSAGE(STATUS "Disabling Cycles tests because tests folder does not exist") endif() endif() + +if(WITH_ALEMBIC) + find_package_wrapper(Alembic) + if(NOT ALEMBIC_FOUND) + message(FATAL_ERROR "Alembic is enabled but cannot be found") + endif() + get_filename_component(real_include_dir ${ALEMBIC_INCLUDE_DIR} REALPATH) + get_filename_component(ALEMBIC_ROOT_DIR ${real_include_dir} DIRECTORY) + + if(MSVC) + # FIXME, de-duplicate. + add_test( + NAME alembic_tests + COMMAND + "$<TARGET_FILE_DIR:blender>/${BLENDER_VERSION_MAJOR}.${BLENDER_VERSION_MINOR}/python/bin/python$<$<CONFIG:Debug>:_d>" + ${CMAKE_CURRENT_LIST_DIR}/alembic_tests.py + --blender "$<TARGET_FILE:blender>" + --testdir "${TEST_SRC_DIR}/alembic" + --alembic-root "${ALEMBIC_ROOT_DIR}" + ) + else() + add_test( + NAME alembic_tests + COMMAND ${CMAKE_CURRENT_LIST_DIR}/alembic_tests.py + --blender "$<TARGET_FILE:blender>" + --testdir "${TEST_SRC_DIR}/alembic" + --alembic-root "${ALEMBIC_ROOT_DIR}" + ) + endif() + + add_test( + NAME script_alembic_import + COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} + --python ${CMAKE_CURRENT_LIST_DIR}/bl_alembic_import_test.py + -- + --testdir "${TEST_SRC_DIR}/alembic" + --with-legacy-depsgraph=${WITH_LEGACY_DEPSGRAPH} + ) +endif() diff --git a/tests/python/alembic_tests.py b/tests/python/alembic_tests.py new file mode 100755 index 00000000000..96a68de9801 --- /dev/null +++ b/tests/python/alembic_tests.py @@ -0,0 +1,478 @@ +#!/usr/bin/env python3 +# ##### 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. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +import argparse +import functools +import shutil +import pathlib +import subprocess +import sys +import tempfile +import unittest + + +def with_tempdir(wrapped): + """Creates a temporary directory for the function, cleaning up after it returns normally. + + When the wrapped function raises an exception, the contents of the temporary directory + remain available for manual inspection. + + The wrapped function is called with an extra positional argument containing + the pathlib.Path() of the temporary directory. + """ + + @functools.wraps(wrapped) + def decorator(*args, **kwargs): + dirname = tempfile.mkdtemp(prefix='blender-alembic-test') + try: + retval = wrapped(*args, pathlib.Path(dirname), **kwargs) + except: + print('Exception in %s, not cleaning up temporary directory %s' % (wrapped, dirname)) + raise + else: + shutil.rmtree(dirname) + return retval + + return decorator + + +class AbcPropError(Exception): + """Raised when AbstractAlembicTest.abcprop() finds an error.""" + + +class AbstractAlembicTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + import re + + cls.blender = args.blender + cls.testdir = pathlib.Path(args.testdir) + cls.alembic_root = pathlib.Path(args.alembic_root) + + # 'abcls' outputs ANSI colour codes, even when stdout is not a terminal. + # See https://github.com/alembic/alembic/issues/120 + cls.ansi_remove_re = re.compile(rb'\x1b[^m]*m') + + # 'abcls' array notation, like "name[16]" + cls.abcls_array = re.compile(r'^(?P<name>[^\[]+)(\[(?P<arraysize>\d+)\])?$') + + def run_blender(self, filepath: str, python_script: str, timeout: int=300) -> str: + """Runs Blender by opening a blendfile and executing a script. + + Returns Blender's stdout + stderr combined into one string. + + :param filepath: taken relative to self.testdir. + :param timeout: in seconds + """ + + blendfile = self.testdir / filepath + + command = ( + self.blender, + '--background', + '-noaudio', + '--factory-startup', + '--enable-autoexec', + str(blendfile), + '-E', 'CYCLES', + '--python-exit-code', '47', + '--python-expr', python_script, + ) + + proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + timeout=timeout) + output = proc.stdout.decode('utf8') + if proc.returncode: + self.fail('Error %d running Blender:\n%s' % (proc.returncode, output)) + + return output + + def abcprop(self, filepath: pathlib.Path, proppath: str) -> dict: + """Uses abcls to obtain compound property values from an Alembic object. + + A dict of subproperties is returned, where the values are Python values. + + The Python bindings for Alembic are old, and only compatible with Python 2.x, + so that's why we can't use them here, and have to rely on other tooling. + """ + import collections + + abcls = self.alembic_root / 'bin' / 'abcls' + + command = (str(abcls), '-vl', '%s%s' % (filepath, proppath)) + proc = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, + timeout=30) + + coloured_output = proc.stdout + output = self.ansi_remove_re.sub(b'', coloured_output).decode('utf8') + + # Because of the ANSI colour codes, we need to remove those first before + # decoding to text. This means that we cannot use the universal_newlines + # parameter to subprocess.run(), and have to do the conversion ourselves + output = output.replace('\r\n', '\n').replace('\r', '\n') + + if proc.returncode: + raise AbcPropError('Error %d running abcls:\n%s' % (proc.returncode, output)) + + # Mapping from value type to callable that can convert a string to Python values. + converters = { + 'bool_t': int, + 'uint8_t': int, + 'int16_t': int, + 'int32_t': int, + 'uint64_t': int, + 'float64_t': float, + 'float32_t': float, + } + + result = {} + + # Ideally we'd get abcls to output JSON, see https://github.com/alembic/alembic/issues/121 + lines = collections.deque(output.split('\n')) + while lines: + info = lines.popleft() + if not info: + continue + parts = info.split() + proptype = parts[0] + + if proptype == 'CompoundProperty': + # To read those, call self.abcprop() on it. + continue + if len(parts) < 2: + raise ValueError('Error parsing result from abcprop: %s', info.strip()) + valtype_and_arrsize, name_and_extent = parts[1:] + + # Parse name and extent + m = self.abcls_array.match(name_and_extent) + if not m: + self.fail('Unparsable name/extent from abcls: %s' % name_and_extent) + name, extent = m.group('name'), m.group('arraysize') + + if extent != '1': + self.fail('Unsupported extent %s for property %s/%s' % (extent, proppath, name)) + + # Parse type + m = self.abcls_array.match(valtype_and_arrsize) + if not m: + self.fail('Unparsable value type from abcls: %s' % valtype_and_arrsize) + valtype, scalarsize = m.group('name'), m.group('arraysize') + + # Convert values + try: + conv = converters[valtype] + except KeyError: + self.fail('Unsupported type %s for property %s/%s' % (valtype, proppath, name)) + + def convert_single_line(linevalue): + try: + if scalarsize is None: + return conv(linevalue) + else: + return [conv(v.strip()) for v in linevalue.split(',')] + except ValueError as ex: + return str(ex) + + if proptype == 'ScalarProperty': + value = lines.popleft() + result[name] = convert_single_line(value) + elif proptype == 'ArrayProperty': + arrayvalue = [] + # Arrays consist of a variable number of items, and end in a blank line. + while True: + linevalue = lines.popleft() + if not linevalue: + break + arrayvalue.append(convert_single_line(linevalue)) + result[name] = arrayvalue + else: + self.fail('Unsupported type %s for property %s/%s' % (proptype, proppath, name)) + + return result + + def assertAlmostEqualFloatArray(self, actual, expect, places=6, delta=None): + """Asserts that the arrays of floats are almost equal.""" + + self.assertEqual(len(actual), len(expect), + 'Actual array has %d items, expected %d' % (len(actual), len(expect))) + + for idx, (act, exp) in enumerate(zip(actual, expect)): + self.assertAlmostEqual(act, exp, places=places, delta=delta, + msg='%f != %f at index %d' % (act, exp, idx)) + + +class HierarchicalAndFlatExportTest(AbstractAlembicTest): + @with_tempdir + def test_hierarchical_export(self, tempdir: pathlib.Path): + abc = tempdir / 'cubes_hierarchical.abc' + script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \ + "renderable_only=True, visible_layers_only=True, flatten=False)" % abc.as_posix() + self.run_blender('cubes-hierarchy.blend', script) + + # Now check the resulting Alembic file. + xform = self.abcprop(abc, '/Cube/Cube_002/Cube_012/.xform') + self.assertEqual(1, xform['.inherits']) + self.assertAlmostEqualFloatArray( + xform['.vals'], + [1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 3.07484, -2.92265, 0.0586434, 1.0] + ) + + @with_tempdir + def test_flat_export(self, tempdir: pathlib.Path): + abc = tempdir / 'cubes_flat.abc' + script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \ + "renderable_only=True, visible_layers_only=True, flatten=True)" % abc.as_posix() + self.run_blender('cubes-hierarchy.blend', script) + + # Now check the resulting Alembic file. + xform = self.abcprop(abc, '/Cube_012/.xform') + self.assertEqual(0, xform['.inherits']) + + self.assertAlmostEqualFloatArray( + xform['.vals'], + [0.343134, 0.485243, 0.804238, 0, + 0.0, 0.856222, -0.516608, 0, + -0.939287, 0.177266, 0.293799, 0, + 1, 3, 4, 1], + ) + + +class DupliGroupExportTest(AbstractAlembicTest): + @with_tempdir + def test_hierarchical_export(self, tempdir: pathlib.Path): + abc = tempdir / 'dupligroup_hierarchical.abc' + script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \ + "renderable_only=True, visible_layers_only=True, flatten=False)" % abc.as_posix() + self.run_blender('dupligroup-scene.blend', script) + + # Now check the resulting Alembic file. + xform = self.abcprop(abc, '/Real_Cube/Linked_Suzanne/Cylinder/Suzanne/.xform') + self.assertEqual(1, xform['.inherits']) + self.assertAlmostEqualFloatArray( + xform['.vals'], + [1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 2.0, 0.0, 1.0] + ) + + @with_tempdir + def test_flat_export(self, tempdir: pathlib.Path): + abc = tempdir / 'dupligroup_hierarchical.abc' + script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \ + "renderable_only=True, visible_layers_only=True, flatten=True)" % abc.as_posix() + self.run_blender('dupligroup-scene.blend', script) + + # Now check the resulting Alembic file. + xform = self.abcprop(abc, '/Suzanne/.xform') + self.assertEqual(0, xform['.inherits']) + + self.assertAlmostEqualFloatArray( + xform['.vals'], + [1.5, 0.0, 0.0, 0.0, + 0.0, 1.5, 0.0, 0.0, + 0.0, 0.0, 1.5, 0.0, + 2.0, 3.0, 0.0, 1.0] + ) + + +class CurveExportTest(AbstractAlembicTest): + @with_tempdir + def test_export_single_curve(self, tempdir: pathlib.Path): + abc = tempdir / 'single-curve.abc' + script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \ + "renderable_only=True, visible_layers_only=True, flatten=False)" % abc.as_posix() + self.run_blender('single-curve.blend', script) + + # Now check the resulting Alembic file. + abcprop = self.abcprop(abc, '/NurbsCurve/NurbsCurveShape/.geom') + self.assertEqual(abcprop['.orders'], [4]) + + abcprop = self.abcprop(abc, '/NurbsCurve/NurbsCurveShape/.geom/.userProperties') + self.assertEqual(abcprop['blender:resolution'], 10) + + +class HairParticlesExportTest(AbstractAlembicTest): + """Tests exporting with/without hair/particles. + + Just a basic test to ensure that the enabling/disabling works, and that export + works at all. NOT testing the quality/contents of the exported file. + """ + + def _do_test(self, tempdir: pathlib.Path, export_hair: bool, export_particles: bool) -> pathlib.Path: + abc = tempdir / 'hair-particles.abc' + script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \ + "renderable_only=True, visible_layers_only=True, flatten=False, " \ + "export_hair=%r, export_particles=%r, as_background_job=False)" \ + % (abc.as_posix(), export_hair, export_particles) + self.run_blender('hair-particles.blend', script) + return abc + + @with_tempdir + def test_with_both(self, tempdir: pathlib.Path): + abc = self._do_test(tempdir, True, True) + + abcprop = self.abcprop(abc, '/Suzanne/Hair system/.geom') + self.assertIn('nVertices', abcprop) + + abcprop = self.abcprop(abc, '/Suzanne/Non-hair particle system/.geom') + self.assertIn('.velocities', abcprop) + + abcprop = self.abcprop(abc, '/Suzanne/SuzanneShape/.geom') + self.assertIn('.faceIndices', abcprop) + + @with_tempdir + def test_with_hair_only(self, tempdir: pathlib.Path): + abc = self._do_test(tempdir, True, False) + + abcprop = self.abcprop(abc, '/Suzanne/Hair system/.geom') + self.assertIn('nVertices', abcprop) + + self.assertRaises(AbcPropError, self.abcprop, abc, + '/Suzanne/Non-hair particle system/.geom') + + abcprop = self.abcprop(abc, '/Suzanne/SuzanneShape/.geom') + self.assertIn('.faceIndices', abcprop) + + @with_tempdir + def test_with_particles_only(self, tempdir: pathlib.Path): + abc = self._do_test(tempdir, False, True) + + self.assertRaises(AbcPropError, self.abcprop, abc, '/Suzanne/Hair system/.geom') + + abcprop = self.abcprop(abc, '/Suzanne/Non-hair particle system/.geom') + self.assertIn('.velocities', abcprop) + + abcprop = self.abcprop(abc, '/Suzanne/SuzanneShape/.geom') + self.assertIn('.faceIndices', abcprop) + + @with_tempdir + def test_with_neither(self, tempdir: pathlib.Path): + abc = self._do_test(tempdir, False, False) + + self.assertRaises(AbcPropError, self.abcprop, abc, '/Suzanne/Hair system/.geom') + self.assertRaises(AbcPropError, self.abcprop, abc, + '/Suzanne/Non-hair particle system/.geom') + + abcprop = self.abcprop(abc, '/Suzanne/SuzanneShape/.geom') + self.assertIn('.faceIndices', abcprop) + + +class LongNamesExportTest(AbstractAlembicTest): + @with_tempdir + def test_export_long_names(self, tempdir: pathlib.Path): + abc = tempdir / 'long-names.abc' + script = "import bpy; bpy.ops.wm.alembic_export(filepath='%s', start=1, end=1, " \ + "renderable_only=False, visible_layers_only=False, flatten=False)" % abc.as_posix() + self.run_blender('long-names.blend', script) + + name_parts = [ + 'foG9aeLahgoh5goacee1dah6Hethaghohjaich5pasizairuWigee1ahPeekiGh', + 'yoNgoisheedah2ua0eigh2AeCaiTee5bo0uphoo7Aixephah9racahvaingeeH4', + 'zuthohnoi1thooS3eezoo8seuph2Boo5aefacaethuvee1aequoonoox1sookie', + 'wugh4ciTh3dipiepeequait5uug7thiseek5ca7Eijei5ietaizokohhaecieto', + 'up9aeheenein9oteiX6fohP3thiez6Ahvah0oohah1ep2Eesho4Beboechaipoh', + 'coh4aehiacheTh0ue0eegho9oku1lohl4loht9ohPoongoow7dasiego6yimuis', + 'lohtho8eigahfeipohviepajaix4it2peeQu6Iefee1nevihaes4cee2soh4noy', + 'kaht9ahv0ieXaiyih7ohxe8bah7eeyicahjoa2ohbu7Choxua7oongah6sei4bu', + 'deif0iPaechohkee5nahx6oi2uJeeN7ze3seunohJibe4shai0mah5Iesh3Quai', + 'ChohDahshooNee0NeNohthah0eiDeese3Vu6ohShil1Iey9ja0uebi2quiShae6', + 'Dee1kai7eiph2ahh2nufah3zai3eexeengohQue1caj0eeW0xeghi3eshuadoot', + 'aeshiup3aengajoog0AhCoo5tiu3ieghaeGhie4Tu1ohh1thee8aepheingah1E', + 'ooRa6ahciolohshaifoopeo9ZeiGhae2aech4raisheiWah9AaNga0uas9ahquo', + 'thaepheip2aip6shief4EaXopei8ohPo0ighuiXah2ashowai9nohp4uach6Mei', + 'ohph4yaev3quieji3phophiem3OoNuisheepahng4waithae3Naichai7aw3noo', + 'aibeawaneBahmieyuph8ieng8iopheereeD2uu9Uyee5bei2phahXeir8eeJ8oo', + 'ooshahphei2hoh3uth5chaen7ohsai6uutiesucheichai8ungah9Gie1Aiphie', + 'eiwohchoo7ere2iebohn4Aapheichaelooriiyaoxaik7ooqua7aezahx0aeJei', + 'Vah0ohgohphiefohTheshieghichaichahch5moshoo0zai5eeva7eisi4yae8T', + 'EibeeN0fee0Gohnguz8iec6yeigh7shuNg4eingu3siph9joucahpeidoom4ree', + 'iejiu3shohheeZahHusheimeefaihoh5eecachu5eeZie9ceisugu9taidohT3U', + 'eex6dilakaix5Eetai7xiCh5Jaa8aiD4Ag3tuij1aijohv5fo0heevah8hohs3m', + 'ohqueeNgahraew6uraemohtoo5qua3oojiex6ohqu6Aideibaithaiphuriquie', + 'cei0eiN4Shiey7Aeluy3unohboo5choiphahc2mahbei5paephaiKeso1thoog1', + 'ieghif4ohKequ7ong0jah5ooBah0eiGh1caechahnahThae9Shoo0phopashoo4', + 'roh9er3thohwi5am8iequeequuSh3aic0voocai3ihi5nie2abahphupiegh7vu', + 'uv3Quei7wujoo5beingei2aish5op4VaiX0aebai7iwoaPee5pei8ko9IepaPig', + 'co7aegh5beitheesi9lu7jeeQu3johgeiphee9cheichi8aithuDehu2gaeNein', + 'thai3Tiewoo4nuir1ohy4aithiuZ7shae1luuwei5phibohriepe2paeci1Ach8', + 'phoi3ribah7ufuvoh8eigh1oB6deeBaiPohphaghiPieshahfah5EiCi3toogoo', + 'aiM8geil7ooreinee4Cheiwea4yeec8eeshi7Sei4Shoo3wu6ohkaNgooQu1mai', + 'agoo3faciewah9ZeesiXeereek7am0eigaeShie3Tisu8haReeNgoo0ci2Hae5u', + 'Aesatheewiedohshaephaenohbooshee8eu7EiJ8isal1laech2eiHo0noaV3ta', + 'liunguep3ooChoo4eir8ahSie8eenee0oo1TooXu8Cais8Aimo4eir6Phoo3xei', + 'toe9heepeobein3teequachemei0Cejoomef9ujie3ohwae9AiNgiephi3ep0de', + 'ua6xooY9uzaeB3of6sheiyaedohoiS5Eev0Aequ9ahm1zoa5Aegh3ooz9ChahDa', + 'eevasah6Bu9wi7EiwiequumahkaeCheegh6lui8xoh4eeY4ieneavah8phaibun', + 'AhNgei2sioZeeng6phaecheemeehiShie5eFeiTh6ooV8iiphabud0die4siep4', + 'kushe6Xieg6ahQuoo9aex3aipheefiec1esa7OhBuG0ueziep9phai5eegh1vie', + 'Jie5yu8aafuQuoh9shaep3moboh3Pooy7och8oC6obeik6jaew2aiLooweib3ch', + 'ohohjajaivaiRail3odaimei6aekohVaicheip2wu7phieg5Gohsaing2ahxaiy', + 'hahzaht6yaiYu9re9jah9loisiit4ahtoh2quoh9xohishioz4oo4phofu3ogha', + 'pu4oorea0uh2tahB8aiZoonge1aophaes6ogaiK9ailaigeej4zoVou8ielotee', + 'cae2thei3Luphuqu0zeeG8leeZuchahxaicai4ui4Eedohte9uW6gae8Geeh0ea', + 'air7tuy7ohw5sho2Tahpai8aep4so5ria7eaShus5weaqu0Naquei2xaeyoo2ae', + 'vohge4aeCh7ahwoo7Jaex6sohl0Koong4Iejisei8Coir0iemeiz9uru9Iebaep', + 'aepeidie8aiw6waish9gie4Woolae2thuj5phae4phexux7gishaeph4Deu7ooS', + 'vahc5ia0xohHooViT0uyuxookiaquu2ogueth0ahquoudeefohshai8aeThahba', + 'mun3oagah2eequaenohfoo8DaigeghoozaV2eiveeQuee7kah0quaa6tiesheet', + 'ooSet4IdieC4ugow3za0die4ohGoh1oopoh6luaPhaeng4Eechea1hae0eimie5', + 'iedeimadaefu2NeiPaey2jooloov5iehiegeakoo4ueso7aeK9ahqu2Thahkaes', + 'nahquah9Quuu2uuf0aJah7eishi2siegh8ue5eiJa2EeVu8ebohkepoh4dahNgo', + 'io1bie7chioPiej5ae2oohe2fee6ooP2thaeJohjohb9Se8tang3eipaifeimai', + 'oungoqu6dieneejiechez1xeD2Zi9iox2Ahchaiy9ithah3ohVoolu2euQuuawo', + 'thaew0veigei4neishohd8mecaixuqu7eeshiex1chaigohmoThoghoitoTa0Eo', + 'ahroob2phohvaiz0Ohteik2ohtakie6Iu1vitho8IyiyeeleeShae9defaiw9ki', + 'DohHoothohzeaxolai3Toh5eJie7ahlah9reF0ohn1chaipoogain2aibahw4no', + 'aif8lo5she4aich5cho2rie8ieJaujeem2Joongeedae4vie3tah1Leequaix1O', + 'Aang0Shaih6chahthie1ahZ7aewei9thiethee7iuThah3yoongi8ahngiobaa5', + 'iephoBuayoothah0Ru6aichai4aiw8deg1umongauvaixai3ohy6oowohlee8ei', + 'ohn5shigoameer0aejohgoh8oChohlaecho9jie6shu0ahg9Bohngau6paevei9', + 'edahghaishak0paigh1eecuich3aad7yeB0ieD6akeeliem2beifufaekee6eat', + 'hiechahgheloh2zo7Ieghaiph0phahhu8aeyuiKie1xeipheech9zai4aeme0ee', + 'Cube' + ] + name = '/' + '/'.join(name_parts) + + # Now check the resulting Alembic file. + abcprop = self.abcprop(abc, '%s/.xform' % name) + self.assertEqual(abcprop['.vals'], [ + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 3.0, 0.0, 1.0, + ]) + + abcprop = self.abcprop(abc, '%s/CubeShape/.geom' % name) + self.assertIn('.faceCounts', abcprop) + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--blender', required=True) + parser.add_argument('--testdir', required=True) + parser.add_argument('--alembic-root', required=True) + args, remaining = parser.parse_known_args() + + unittest.main(argv=sys.argv[0:1] + remaining) diff --git a/tests/python/batch_import.py b/tests/python/batch_import.py index 8fc679a7c15..a6e2469349b 100644 --- a/tests/python/batch_import.py +++ b/tests/python/batch_import.py @@ -48,29 +48,17 @@ import os import sys -def clear_scene(): - import bpy - unique_obs = set() - for scene in bpy.data.scenes: - for obj in scene.objects[:]: - scene.objects.unlink(obj) - unique_obs.add(obj) - - # remove obdata, for now only worry about the startup scene - for bpy_data_iter in (bpy.data.objects, bpy.data.meshes, bpy.data.lamps, bpy.data.cameras): - for id_data in bpy_data_iter: - bpy_data_iter.remove(id_data) - - -def batch_import(operator="", - path="", - save_path="", - match="", - start=0, - end=sys.maxsize, - ): +def batch_import( + operator="", + path="", + save_path="", + match="", + start=0, + end=sys.maxsize, +): import addon_utils _reset_all = addon_utils.reset_all # XXX, hack + _disable_all = addon_utils.disable_all # XXX, hack import fnmatch @@ -84,10 +72,8 @@ def batch_import(operator="", def file_generator(path): for dirpath, dirnames, filenames in os.walk(path): - - # skip '.svn' - if dirpath.startswith("."): - continue + # skip '.git' + dirnames[:] = [d for d in dirnames if not d.startswith(".")] for filename in filenames: if pattern_match(filename): @@ -116,11 +102,12 @@ def batch_import(operator="", # hack so loading the new file doesn't undo our loaded addons addon_utils.reset_all = lambda: None # XXX, hack + addon_utils.disable_all = lambda: None # XXX, hack - bpy.ops.wm.read_factory_settings() + bpy.ops.wm.read_factory_settings(use_empty=True) addon_utils.reset_all = _reset_all # XXX, hack - clear_scene() + addon_utils.disable_all = _disable_all # XXX, hack result = op(filepath=f) diff --git a/tests/python/bl_alembic_import_test.py b/tests/python/bl_alembic_import_test.py new file mode 100644 index 00000000000..c3a4af26e11 --- /dev/null +++ b/tests/python/bl_alembic_import_test.py @@ -0,0 +1,270 @@ +# ##### 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. +# +# ##### END GPL LICENSE BLOCK ##### + +# <pep8 compliant> + +""" +./blender.bin --background -noaudio --factory-startup --python tests/python/bl_alembic_import_test.py -- --testdir /path/to/lib/tests/alembic +""" + +import pathlib +import sys +import unittest + +import bpy + +args = None + + +class AbstractAlembicTest(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.testdir = args.testdir + + def setUp(self): + self.assertTrue(self.testdir.exists(), + 'Test dir %s should exist' % self.testdir) + + # Make sure we always start with a known-empty file. + bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend")) + + def assertAlmostEqualFloatArray(self, actual, expect, places=6, delta=None): + """Asserts that the arrays of floats are almost equal.""" + + self.assertEqual(len(actual), len(expect), + 'Actual array has %d items, expected %d' % (len(actual), len(expect))) + + for idx, (act, exp) in enumerate(zip(actual, expect)): + self.assertAlmostEqual(act, exp, places=places, delta=delta, + msg='%f != %f at index %d' % (act, exp, idx)) + + +class SimpleImportTest(AbstractAlembicTest): + def test_import_cube_hierarchy(self): + res = bpy.ops.wm.alembic_import( + filepath=str(self.testdir / "cubes-hierarchy.abc"), + as_background_job=False) + self.assertEqual({'FINISHED'}, res) + + # The objects should be linked to scene_collection in Blender 2.8, + # and to scene in Blender 2.7x. + objects = bpy.context.scene.objects + self.assertEqual(13, len(objects)) + + # Test the hierarchy. + self.assertIsNone(objects['Cube'].parent) + self.assertEqual(objects['Cube'], objects['Cube_001'].parent) + self.assertEqual(objects['Cube'], objects['Cube_002'].parent) + self.assertEqual(objects['Cube'], objects['Cube_003'].parent) + self.assertEqual(objects['Cube_003'], objects['Cube_004'].parent) + self.assertEqual(objects['Cube_003'], objects['Cube_005'].parent) + self.assertEqual(objects['Cube_003'], objects['Cube_006'].parent) + + def test_inherit_or_not(self): + res = bpy.ops.wm.alembic_import( + filepath=str(self.testdir / "T52022-inheritance.abc"), + as_background_job=False) + self.assertEqual({'FINISHED'}, res) + + # The objects should be linked to scene_collection in Blender 2.8, + # and to scene in Blender 2.7x. + objects = bpy.context.scene.objects + + # ABC parent is top-level object, which translates to nothing in Blender + self.assertIsNone(objects['locator1'].parent) + + # ABC parent is locator1, but locator2 has "inherits Xforms" = false, which + # translates to "no parent" in Blender. + self.assertIsNone(objects['locator2'].parent) + + # Shouldn't have inherited the ABC parent's transform. + x, y, z = objects['locator2'].matrix_world.to_translation() + self.assertAlmostEqual(0, x) + self.assertAlmostEqual(0, y) + self.assertAlmostEqual(2, z) + + # ABC parent is inherited and translates to normal parent in Blender. + self.assertEqual(objects['locator2'], objects['locatorShape2'].parent) + + # Should have inherited its ABC parent's transform. + x, y, z = objects['locatorShape2'].matrix_world.to_translation() + self.assertAlmostEqual(0, x) + self.assertAlmostEqual(0, y) + self.assertAlmostEqual(2, z) + + + def test_select_after_import(self): + # Add a sphere, so that there is something in the scene, selected, and active, + # before we do the Alembic import. + bpy.ops.mesh.primitive_uv_sphere_add() + sphere = bpy.context.active_object + self.assertEqual('Sphere', sphere.name) + self.assertEqual([sphere], bpy.context.selected_objects) + + bpy.ops.wm.alembic_import( + filepath=str(self.testdir / "cubes-hierarchy.abc"), + as_background_job=False) + + # The active object is probably the first one that was imported, but this + # behaviour is not defined. At least it should be one of the cubes, and + # not the sphere. + self.assertNotEqual(sphere, bpy.context.active_object) + self.assertTrue('Cube' in bpy.context.active_object.name) + + # All cubes should be selected, but the sphere shouldn't be. + for ob in bpy.data.objects: + self.assertEqual('Cube' in ob.name, ob.select) + + def test_change_path_constraint(self): + import math + + fname = 'cube-rotating1.abc' + abc = self.testdir / fname + relpath = bpy.path.relpath(str(abc)) + + res = bpy.ops.wm.alembic_import(filepath=str(abc), as_background_job=False) + self.assertEqual({'FINISHED'}, res) + cube = bpy.context.active_object + + # Check that the file loaded ok. + bpy.context.scene.frame_set(10) + x, y, z = cube.matrix_world.to_euler('XYZ') + self.assertAlmostEqual(x, 0) + self.assertAlmostEqual(y, 0) + self.assertAlmostEqual(z, math.pi / 2, places=5) + + # Change path from absolute to relative. This should not break the animation. + bpy.context.scene.frame_set(1) + bpy.data.cache_files[fname].filepath = relpath + bpy.context.scene.frame_set(10) + + x, y, z = cube.matrix_world.to_euler('XYZ') + self.assertAlmostEqual(x, 0) + self.assertAlmostEqual(y, 0) + self.assertAlmostEqual(z, math.pi / 2, places=5) + + # Replace the Alembic file; this should apply new animation. + bpy.data.cache_files[fname].filepath = relpath.replace('1.abc', '2.abc') + bpy.context.scene.update() + + if args.with_legacy_depsgraph: + bpy.context.scene.frame_set(10) + + x, y, z = cube.matrix_world.to_euler('XYZ') + self.assertAlmostEqual(x, math.pi / 2, places=5) + self.assertAlmostEqual(y, 0) + self.assertAlmostEqual(z, 0) + + def test_change_path_modifier(self): + import math + + fname = 'animated-mesh.abc' + abc = self.testdir / fname + relpath = bpy.path.relpath(str(abc)) + + res = bpy.ops.wm.alembic_import(filepath=str(abc), as_background_job=False) + self.assertEqual({'FINISHED'}, res) + plane = bpy.context.active_object + + # Check that the file loaded ok. + bpy.context.scene.frame_set(6) + mesh = plane.to_mesh(bpy.context.scene, True, 'RENDER') + self.assertAlmostEqual(-1, mesh.vertices[0].co.x) + self.assertAlmostEqual(-1, mesh.vertices[0].co.y) + self.assertAlmostEqual(0.5905638933181763, mesh.vertices[0].co.z) + + # Change path from absolute to relative. This should not break the animation. + bpy.context.scene.frame_set(1) + bpy.data.cache_files[fname].filepath = relpath + bpy.context.scene.frame_set(6) + + mesh = plane.to_mesh(bpy.context.scene, True, 'RENDER') + self.assertAlmostEqual(1, mesh.vertices[3].co.x) + self.assertAlmostEqual(1, mesh.vertices[3].co.y) + self.assertAlmostEqual(0.5905638933181763, mesh.vertices[3].co.z) + + def test_import_long_names(self): + # This file contains very long names. The longest name is 4047 chars. + bpy.ops.wm.alembic_import( + filepath=str(self.testdir / "long-names.abc"), + as_background_job=False) + + self.assertIn('Cube', bpy.data.objects) + self.assertEqual('CubeShape', bpy.data.objects['Cube'].data.name) + + +class VertexColourImportTest(AbstractAlembicTest): + def test_import_from_houdini(self): + # Houdini saved "face-varying", and as RGB. + res = bpy.ops.wm.alembic_import( + filepath=str(self.testdir / "vertex-colours-houdini.abc"), + as_background_job=False) + self.assertEqual({'FINISHED'}, res) + + ob = bpy.context.active_object + layer = ob.data.vertex_colors['Cf'] # MeshLoopColorLayer + + # Test some known-good values. + self.assertAlmostEqualFloatArray(layer.data[0].color, (0, 0, 0)) + self.assertAlmostEqualFloatArray(layer.data[98].color, (0.9019607, 0.4745098, 0.2666666)) + self.assertAlmostEqualFloatArray(layer.data[99].color, (0.8941176, 0.4705882, 0.2627451)) + + def test_import_from_blender(self): + # Blender saved per-vertex, and as RGBA. + res = bpy.ops.wm.alembic_import( + filepath=str(self.testdir / "vertex-colours-blender.abc"), + as_background_job=False) + self.assertEqual({'FINISHED'}, res) + + ob = bpy.context.active_object + layer = ob.data.vertex_colors['Cf'] # MeshLoopColorLayer + + # Test some known-good values. + self.assertAlmostEqualFloatArray(layer.data[0].color, (1.0, 0.0156862, 0.3607843)) + self.assertAlmostEqualFloatArray(layer.data[98].color, (0.0941176, 0.1215686, 0.9137254)) + self.assertAlmostEqualFloatArray(layer.data[99].color, (0.1294117, 0.3529411, 0.7529411)) + + +def main(): + global args + import argparse + + if '--' in sys.argv: + argv = [sys.argv[0]] + sys.argv[sys.argv.index('--') + 1:] + else: + argv = sys.argv + + parser = argparse.ArgumentParser() + parser.add_argument('--testdir', required=True, type=pathlib.Path) + parser.add_argument('--with-legacy-depsgraph', default=False, + type=lambda v: v in {'ON', 'YES', 'TRUE'}) + args, remaining = parser.parse_known_args(argv) + + unittest.main(argv=remaining) + + +if __name__ == "__main__": + import traceback + # So a python error exits Blender itself too + try: + main() + except SystemExit: + raise + except: + traceback.print_exc() + sys.exit(1) diff --git a/tests/python/bl_keymap_completeness.py b/tests/python/bl_keymap_completeness.py index 00322907f69..652ed449a3c 100644 --- a/tests/python/bl_keymap_completeness.py +++ b/tests/python/bl_keymap_completeness.py @@ -80,5 +80,6 @@ def main(): import sys sys.exit(1) + if __name__ == "__main__": main() diff --git a/tests/python/bl_load_py_modules.py b/tests/python/bl_load_py_modules.py index c13679d16f0..39e7bd33d44 100644 --- a/tests/python/bl_load_py_modules.py +++ b/tests/python/bl_load_py_modules.py @@ -36,6 +36,9 @@ BLACKLIST = { "cycles", "io_export_dxf", # TODO, check on why this fails 'io_import_dxf', # Because of cydxfentity.so dependency + + # The unpacked wheel is only loaded when actually used, not directly on import: + os.path.join("io_blend_utils", "blender_bam-unpacked.whl"), } # Some modules need to add to the `sys.path`. @@ -90,9 +93,8 @@ def addon_modules_sorted(): def source_list(path, filename_check=None): from os.path import join for dirpath, dirnames, filenames in os.walk(path): - # skip '.svn' - if dirpath.startswith("."): - continue + # skip '.git' + dirnames[:] = [d for d in dirnames if not d.startswith(".")] for filename in filenames: filepath = join(dirpath, filename) @@ -120,6 +122,8 @@ def load_addons(): def load_modules(): + VERBOSE = os.environ.get("BLENDER_VERBOSE") is not None + modules = [] module_paths = [] @@ -159,6 +163,14 @@ def load_modules(): del module_names # + # test we tested all files except for presets and templates + ignore_paths = [ + os.sep + "presets" + os.sep, + os.sep + "templates" + os.sep, + ] + ([(os.sep + f + os.sep) for f in BLACKLIST] + + [(os.sep + f + ".py") for f in BLACKLIST]) + + # # now submodules for m in modules: filepath = m.__file__ @@ -175,15 +187,35 @@ def load_modules(): for f in MODULE_SYS_PATHS.get(mod_name_full, ()) ]) - __import__(mod_name_full) - mod_imp = sys.modules[mod_name_full] - - sys.path[:] = sys_path_back - - # check we load what we ask for. - assert(os.path.samefile(mod_imp.__file__, submod_full)) - - modules.append(mod_imp) + try: + __import__(mod_name_full) + mod_imp = sys.modules[mod_name_full] + + sys.path[:] = sys_path_back + + # check we load what we ask for. + assert(os.path.samefile(mod_imp.__file__, submod_full)) + + modules.append(mod_imp) + except Exception as e: + import traceback + # Module might fail to import, but we don't want whole test to fail here. + # Reasoning: + # - This module might be in ignored list (for example, preset or template), + # so failing here will cause false-positive test failure. + # - If this is module which should not be ignored, it is not added to list + # of successfully loaded modules, meaning the test will catch this + # import failure. + # - We want to catch all failures of this script instead of stopping on + # a first big failure. + do_print = True + if not VERBOSE: + for ignore in ignore_paths: + if ignore in submod_full: + do_print = False + break + if do_print: + traceback.print_exc() # # check which filepaths we didn't load @@ -202,20 +234,11 @@ def load_modules(): for f in loaded_files: source_files.remove(f) - # - # test we tested all files except for presets and templates - ignore_paths = [ - os.sep + "presets" + os.sep, - os.sep + "templates" + os.sep, - ] + ([(os.sep + f + os.sep) for f in BLACKLIST] + - [(os.sep + f + ".py") for f in BLACKLIST]) - for f in source_files: - ok = False for ignore in ignore_paths: if ignore in f: - ok = True - if not ok: + break + else: raise Exception("Source file %r not loaded in test" % f) print("loaded %d modules" % len(loaded_files)) diff --git a/tests/python/bl_mesh_modifiers.py b/tests/python/bl_mesh_modifiers.py index 526a54a49a2..bff2c31984c 100644 --- a/tests/python/bl_mesh_modifiers.py +++ b/tests/python/bl_mesh_modifiers.py @@ -31,7 +31,6 @@ import math USE_QUICK_RENDER = False -IS_BMESH = hasattr(__import__("bpy").types, "LoopColors") # ----------------------------------------------------------------------------- # utility functions @@ -203,13 +202,8 @@ def defaults_object(obj): mesh.show_normal_vertex = True - # lame! - if IS_BMESH: - for poly in mesh.polygons: - poly.use_smooth = True - else: - for face in mesh.faces: - face.use_smooth = True + for poly in mesh.polygons: + poly.use_smooth = True def defaults_modifier(mod): @@ -220,16 +214,14 @@ def defaults_modifier(mod): # ----------------------------------------------------------------------------- # models (utils) +def mesh_bmesh_poly_elems(poly, elems): + vert_start = poly.loop_start + vert_total = poly.loop_total + return elems[vert_start:vert_start + vert_total] -if IS_BMESH: - def mesh_bmesh_poly_elems(poly, elems): - vert_start = poly.loop_start - vert_total = poly.loop_total - return elems[vert_start:vert_start + vert_total] - - def mesh_bmesh_poly_vertices(poly): - return [loop.vertex_index - for loop in mesh_bmesh_poly_elems(poly, poly.id_data.loops)] +def mesh_bmesh_poly_vertices(poly): + return [loop.vertex_index + for loop in mesh_bmesh_poly_elems(poly, poly.id_data.loops)] def mesh_bounds(mesh): @@ -258,21 +250,14 @@ def mesh_uv_add(obj): uv_lay = obj.data.uv_textures.new() - if IS_BMESH: - # XXX, odd that we need to do this. until UV's and texface - # are separated we will need to keep it - uv_loops = obj.data.uv_layers[-1] - uv_list = uv_loops.data[:] - for poly in obj.data.polygons: - poly_uvs = mesh_bmesh_poly_elems(poly, uv_list) - for i, c in enumerate(poly_uvs): - c.uv = uvs[i % 4] - else: - for uv in uv_lay.data: - uv.uv1 = uvs[0] - uv.uv2 = uvs[1] - uv.uv3 = uvs[2] - uv.uv4 = uvs[3] + # XXX, odd that we need to do this. until UV's and texface + # are separated we will need to keep it + uv_loops = obj.data.uv_layers[-1] + uv_list = uv_loops.data[:] + for poly in obj.data.polygons: + poly_uvs = mesh_bmesh_poly_elems(poly, uv_list) + for i, c in enumerate(poly_uvs): + c.uv = uvs[i % 4] return uv_lay @@ -296,21 +281,12 @@ def mesh_vcol_add(obj, mode=0): mesh = obj.data - if IS_BMESH: - col_list = vcol_lay.data[:] - for poly in mesh.polygons: - face_verts = mesh_bmesh_poly_vertices(poly) - poly_cols = mesh_bmesh_poly_elems(poly, col_list) - for i, c in enumerate(poly_cols): - c.color = colors_get(face_verts[i]) - else: - for i, col in enumerate(vcol_lay.data): - face_verts = mesh.faces[i].vertices - col.color1 = colors_get(face_verts[0]) - col.color2 = colors_get(face_verts[1]) - col.color3 = colors_get(face_verts[2]) - if len(face_verts) == 4: - col.color4 = colors_get(face_verts[3]) + col_list = vcol_lay.data[:] + for poly in mesh.polygons: + face_verts = mesh_bmesh_poly_vertices(poly) + poly_cols = mesh_bmesh_poly_elems(poly, col_list) + for i, c in enumerate(poly_cols): + c.color = colors_get(face_verts[i]) return vcol_lay @@ -470,10 +446,7 @@ def modifier_build_add(scene, obj): defaults_modifier(mod) # ensure we display some faces - if IS_BMESH: - totface = len(obj.data.polygons) - else: - totface = len(obj.data.faces) + totface = len(obj.data.polygons) mod.frame_start = totface // 2 mod.frame_duration = totface diff --git a/tests/python/bl_pyapi_bpy_utils_units.py b/tests/python/bl_pyapi_bpy_utils_units.py index f40dab4b5eb..251419cb9ef 100644 --- a/tests/python/bl_pyapi_bpy_utils_units.py +++ b/tests/python/bl_pyapi_bpy_utils_units.py @@ -32,18 +32,17 @@ class UnitsTesting(unittest.TestCase): OUTPUT_TESTS = ( # system, type, prec, sep, compat, value, output ##### LENGTH + # Note: precision handling is a bit complicated when using multi-units... ('IMPERIAL', 'LENGTH', 3, False, False, 0.3048, "1'"), ('IMPERIAL', 'LENGTH', 3, False, True, 0.3048, "1ft"), - ('IMPERIAL', 'LENGTH', 3, True, False, 0.3048 * 2 + 0.0254 * 5.5, "2' 5.5\""), - # Those next two fail, here again because precision ignores order magnitude :/ - #('IMPERIAL', 'LENGTH', 3, False, False, 1609.344 * 1e6, "1000000mi"), # == 1000000.004mi!!! - #('IMPERIAL', 'LENGTH', 6, False, False, 1609.344 * 1e6, "1000000mi"), # == 1000000.003641mi!!! - ('METRIC', 'LENGTH', 3, True, False, 1000 * 2 + 0.001 * 15, "2km 1.5cm"), - ('METRIC', 'LENGTH', 3, True, False, 1234.56789, "1km 234.6m"), - # Note: precision seems basically unused when using multi units! - ('METRIC', 'LENGTH', 9, True, False, 1234.56789, "1km 234.6m"), - ('METRIC', 'LENGTH', 9, False, False, 1234.56789, "1.23456789km"), - ('METRIC', 'LENGTH', 9, True, False, 1000.000123456789, "1km 0.1mm"), + ('IMPERIAL', 'LENGTH', 4, True, False, 0.3048 * 2 + 0.0254 * 5.5, "2' 5.5\""), + ('IMPERIAL', 'LENGTH', 3, False, False, 1609.344 * 1e6, "1000000mi"), + ('IMPERIAL', 'LENGTH', 6, False, False, 1609.344 * 1e6, "1000000mi"), + ('METRIC', 'LENGTH', 3, True, False, 1000 * 2 + 0.001 * 15, "2km 2cm"), + ('METRIC', 'LENGTH', 5, True, False, 1234.56789, "1km 234.6m"), + ('METRIC', 'LENGTH', 6, True, False, 1234.56789, "1km 234.57m"), + ('METRIC', 'LENGTH', 9, False, False, 1234.56789, "1.234568km"), + ('METRIC', 'LENGTH', 9, True, False, 1000.000123456789, "1km 0.123mm"), ) def test_units_inputs(self): diff --git a/tests/python/bl_pyapi_idprop.py b/tests/python/bl_pyapi_idprop.py new file mode 100644 index 00000000000..7bf68c16cc7 --- /dev/null +++ b/tests/python/bl_pyapi_idprop.py @@ -0,0 +1,204 @@ +# Apache License, Version 2.0 + +# ./blender.bin --background -noaudio --python tests/python/bl_pyapi_idprop.py -- --verbose +import bpy +import unittest +import numpy as np +from array import array + + +class TestHelper: + + @property + def id(self): + return self._id + + def setUp(self): + self._id = bpy.context.scene + assert(len(self._id.keys()) == 0) + + def tearDown(self): + for key in list(self._id.keys()): + del self._id[key] + + def assertAlmostEqualSeq(self, list1, list2): + self.assertEqual(len(list1), len(list2)) + for v1, v2 in zip(list1, list2): + self.assertAlmostEqual(v1, v2, places=5) + + +class TestIdPropertyCreation(TestHelper, unittest.TestCase): + + def test_name_empty(self): + self.id[""] = 4 + self.assertEqual(self.id[""], 4) + + def test_name_too_long(self): + with self.assertRaises(KeyError): + self.id["name" * 30] = 4 + + def test_int(self): + self.id["a"] = 2 + self.assertEqual(self.id["a"], 2) + self.assertTrue(isinstance(self.id["a"], int)) + + with self.assertRaises(OverflowError): + self.id["a"] = 2 ** 31 # integer <= 2 ** 31-1 + + def test_double(self): + self.id["a"] = 2.5 + self.assertEqual(self.id["a"], 2.5) + self.assertTrue(isinstance(self.id["a"], float)) + + def test_unicode(self): + self.id["a"] = "Hello World" + self.assertEqual(self.id["a"], "Hello World") + self.assertTrue(isinstance(self.id["a"], str)) + + def test_bytes(self): + self.id["a"] = b"Hello World" + self.assertEqual(self.id["a"], b"Hello World") + self.assertTrue(isinstance(self.id["a"], bytes)) + + def test_sequence_double_list(self): + mylist = [1.2, 3.4, 5.6] + self.id["a"] = mylist + self.assertEqual(self.id["a"].to_list(), mylist) + self.assertEqual(self.id["a"].typecode, "d") + + def test_sequence_int_list(self): + mylist = [1, 2, 3] + self.id["a"] = mylist + self.assertEqual(self.id["a"].to_list(), mylist) + self.assertEqual(self.id["a"].typecode, "i") + + def test_sequence_float_array(self): + mylist = [1.2, 3.4, 5.6] + self.id["a"] = array("f", mylist) + self.assertAlmostEqualSeq(self.id["a"].to_list(), mylist) + self.assertEqual(self.id["a"].typecode, "f") + + def test_sequence_double_array(self): + mylist = [1.2, 3.4, 5.6] + self.id["a"] = array("d", mylist) + self.assertAlmostEqualSeq(self.id["a"].to_list(), mylist) + self.assertEqual(self.id["a"].typecode, "d") + + def test_sequence_int_array(self): + mylist = [1, 2, 3] + self.id["a"] = array("i", mylist) + self.assertAlmostEqualSeq(self.id["a"].to_list(), mylist) + self.assertEqual(self.id["a"].typecode, "i") + + def test_sequence_other_array(self): + mylist = [1, 2, 3] + self.id["a"] = array("Q", mylist) + self.assertEqual(self.id["a"].to_list(), mylist) + + def test_sequence_mixed_numerical_type(self): + self.id["a"] = [1, 2, 3.4, 5] + self.assertAlmostEqualSeq(self.id["a"].to_list(), [1.0, 2.0, 3.4, 5.0]) + self.assertEqual(self.id["a"].typecode, "d") + + def test_sequence_str_list(self): + # I'm a bit surprised that this works + mylist = ["abc", "qwe"] + self.id["a"] = mylist + self.assertEqual(self.id["a"], mylist) + + def test_sequence_mixed_type(self): + with self.assertRaises(TypeError): + mylist = ["abc", 3, "qwe", 3.4] + self.id["a"] = mylist + + def test_mapping_simple(self): + mydict = {"1": 10, "2": "20", "3": 30.5} + self.id["a"] = mydict + self.assertEqual(self.id["a"]["1"], mydict["1"]) + self.assertEqual(self.id["a"]["2"], mydict["2"]) + self.assertEqual(self.id["a"]["3"], mydict["3"]) + + def test_mapping_complex(self): + mydict = { + "1": [1, 2, 3], + "2": {"1": "abc", "2": array("i", [4, 5, 6])}, + "3": {"1": {"1": 10}, "2": b"qwe"}, + } + self.id["a"] = mydict + self.assertEqual(self.id["a"]["1"].to_list(), [1, 2, 3]) + self.assertEqual(self.id["a"]["2"]["1"], "abc") + self.assertEqual(self.id["a"]["2"]["2"].to_list(), [4, 5, 6]) + self.assertEqual(self.id["a"]["3"]["1"]["1"], 10) + self.assertEqual(self.id["a"]["3"]["2"], b"qwe") + + with self.assertRaises(KeyError): + a = self.id["a"]["2"]["a"] + + def test_invalid_type(self): + with self.assertRaises(TypeError): + self.id["a"] = self + + +class TestBufferProtocol(TestHelper, unittest.TestCase): + + def test_int(self): + self.id["a"] = array("i", [1, 2, 3, 4, 5]) + a = np.frombuffer(self.id["a"], self.id["a"].typecode) + self.assertEqual(len(a), 5) + a[2] = 10 + self.assertEqual(self.id["a"].to_list(), [1, 2, 10, 4, 5]) + + def test_float(self): + self.id["a"] = array("f", [1.0, 2.0, 3.0, 4.0]) + a = np.frombuffer(self.id["a"], self.id["a"].typecode) + self.assertEqual(len(a), 4) + a[-1] = 10 + self.assertEqual(self.id["a"].to_list(), [1.0, 2.0, 3.0, 10.0]) + + def test_double(self): + self.id["a"] = array("d", [1.0, 2.0, 3.0, 4.0]) + a = np.frombuffer(self.id["a"], self.id["a"].typecode) + a[1] = 10 + self.assertEqual(self.id["a"].to_list(), [1.0, 10.0, 3.0, 4.0]) + + def test_full_update(self): + self.id["a"] = array("i", [1, 2, 3, 4, 5, 6]) + a = np.frombuffer(self.id["a"], self.id["a"].typecode) + a[:] = [10, 20, 30, 40, 50, 60] + self.assertEqual(self.id["a"].to_list(), [10, 20, 30, 40, 50, 60]) + + def test_partial_update(self): + self.id["a"] = array("i", [1, 2, 3, 4, 5, 6, 7, 8]) + a = np.frombuffer(self.id["a"], self.id["a"].typecode) + a[1:5] = [10, 20, 30, 40] + self.assertEqual(self.id["a"].to_list(), [1, 10, 20, 30, 40, 6, 7, 8]) + + def test_copy(self): + self.id["a"] = array("i", [1, 2, 3, 4, 5]) + self.id["b"] = self.id["a"] + self.assertEqual(self.id["a"].to_list(), self.id["b"].to_list()) + + def test_memview_attributes(self): + mylist = [1, 2, 3] + self.id["a"] = mylist + + view1 = memoryview(self.id["a"]) + view2 = memoryview(array("i", mylist)) + + self.assertEqualMemviews(view1, view2) + + def assertEqualMemviews(self, view1, view2): + props_to_compare = ( + "contiguous", "format", "itemsize", "nbytes", "ndim", + "readonly", "shape", "strides", "suboffsets" + ) + for attr in props_to_compare: + self.assertEqual(getattr(view1, attr), getattr(view2, attr)) + + self.assertEqual(list(view1), list(view2)) + self.assertEqual(view1.tobytes(), view2.tobytes()) + +if __name__ == '__main__': + import sys + sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []) + unittest.main() diff --git a/tests/python/bl_pyapi_idprop_datablock.py b/tests/python/bl_pyapi_idprop_datablock.py new file mode 100644 index 00000000000..4acfb83bd95 --- /dev/null +++ b/tests/python/bl_pyapi_idprop_datablock.py @@ -0,0 +1,338 @@ +# ##### 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. +# +# ##### END GPL LICENSE BLOCK ##### + +import bpy +import sys +import os +import tempfile +import traceback +import inspect +from bpy.types import UIList + +arr_len = 100 +ob_cp_count = 100 +lib_path = os.path.join(tempfile.gettempdir(), "lib.blend") +test_path = os.path.join(tempfile.gettempdir(), "test.blend") + + +def print_fail_msg_and_exit(msg): + def __LINE__(): + try: + raise Exception + except: + return sys.exc_info()[2].tb_frame.f_back.f_back.f_back.f_lineno + + def __FILE__(): + return inspect.currentframe().f_code.co_filename + + print("'%s': %d >> %s" % (__FILE__(), __LINE__(), msg), file=sys.stderr) + sys.stderr.flush() + sys.stdout.flush() + os._exit(1) + + +def abort_if_false(expr, msg=None): + if not expr: + if not msg: + msg = "test failed" + print_fail_msg_and_exit(msg) + + +class TestClass(bpy.types.PropertyGroup): + test_prop = bpy.props.PointerProperty(type=bpy.types.Object) + name = bpy.props.StringProperty() + + +def get_scene(lib_name, sce_name): + for s in bpy.data.scenes: + if s.name == sce_name: + if (s.library and s.library.name == lib_name) or \ + (lib_name == None and s.library == None): + return s + + +def check_crash(fnc, args=None): + try: + fnc(args) if args else fnc() + except: + return + print_fail_msg_and_exit("test failed") + + +def init(): + bpy.utils.register_class(TestClass) + bpy.types.Object.prop_array = bpy.props.CollectionProperty( + name="prop_array", + type=TestClass) + bpy.types.Object.prop = bpy.props.PointerProperty(type=bpy.types.Object) + + +def make_lib(): + bpy.ops.wm.read_factory_settings() + + # datablock pointer to the Camera object + bpy.data.objects["Cube"].prop = bpy.data.objects['Camera'] + + # array of datablock pointers to the Lamp object + for i in range(0, arr_len): + a = bpy.data.objects["Cube"].prop_array.add() + a.test_prop = bpy.data.objects['Lamp'] + a.name = a.test_prop.name + + # make unique named copy of the cube + ob = bpy.data.objects["Cube"].copy() + bpy.context.scene.objects.link(ob) + + bpy.data.objects["Cube.001"].name = "Unique_Cube" + + # duplicating of Cube + for i in range(0, ob_cp_count): + ob = bpy.data.objects["Cube"].copy() + bpy.context.scene.objects.link(ob) + + # nodes + bpy.data.scenes["Scene"].use_nodes = True + bpy.data.scenes["Scene"].node_tree.nodes['Render Layers']["prop"] =\ + bpy.data.objects['Camera'] + + # rename scene and save + bpy.data.scenes["Scene"].name = "Scene_lib" + bpy.ops.wm.save_as_mainfile(filepath=lib_path) + + +def check_lib(): + # check pointer + abort_if_false(bpy.data.objects["Cube"].prop == bpy.data.objects['Camera']) + + # check array of pointers in duplicated object + for i in range(0, arr_len): + abort_if_false(bpy.data.objects["Cube.001"].prop_array[i].test_prop == + bpy.data.objects['Lamp']) + + +def check_lib_linking(): + # open startup file + bpy.ops.wm.read_factory_settings() + + # link scene to the startup file + with bpy.data.libraries.load(lib_path, link=True) as (data_from, data_to): + data_to.scenes = ["Scene_lib"] + + o = bpy.data.scenes["Scene_lib"].objects['Unique_Cube'] + + abort_if_false(o.prop_array[0].test_prop == bpy.data.scenes["Scene_lib"].objects['Lamp']) + abort_if_false(o.prop == bpy.data.scenes["Scene_lib"].objects['Camera']) + abort_if_false(o.prop.library == o.library) + + bpy.ops.wm.save_as_mainfile(filepath=test_path) + + +def check_linked_scene_copying(): + # full copy of the scene with datablock props + bpy.ops.wm.open_mainfile(filepath=test_path) + bpy.data.screens['Default'].scene = bpy.data.scenes["Scene_lib"] + bpy.ops.scene.new(type='FULL_COPY') + + # check save/open + bpy.ops.wm.save_as_mainfile(filepath=test_path) + bpy.ops.wm.open_mainfile(filepath=test_path) + + intern_sce = get_scene(None, "Scene_lib") + extern_sce = get_scene("Lib", "Scene_lib") + + # check node's props + # we made full copy from linked scene, so pointers must equal each other + abort_if_false(intern_sce.node_tree.nodes['Render Layers']["prop"] and + intern_sce.node_tree.nodes['Render Layers']["prop"] == + extern_sce.node_tree.nodes['Render Layers']["prop"]) + + +def check_scene_copying(): + # full copy of the scene with datablock props + bpy.ops.wm.open_mainfile(filepath=lib_path) + bpy.data.screens['Default'].scene = bpy.data.scenes["Scene_lib"] + bpy.ops.scene.new(type='FULL_COPY') + + path = test_path + "_" + # check save/open + bpy.ops.wm.save_as_mainfile(filepath=path) + bpy.ops.wm.open_mainfile(filepath=path) + + first_sce = get_scene(None, "Scene_lib") + second_sce = get_scene(None, "Scene_lib.001") + + # check node's props + # must point to own scene camera + abort_if_false(not (first_sce.node_tree.nodes['Render Layers']["prop"] == + second_sce.node_tree.nodes['Render Layers']["prop"])) + + +# count users +def test_users_counting(): + bpy.ops.wm.read_factory_settings() + lamp_us = bpy.data.objects["Lamp"].data.users + n = 1000 + for i in range(0, n): + bpy.data.objects["Cube"]["a%s" % i] = bpy.data.objects["Lamp"].data + abort_if_false(bpy.data.objects["Lamp"].data.users == lamp_us + n) + + for i in range(0, int(n / 2)): + bpy.data.objects["Cube"]["a%s" % i] = 1 + abort_if_false(bpy.data.objects["Lamp"].data.users == lamp_us + int(n / 2)) + + +# linking +def test_linking(): + make_lib() + check_lib() + check_lib_linking() + check_linked_scene_copying() + check_scene_copying() + + +# check restrictions for datablock pointers for some classes; GUI for manual testing +def test_restrictions1(): + class TEST_Op(bpy.types.Operator): + bl_idname = 'scene.test_op' + bl_label = 'Test' + bl_options = {"INTERNAL"} + str_prop = bpy.props.StringProperty(name="str_prop") + + # disallow registration of datablock properties in operators + # will be checked in the draw method (test manually) + # also, see console: + # ValueError: bpy_struct "SCENE_OT_test_op" doesn't support datablock properties + id_prop = bpy.props.PointerProperty(type=bpy.types.Object) + + def execute(self, context): + return {'FINISHED'} + + # just panel for testing the poll callback with lots of objects + class TEST_PT_DatablockProp(bpy.types.Panel): + bl_label = "Datablock IDProp" + bl_space_type = "PROPERTIES" + bl_region_type = "WINDOW" + bl_context = "render" + + def draw(self, context): + self.layout.prop_search(context.scene, "prop", bpy.data, + "objects") + self.layout.template_ID(context.scene, "prop1") + self.layout.prop_search(context.scene, "prop2", bpy.data, "node_groups") + + op = self.layout.operator("scene.test_op") + op.str_prop = "test string" + + def test_fnc(op): + op["ob"] = bpy.data.objects['Unique_Cube'] + check_crash(test_fnc, op) + abort_if_false(not hasattr(op, "id_prop")) + + bpy.utils.register_class(TEST_PT_DatablockProp) + bpy.utils.register_class(TEST_Op) + + def poll(self, value): + return value.name in bpy.data.scenes["Scene_lib"].objects + + def poll1(self, value): + return True + + bpy.types.Scene.prop = bpy.props.PointerProperty(type=bpy.types.Object) + bpy.types.Scene.prop1 = bpy.props.PointerProperty(type=bpy.types.Object, poll=poll) + bpy.types.Scene.prop2 = bpy.props.PointerProperty(type=bpy.types.NodeTree, poll=poll1) + + # check poll effect on UI (poll returns false => red alert) + bpy.context.scene.prop = bpy.data.objects["Lamp.001"] + bpy.context.scene.prop1 = bpy.data.objects["Lamp.001"] + + # check incorrect type assignment + def sub_test(): + # NodeTree id_prop + bpy.context.scene.prop2 = bpy.data.objects["Lamp.001"] + + check_crash(sub_test) + + bpy.context.scene.prop2 = bpy.data.node_groups.new("Shader", "ShaderNodeTree") + + print("Please, test GUI performance manually on the Render tab, '%s' panel" % + TEST_PT_DatablockProp.bl_label, file=sys.stderr) + sys.stderr.flush() + + +# check some possible regressions +def test_regressions(): + bpy.types.Object.prop_str = bpy.props.StringProperty(name="str") + bpy.data.objects["Unique_Cube"].prop_str = "test" + + bpy.types.Object.prop_gr = bpy.props.PointerProperty( + name="prop_gr", + type=TestClass, + description="test") + + bpy.data.objects["Unique_Cube"].prop_gr = None + + +# test restrictions for datablock pointers +def test_restrictions2(): + class TestClassCollection(bpy.types.PropertyGroup): + prop = bpy.props.CollectionProperty( + name="prop_array", + type=TestClass) + bpy.utils.register_class(TestClassCollection) + + class TestPrefs(bpy.types.AddonPreferences): + bl_idname = "testprefs" + # expecting crash during registering + my_prop2 = bpy.props.PointerProperty(type=TestClass) + + prop = bpy.props.PointerProperty( + name="prop", + type=TestClassCollection, + description="test") + + bpy.types.Addon.a = bpy.props.PointerProperty(type=bpy.types.Object) + + class TestUIList(UIList): + test = bpy.props.PointerProperty(type=bpy.types.Object) + def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index): + layout.prop(item, "name", text="", emboss=False, icon_value=icon) + + check_crash(bpy.utils.register_class, TestPrefs) + check_crash(bpy.utils.register_class, TestUIList) + + bpy.utils.unregister_class(TestClassCollection) + + +def main(): + init() + test_users_counting() + test_linking() + test_restrictions1() + check_crash(test_regressions) + test_restrictions2() + + +if __name__ == "__main__": + try: + main() + except: + import traceback + + traceback.print_exc() + sys.stderr.flush() + os._exit(1) diff --git a/tests/python/bl_pyapi_mathutils.py b/tests/python/bl_pyapi_mathutils.py index 7761b6cb7b1..9ca0376192a 100644 --- a/tests/python/bl_pyapi_mathutils.py +++ b/tests/python/bl_pyapi_mathutils.py @@ -260,6 +260,11 @@ class KDTreeTesting(unittest.TestCase): k.balance() return k + def assertAlmostEqualVector(self, first, second, places=7, msg=None, delta=None): + self.assertAlmostEqual(first[0], second[0], places=places, msg=msg, delta=delta) + self.assertAlmostEqual(first[1], second[1], places=places, msg=msg, delta=delta) + self.assertAlmostEqual(first[2], second[2], places=places, msg=msg, delta=delta) + def test_kdtree_single(self): co = (0,) * 3 index = 2 @@ -360,12 +365,12 @@ class KDTreeTesting(unittest.TestCase): ret_regular = k_odd.find(co) self.assertEqual(ret_regular[1] % 2, 1) ret_filter = k_all.find(co, lambda i: (i % 2) == 1) - self.assertEqual(ret_regular, ret_filter) + self.assertAlmostEqualVector(ret_regular, ret_filter) ret_regular = k_evn.find(co) self.assertEqual(ret_regular[1] % 2, 0) ret_filter = k_all.find(co, lambda i: (i % 2) == 0) - self.assertEqual(ret_regular, ret_filter) + self.assertAlmostEqualVector(ret_regular, ret_filter) # filter out all values (search odd tree for even values and the reverse) diff --git a/tests/python/bl_run_operators.py b/tests/python/bl_run_operators.py index 7e92b424faa..7b6b97e5ad1 100644 --- a/tests/python/bl_run_operators.py +++ b/tests/python/bl_run_operators.py @@ -65,6 +65,7 @@ op_blacklist = ( "wm.blenderplayer_start", "wm.recover_auto_save", "wm.quit_blender", + "wm.window_close", "wm.url_open", "wm.doc_view", "wm.doc_edit", @@ -99,10 +100,8 @@ def blend_list(mainpath): def file_list(path, filename_check=None): for dirpath, dirnames, filenames in os.walk(path): - - # skip '.svn' - if dirpath.startswith("."): - continue + # skip '.git' + dirnames[:] = [d for d in dirnames if not d.startswith(".")] for filename in filenames: filepath = join(dirpath, filename) @@ -308,16 +307,7 @@ def run_ops(operators, setup_func=None, reset=True): # contexts def ctx_clear_scene(): # copied from batch_import.py - unique_obs = set() - for scene in bpy.data.scenes: - for obj in scene.objects[:]: - scene.objects.unlink(obj) - unique_obs.add(obj) - - # remove obdata, for now only worry about the startup scene - for bpy_data_iter in (bpy.data.objects, bpy.data.meshes, bpy.data.lamps, bpy.data.cameras): - for id_data in bpy_data_iter: - bpy_data_iter.remove(id_data) + bpy.ops.wm.read_factory_settings(use_empty=True) def ctx_editmode_mesh(): diff --git a/tests/python/cycles_render_tests.py b/tests/python/cycles_render_tests.py index 78b4b346f24..731996df8ef 100755 --- a/tests/python/cycles_render_tests.py +++ b/tests/python/cycles_render_tests.py @@ -2,27 +2,112 @@ # Apache License, Version 2.0 import argparse +import glob import os +import pathlib +import shlex import shutil import subprocess import sys +import time import tempfile +class COLORS_ANSI: + RED = '\033[00;31m' + GREEN = '\033[00;32m' + ENDC = '\033[0m' + + +class COLORS_DUMMY: + RED = '' + GREEN = '' + ENDC = '' + +COLORS = COLORS_DUMMY + + +def print_message(message, type=None, status=''): + if type == 'SUCCESS': + print(COLORS.GREEN, end="") + elif type == 'FAILURE': + print(COLORS.RED, end="") + status_text = ... + if status == 'RUN': + status_text = " RUN " + elif status == 'OK': + status_text = " OK " + elif status == 'PASSED': + status_text = " PASSED " + elif status == 'FAILED': + status_text = " FAILED " + else: + status_text = status + if status_text: + print("[{}]" . format(status_text), end="") + print(COLORS.ENDC, end="") + print(" {}" . format(message)) + sys.stdout.flush() + + def render_file(filepath): - command = ( - BLENDER, - "--background", - "-noaudio", - "--factory-startup", - filepath, - "-E", "CYCLES", - # Run with OSL enabled - # "--python-expr", "import bpy; bpy.context.scene.cycles.shading_system = True", - "-o", TEMP_FILE_MASK, - "-F", "PNG", - "-f", "1", - ) + dirname = os.path.dirname(filepath) + basedir = os.path.dirname(dirname) + subject = os.path.basename(dirname) + + custom_args = os.getenv('CYCLESTEST_ARGS') + custom_args = shlex.split(custom_args) if custom_args else [] + + # OSL and GPU examples + # custom_args += ["--python-expr", "import bpy; bpy.context.scene.cycles.shading_system = True"] + # custom_args += ["--python-expr", "import bpy; bpy.context.scene.cycles.device = 'GPU'"] + + if subject == 'opengl': + command = [ + BLENDER, + "--window-geometry", "0", "0", "1", "1", + "-noaudio", + "--factory-startup", + "--enable-autoexec", + filepath, + "-E", "CYCLES"] + command += custom_args + command += [ + "-o", TEMP_FILE_MASK, + "-F", "PNG", + '--python', os.path.join(basedir, + "util", + "render_opengl.py")] + elif subject == 'bake': + command = [ + BLENDER, + "-b", + "-noaudio", + "--factory-startup", + "--enable-autoexec", + filepath, + "-E", "CYCLES"] + command += custom_args + command += [ + "-o", TEMP_FILE_MASK, + "-F", "PNG", + '--python', os.path.join(basedir, + "util", + "render_bake.py")] + else: + command = [ + BLENDER, + "--background", + "-noaudio", + "--factory-startup", + "--enable-autoexec", + filepath, + "-E", "CYCLES"] + command += custom_args + command += [ + "-o", TEMP_FILE_MASK, + "-F", "PNG", + "-f", "1"] try: output = subprocess.check_output(command) if VERBOSE: @@ -50,52 +135,241 @@ def test_get_name(filepath): filename = os.path.basename(filepath) return os.path.splitext(filename)[0] - -def verify_output(filepath): +def test_get_images(filepath): testname = test_get_name(filepath) dirpath = os.path.dirname(filepath) - reference_dirpath = os.path.join(dirpath, "reference_renders") - reference_image = os.path.join(reference_dirpath, testname + ".png") - failed_image = os.path.join(reference_dirpath, testname + ".fail.png") - if not os.path.exists(reference_image): - return False + + old_dirpath = os.path.join(dirpath, "reference_renders") + old_img = os.path.join(old_dirpath, testname + ".png") + + ref_dirpath = os.path.join(OUTDIR, os.path.basename(dirpath), "ref") + ref_img = os.path.join(ref_dirpath, testname + ".png") + if not os.path.exists(ref_dirpath): + os.makedirs(ref_dirpath) + if os.path.exists(old_img): + shutil.copy(old_img, ref_img) + + new_dirpath = os.path.join(OUTDIR, os.path.basename(dirpath)) + if not os.path.exists(new_dirpath): + os.makedirs(new_dirpath) + new_img = os.path.join(new_dirpath, testname + ".png") + + diff_dirpath = os.path.join(OUTDIR, os.path.basename(dirpath), "diff") + if not os.path.exists(diff_dirpath): + os.makedirs(diff_dirpath) + diff_img = os.path.join(diff_dirpath, testname + ".diff.png") + + return old_img, ref_img, new_img, diff_img + + +class Report: + def __init__(self, testname): + self.failed_tests = "" + self.passed_tests = "" + self.testname = testname + + def output(self): + # write intermediate data for single test + outdir = os.path.join(OUTDIR, self.testname) + if not os.path.exists(outdir): + os.makedirs(outdir) + + filepath = os.path.join(outdir, "failed.data") + pathlib.Path(filepath).write_text(self.failed_tests) + + filepath = os.path.join(outdir, "passed.data") + pathlib.Path(filepath).write_text(self.passed_tests) + + # gather intermediate data for all tests + failed_data = sorted(glob.glob(os.path.join(OUTDIR, "*/failed.data"))) + passed_data = sorted(glob.glob(os.path.join(OUTDIR, "*/passed.data"))) + + failed_tests = "" + passed_tests = "" + + for filename in failed_data: + filepath = os.path.join(OUTDIR, filename) + failed_tests += pathlib.Path(filepath).read_text() + for filename in passed_data: + filepath = os.path.join(OUTDIR, filename) + passed_tests += pathlib.Path(filepath).read_text() + + # write html for all tests + self.html = """ +<html> +<head> + <title>Cycles Test Report</title> + <style> + img {{ image-rendering: pixelated; width: 256px; background-color: #000; }} + img.render {{ + background-color: #fff; + background-image: + -moz-linear-gradient(45deg, #eee 25%, transparent 25%), + -moz-linear-gradient(-45deg, #eee 25%, transparent 25%), + -moz-linear-gradient(45deg, transparent 75%, #eee 75%), + -moz-linear-gradient(-45deg, transparent 75%, #eee 75%); + background-image: + -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.25, #eee), color-stop(.25, transparent)), + -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.25, #eee), color-stop(.25, transparent)), + -webkit-gradient(linear, 0 100%, 100% 0, color-stop(.75, transparent), color-stop(.75, #eee)), + -webkit-gradient(linear, 0 0, 100% 100%, color-stop(.75, transparent), color-stop(.75, #eee)); + + -moz-background-size:50px 50px; + background-size:50px 50px; + -webkit-background-size:50px 51px; /* override value for shitty webkit */ + + background-position:0 0, 25px 0, 25px -25px, 0px 25px; + }} + table td:first-child {{ width: 256px; }} + </style> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css"> +</head> +<body> + <div class="container"> + <br/> + <h1>Cycles Test Report</h1> + <br/> + <table class="table table-striped"> + <thead class="thead-default"> + <tr><th>Name</th><th>New</th><th>Reference</th><th>Diff</th> + </thead> + {}{} + </table> + <br/> + </div> +</body> +</html> + """ . format(failed_tests, passed_tests) + + filepath = os.path.join(OUTDIR, "report.html") + pathlib.Path(filepath).write_text(self.html) + + print_message("Report saved to: " + pathlib.Path(filepath).as_uri()) + + def relative_url(self, filepath): + relpath = os.path.relpath(filepath, OUTDIR) + return pathlib.Path(relpath).as_posix() + + def add_test(self, filepath, error): + name = test_get_name(filepath) + name = name.replace('_', ' ') + + old_img, ref_img, new_img, diff_img = test_get_images(filepath) + + status = error if error else "" + style = """ style="background-color: #f99;" """ if error else "" + + new_url = self.relative_url(new_img) + ref_url = self.relative_url(ref_img) + diff_url = self.relative_url(diff_img) + + test_html = """ + <tr{}> + <td><b>{}</b><br/>{}<br/>{}</td> + <td><img src="{}" onmouseover="this.src='{}';" onmouseout="this.src='{}';" class="render"></td> + <td><img src="{}" onmouseover="this.src='{}';" onmouseout="this.src='{}';" class="render"></td> + <td><img src="{}"></td> + </tr>""" . format(style, name, self.testname, status, + new_url, ref_url, new_url, + ref_url, new_url, ref_url, + diff_url) + + if error: + self.failed_tests += test_html + else: + self.passed_tests += test_html + + +def verify_output(report, filepath): + old_img, ref_img, new_img, diff_img = test_get_images(filepath) + + # copy new image + if os.path.exists(new_img): + os.remove(new_img) + if os.path.exists(TEMP_FILE): + shutil.copy(TEMP_FILE, new_img) + + update = os.getenv('CYCLESTEST_UPDATE') + + if os.path.exists(ref_img): + # diff test with threshold + command = ( + IDIFF, + "-fail", "0.016", + "-failpercent", "1", + ref_img, + TEMP_FILE, + ) + try: + subprocess.check_output(command) + failed = False + except subprocess.CalledProcessError as e: + if VERBOSE: + print_message(e.output.decode("utf-8")) + failed = e.returncode != 1 + else: + if not update: + return False + + failed = True + + if failed and update: + # update reference + shutil.copy(new_img, ref_img) + shutil.copy(new_img, old_img) + failed = False + + # generate diff image command = ( IDIFF, - "-fail", "0.015", - "-failpercent", "1", - reference_image, - TEMP_FILE, + "-o", diff_img, + "-abs", "-scale", "16", + ref_img, + TEMP_FILE ) + try: subprocess.check_output(command) - failed = False except subprocess.CalledProcessError as e: if VERBOSE: - print(e.output.decode("utf-8")) - failed = e.returncode != 1 - if failed: - shutil.copy(TEMP_FILE, failed_image) - elif os.path.exists(failed_image): - os.remove(failed_image) + print_message(e.output.decode("utf-8")) + return not failed -def run_test(filepath): +def run_test(report, filepath): testname = test_get_name(filepath) spacer = "." * (32 - len(testname)) - print(testname, spacer, end="") - sys.stdout.flush() + print_message(testname, 'SUCCESS', 'RUN') + time_start = time.time() error = render_file(filepath) + status = "FAIL" if not error: - if verify_output(filepath): - print("PASS") - else: + if not verify_output(report, filepath): error = "VERIFY" - if error: - print("FAIL", error) + time_end = time.time() + elapsed_ms = int((time_end - time_start) * 1000) + if not error: + print_message("{} ({} ms)" . format(testname, elapsed_ms), + 'SUCCESS', 'OK') + else: + if error == "NO_CYCLES": + print_message("Can't perform tests because Cycles failed to load!") + return error + elif error == "NO_START": + print_message('Can not perform tests because blender fails to start.', + 'Make sure INSTALL target was run.') + return error + elif error == 'VERIFY': + print_message("Rendered result is different from reference image") + else: + print_message("Unknown error %r" % error) + print_message("{} ({} ms)" . format(testname, elapsed_ms), + 'FAILURE', 'FAILED') return error + def blend_list(path): for dirpath, dirnames, filenames in os.walk(path): for filename in filenames: @@ -103,40 +377,54 @@ def blend_list(path): filepath = os.path.join(dirpath, filename) yield filepath - def run_all_tests(dirpath): + passed_tests = [] failed_tests = [] all_files = list(blend_list(dirpath)) all_files.sort() + report = Report(os.path.basename(dirpath)) + print_message("Running {} tests from 1 test case." . + format(len(all_files)), + 'SUCCESS', "==========") + time_start = time.time() for filepath in all_files: - error = run_test(filepath) + error = run_test(report, filepath) + testname = test_get_name(filepath) if error: if error == "NO_CYCLES": - print("Can't perform tests because Cycles failed to load!") return False elif error == "NO_START": - print('Can not perform tests because blender fails to start.', - 'Make sure INSTALL target was run.') return False - elif error == 'VERIFY': - pass - else: - print("Unknown error %r" % error) - testname = test_get_name(filepath) failed_tests.append(testname) + else: + passed_tests.append(testname) + report.add_test(filepath, error) + time_end = time.time() + elapsed_ms = int((time_end - time_start) * 1000) + print_message("") + print_message("{} tests from 1 test case ran. ({} ms total)" . + format(len(all_files), elapsed_ms), + 'SUCCESS', "==========") + print_message("{} tests." . + format(len(passed_tests)), + 'SUCCESS', 'PASSED') if failed_tests: + print_message("{} tests, listed below:" . + format(len(failed_tests)), + 'FAILURE', 'FAILED') failed_tests.sort() - print("\n\nFAILED tests:") for test in failed_tests: - print(" ", test) - return False - return True + print_message("{}" . format(test), 'FAILURE', "FAILED") + + report.output() + return not bool(failed_tests) def create_argparse(): parser = argparse.ArgumentParser() parser.add_argument("-blender", nargs="+") parser.add_argument("-testdir", nargs=1) + parser.add_argument("-outdir", nargs=1) parser.add_argument("-idiff", nargs=1) return parser @@ -145,13 +433,21 @@ def main(): parser = create_argparse() args = parser.parse_args() - global BLENDER, ROOT, IDIFF + global COLORS + global BLENDER, TESTDIR, IDIFF, OUTDIR global TEMP_FILE, TEMP_FILE_MASK, TEST_SCRIPT global VERBOSE + if os.environ.get("CYCLESTEST_COLOR") is not None: + COLORS = COLORS_ANSI + BLENDER = args.blender[0] - ROOT = args.testdir[0] + TESTDIR = args.testdir[0] IDIFF = args.idiff[0] + OUTDIR = args.outdir[0] + + if not os.path.exists(OUTDIR): + os.makedirs(OUTDIR) TEMP = tempfile.mkdtemp() TEMP_FILE_MASK = os.path.join(TEMP, "test") @@ -161,7 +457,7 @@ def main(): VERBOSE = os.environ.get("BLENDER_VERBOSE") is not None - ok = run_all_tests(ROOT) + ok = run_all_tests(TESTDIR) # Cleanup temp files and folders if os.path.exists(TEMP_FILE): diff --git a/tests/python/pep8.py b/tests/python/pep8.py index 0e6250f534b..dde4250f6aa 100644 --- a/tests/python/pep8.py +++ b/tests/python/pep8.py @@ -178,5 +178,6 @@ def main(): "--max-line-length=1000" " '%s'" % f) + if __name__ == "__main__": main() diff --git a/tests/python/rna_info_dump.py b/tests/python/rna_info_dump.py index c26d94a1246..da228e52652 100644 --- a/tests/python/rna_info_dump.py +++ b/tests/python/rna_info_dump.py @@ -127,5 +127,6 @@ def api_dump(use_properties=True, use_functions=True): print("END") + if __name__ == "__main__": api_dump() |