diff --git a/.travis.yml b/.travis.yml index c6facef2ba..781f498e2e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,8 +24,6 @@ addons: packages: [ # Dev cmake, clang-3.6, libunshield-dev, libtinyxml-dev, - # Tests - libgtest-dev, google-mock, # Boost libboost-filesystem-dev, libboost-program-options-dev, libboost-system-dev, # FFmpeg diff --git a/CI/before_install.linux.sh b/CI/before_install.linux.sh index 2b0e73110d..25d05e619c 100755 --- a/CI/before_install.linux.sh +++ b/CI/before_install.linux.sh @@ -1,11 +1,3 @@ #!/bin/sh sudo ln -s /usr/bin/clang-3.6 /usr/local/bin/clang sudo ln -s /usr/bin/clang++-3.6 /usr/local/bin/clang++ - -# build libgtest & libgtest_main -sudo mkdir /usr/src/gtest/build -cd /usr/src/gtest/build -sudo cmake .. -DBUILD_SHARED_LIBS=1 -sudo make -j4 -sudo ln -s /usr/src/gtest/build/libgtest.so /usr/lib/libgtest.so -sudo ln -s /usr/src/gtest/build/libgtest_main.so /usr/lib/libgtest_main.so diff --git a/CI/before_script.linux.sh b/CI/before_script.linux.sh index 93be1cb48e..dd879989a8 100755 --- a/CI/before_script.linux.sh +++ b/CI/before_script.linux.sh @@ -1,8 +1,21 @@ -#!/bin/sh +#!/bin/sh -e free -m + +env GENERATOR='Unix Makefiles' CONFIGURATION=Release CI/build_googletest.sh +GOOGLETEST_DIR="$(pwd)/googletest/build" + mkdir build cd build export CODE_COVERAGE=1 if [ "${CC}" = "clang" ]; then export CODE_COVERAGE=0; fi -${ANALYZE}cmake .. -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} -DBUILD_UNITTESTS=1 -DCMAKE_INSTALL_PREFIX=/usr -DBINDIR=/usr/games -DCMAKE_BUILD_TYPE="None" -DUSE_SYSTEM_TINYXML=TRUE +${ANALYZE}cmake \ + -DBUILD_WITH_CODE_COVERAGE=${CODE_COVERAGE} \ + -DBUILD_UNITTESTS=1 \ + -DCMAKE_INSTALL_PREFIX=/usr \ + -DBINDIR=/usr/games \ + -DCMAKE_BUILD_TYPE="None" \ + -DUSE_SYSTEM_TINYXML=TRUE \ + -DGTEST_ROOT="${GOOGLETEST_DIR}" \ + -DGMOCK_ROOT="${GOOGLETEST_DIR}" \ + .. diff --git a/CI/build_googletest.sh b/CI/build_googletest.sh new file mode 100755 index 0000000000..cd61379b59 --- /dev/null +++ b/CI/build_googletest.sh @@ -0,0 +1,13 @@ +#!/bin/sh -e + +git clone https://github.com/google/googletest.git +cd googletest +mkdir build +cd build +cmake \ + -D CMAKE_BUILD_TYPE="${CONFIGURATION}" \ + -D CMAKE_INSTALL_PREFIX=. \ + -G "${GENERATOR}" \ + .. +cmake --build . --config "${CONFIGURATION}" +cmake --build . --target install --config "${CONFIGURATION}" diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 48c8be4d8f..bc39bc2be6 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -1,7 +1,9 @@ find_package(GTest REQUIRED) +find_package(GMock REQUIRED) -if (GTEST_FOUND) +if (GTEST_FOUND AND GMOCK_FOUND) include_directories(SYSTEM ${GTEST_INCLUDE_DIRS}) + include_directories(SYSTEM ${GMOCK_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES ../openmw/mwworld/store.cpp @@ -13,17 +15,22 @@ if (GTEST_FOUND) esm/test_fixed_string.cpp misc/test_stringops.cpp + + nifloader/testbulletnifloader.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) openmw_add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) - target_link_libraries(openmw_test_suite ${GTEST_BOTH_LIBRARIES} components) + target_link_libraries(openmw_test_suite ${GMOCK_LIBRARIES} components) # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT}) endif() + + if (BUILD_WITH_CODE_COVERAGE) + add_definitions(--coverage) + target_link_libraries(openmw_test_suite gcov) + endif() endif() - - diff --git a/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp new file mode 100644 index 0000000000..a2311be490 --- /dev/null +++ b/apps/openmw_test_suite/nifloader/testbulletnifloader.cpp @@ -0,0 +1,951 @@ +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace +{ + template + bool compareObjects(const T* lhs, const T* rhs) + { + return (!lhs && !rhs) || (lhs && rhs && *lhs == *rhs); + } + + std::vector getTriangles(const btBvhTriangleMeshShape& shape) + { + std::vector result; + auto callback = BulletHelpers::makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { + for (std::size_t i = 0; i < 3; ++i) + result.push_back(triangle[i]); + }); + btVector3 aabbMin; + btVector3 aabbMax; + shape.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + shape.processAllTriangles(&callback, aabbMin, aabbMax); + return result; + } +} + +static std::ostream& operator <<(std::ostream& stream, const btVector3& value) +{ + return stream << "btVector3 {" + << std::setprecision(std::numeric_limits::max_exponent10) << value.getX() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << value.getY() << ", " + << std::setprecision(std::numeric_limits::max_exponent10) << value.getZ() << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btMatrix3x3& value) +{ + stream << "btMatrix3x3 {"; + for (int i = 0; i < 3; ++i) + stream << value.getRow(i) << ", "; + return stream << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btTransform& value) +{ + return stream << "btTransform {" << value.getBasis() << ", " << value.getOrigin() << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btCollisionShape* value); + +static std::ostream& operator <<(std::ostream& stream, const btCompoundShape& value) +{ + stream << "btCompoundShape {" << value.getLocalScaling() << ", "; + stream << "{"; + for (int i = 0; i < value.getNumChildShapes(); ++i) + stream << value.getChildShape(i) << ", "; + stream << "},"; + stream << "{"; + for (int i = 0; i < value.getNumChildShapes(); ++i) + stream << value.getChildTransform(i) << ", "; + stream << "}"; + return stream << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btBoxShape& value) +{ + return stream << "btBoxShape {" << value.getLocalScaling() << ", " << value.getHalfExtentsWithoutMargin() << "}"; +} + +namespace Resource +{ + +static std::ostream& operator <<(std::ostream& stream, const TriangleMeshShape& value) +{ + stream << "Resource::TriangleMeshShape {" << value.getLocalScaling() << ", " + << value.usesQuantizedAabbCompression() << ", " << value.getOwnsBvh() << ", {"; + auto callback = BulletHelpers::makeProcessTriangleCallback([&] (btVector3* triangle, int, int) { + for (std::size_t i = 0; i < 3; ++i) + stream << triangle[i] << ", "; + }); + btVector3 aabbMin; + btVector3 aabbMax; + value.getAabb(btTransform::getIdentity(), aabbMin, aabbMax); + value.processAllTriangles(&callback, aabbMin, aabbMax); + return stream << "}}"; +} + +} + +static std::ostream& operator <<(std::ostream& stream, const btCollisionShape& value) +{ + switch (value.getShapeType()) + { + case COMPOUND_SHAPE_PROXYTYPE: + return stream << static_cast(value); + case BOX_SHAPE_PROXYTYPE: + return stream << static_cast(value); + case TRIANGLE_MESH_SHAPE_PROXYTYPE: + if (const auto casted = dynamic_cast(&value)) + return stream << *casted; + break; + } + return stream << "btCollisionShape {" << value.getShapeType() << "}"; +} + +static std::ostream& operator <<(std::ostream& stream, const btCollisionShape* value) +{ + return value ? stream << "&" << *value : stream << "nullptr"; +} + +namespace osg +{ + static std::ostream& operator <<(std::ostream& stream, const Vec3f& value) + { + return stream << "osg::Vec3f {" + << value.x() << ", " + << value.y() << ", " + << value.z() << "}"; + } +} + +namespace std +{ + static std::ostream& operator <<(std::ostream& stream, const map& value) + { + stream << "std::map {"; + for (const auto& v : value) + stream << "{" << v.first << ", " << v.second << "},"; + return stream << "}"; + } +} + +namespace Resource +{ + static bool operator ==(const Resource::BulletShape& lhs, const Resource::BulletShape& rhs) + { + return compareObjects(lhs.mCollisionShape, rhs.mCollisionShape) + && lhs.mCollisionBoxHalfExtents == rhs.mCollisionBoxHalfExtents + && lhs.mCollisionBoxTranslate == rhs.mCollisionBoxTranslate + && lhs.mAnimatedShapes == rhs.mAnimatedShapes; + } + + static std::ostream& operator <<(std::ostream& stream, const Resource::BulletShape& value) + { + return stream << "Resource::BulletShape {" + << value.mCollisionShape << ", " + << value.mCollisionBoxHalfExtents << ", " + << value.mAnimatedShapes + << "}"; + } +} + +static bool operator ==(const btCollisionShape& lhs, const btCollisionShape& rhs); + +static bool operator ==(const btCompoundShape& lhs, const btCompoundShape& rhs) +{ + if (lhs.getNumChildShapes() != rhs.getNumChildShapes() || lhs.getLocalScaling() != rhs.getLocalScaling()) + return false; + for (int i = 0; i < lhs.getNumChildShapes(); ++i) + { + if (!compareObjects(lhs.getChildShape(i), rhs.getChildShape(i)) + || !(lhs.getChildTransform(i) == rhs.getChildTransform(i))) + return false; + } + return true; +} + +static bool operator ==(const btBoxShape& lhs, const btBoxShape& rhs) +{ + return lhs.getLocalScaling() == rhs.getLocalScaling() + && lhs.getHalfExtentsWithoutMargin() == rhs.getHalfExtentsWithoutMargin(); +} + +static bool operator ==(const btBvhTriangleMeshShape& lhs, const btBvhTriangleMeshShape& rhs) +{ + return lhs.getLocalScaling() == rhs.getLocalScaling() + && lhs.usesQuantizedAabbCompression() == rhs.usesQuantizedAabbCompression() + && lhs.getOwnsBvh() == rhs.getOwnsBvh() + && getTriangles(lhs) == getTriangles(rhs); +} + +static bool operator ==(const btCollisionShape& lhs, const btCollisionShape& rhs) +{ + if (lhs.getShapeType() != rhs.getShapeType()) + return false; + switch (lhs.getShapeType()) + { + case COMPOUND_SHAPE_PROXYTYPE: + return static_cast(lhs) == static_cast(rhs); + case BOX_SHAPE_PROXYTYPE: + return static_cast(lhs) == static_cast(rhs); + case TRIANGLE_MESH_SHAPE_PROXYTYPE: + if (const auto lhsCasted = dynamic_cast(&lhs)) + if (const auto rhsCasted = dynamic_cast(&rhs)) + return *lhsCasted == *rhsCasted; + return false; + } + return false; +} + +namespace +{ + using namespace testing; + using NifBullet::BulletNifLoader; + + void init(Nif::Transformation& value) + { + value = Nif::Transformation::getIdentity(); + } + + void init(Nif::Extra& value) + { + value.extra = Nif::ExtraPtr(nullptr); + } + + void init(Nif::Controlled& value) + { + init(static_cast(value)); + value.controller = Nif::ControllerPtr(nullptr); + } + + void init(Nif::Named& value) + { + init(static_cast(value)); + } + + void init(Nif::Node& value) + { + init(static_cast(value)); + value.flags = 0; + init(value.trafo); + value.hasBounds = false; + value.parent = nullptr; + value.isBone = false; + } + + void init(Nif::NiTriShape& value) + { + init(static_cast(value)); + value.recType = Nif::RC_NiTriShape; + value.data = Nif::NiTriShapeDataPtr(nullptr); + value.skin = Nif::NiSkinInstancePtr(nullptr); + } + + void init(Nif::NiSkinInstance& value) + { + value.data = Nif::NiSkinDataPtr(nullptr); + value.root = Nif::NodePtr(nullptr); + } + + void init(Nif::Controller& value) + { + value.next = Nif::ControllerPtr(nullptr); + value.flags = 0; + value.frequency = 0; + value.phase = 0; + value.timeStart = 0; + value.timeStop = 0; + value.target = Nif::ControlledPtr(nullptr); + } + + void copy(const btTransform& src, Nif::Transformation& dst) { + dst.pos = osg::Vec3f(src.getOrigin().x(), src.getOrigin().y(), src.getOrigin().z()); + for (int row = 0; row < 3; ++row) + for (int column = 0; column < 3; ++column) + dst.rotation.mValues[column][row] = src.getBasis().getRow(row)[column]; + } + + struct NifFileMock : Nif::File + { + MOCK_CONST_METHOD1(fail, void (const std::string&)); + MOCK_CONST_METHOD1(warn, void (const std::string&)); + MOCK_CONST_METHOD1(getRecord, Nif::Record* (std::size_t)); + MOCK_CONST_METHOD0(numRecords, std::size_t ()); + MOCK_CONST_METHOD1(getRoot, Nif::Record* (std::size_t)); + MOCK_CONST_METHOD0(numRoots, std::size_t ()); + MOCK_METHOD1(setUseSkinning, void (bool)); + MOCK_CONST_METHOD0(getUseSkinning, bool ()); + MOCK_CONST_METHOD0(getFilename, std::string ()); + }; + + struct RecordMock : Nif::Record + { + MOCK_METHOD1(read, void (Nif::NIFStream *nif)); + }; + + struct TestBulletNifLoader : Test + { + BulletNifLoader mLoader; + const StrictMock mNifFile; + Nif::Node mNode; + Nif::Node mNode2; + Nif::NiNode mNiNode; + Nif::NiNode mNiNode2; + Nif::NiNode mNiNode3; + Nif::NiTriShapeData mNiTriShapeData; + Nif::NiTriShape mNiTriShape; + Nif::NiTriShapeData mNiTriShapeData2; + Nif::NiTriShape mNiTriShape2; + Nif::NiSkinInstance mNiSkinInstance; + Nif::NiStringExtraData mNiStringExtraData; + Nif::NiStringExtraData mNiStringExtraData2; + Nif::Controller mController; + btTransform mTransform {btMatrix3x3(btQuaternion(btVector3(1, 0, 0), 0.5f)), btVector3(1, 2, 3)}; + btTransform mResultTransform { + btMatrix3x3( + 1, 0, 0, + 0, 0.82417738437652587890625, 0.56633174419403076171875, + 0, -0.56633174419403076171875, 0.82417738437652587890625 + ), + btVector3(1, 2, 3) + }; + btTransform mResultTransform2 { + btMatrix3x3( + 1, 0, 0, + 0, 0.7951543331146240234375, 0.606407105922698974609375, + 0, -0.606407105922698974609375, 0.7951543331146240234375 + ), + btVector3(4, 8, 12) + }; + + TestBulletNifLoader() + { + init(mNode); + init(mNode2); + init(mNiNode); + init(mNiNode2); + init(mNiNode3); + init(mNiTriShape); + init(mNiTriShape2); + init(mNiSkinInstance); + init(mNiStringExtraData); + init(mNiStringExtraData2); + init(mController); + + mNiTriShapeData.vertices = {osg::Vec3f(0, 0, 0), osg::Vec3f(1, 0, 0), osg::Vec3f(1, 1, 0)}; + mNiTriShapeData.triangles = {0, 1, 2}; + mNiTriShape.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData); + + mNiTriShapeData2.vertices = {osg::Vec3f(0, 0, 1), osg::Vec3f(1, 0, 1), osg::Vec3f(1, 1, 1)}; + mNiTriShapeData2.triangles = {0, 1, 2}; + mNiTriShape2.data = Nif::NiTriShapeDataPtr(&mNiTriShapeData2); + } + }; + + TEST_F(TestBulletNifLoader, for_zero_num_roots_should_return_default) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(0)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_not_nif_node_should_return_default) + { + StrictMock record; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&record)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_default_root_nif_node_should_return_default) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_default_root_collision_node_nif_node_should_return_default) + { + mNode.recType = Nif::RC_RootCollisionNode; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_default_root_nif_node_and_filename_starting_with_x_should_return_default) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounding_box_should_return_shape_with_compound_shape_and_box_inside) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_child_nif_node_with_bounding_box) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_and_child_nif_node_with_bounding_box_but_root_without_flag_should_use_child_bounds) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + mNiNode.hasBounds = true; + mNiNode.boundXYZ = osg::Vec3f(4, 5, 6); + mNiNode.boundPos = osg::Vec3f(-4, -5, -6); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_first_with_flag_should_use_first_bounds) + { + mNode.hasBounds = true; + mNode.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + mNode2.hasBounds = true; + mNode2.boundXYZ = osg::Vec3f(4, 5, 6); + mNode2.boundPos = osg::Vec3f(-4, -5, -6); + + mNiNode.hasBounds = true; + mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); + mNiNode.boundPos = osg::Vec3f(-7, -8, -9); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + std::unique_ptr box(new btBoxShape(btVector3(1, 2, 3))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-1, -2, -3)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_and_two_children_where_both_with_bounds_but_only_second_with_flag_should_use_second_bounds) + { + mNode.hasBounds = true; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + mNode2.hasBounds = true; + mNode2.flags |= Nif::NiNode::Flag_BBoxCollision; + mNode2.boundXYZ = osg::Vec3f(4, 5, 6); + mNode2.boundPos = osg::Vec3f(-4, -5, -6); + + mNiNode.hasBounds = true; + mNiNode.boundXYZ = osg::Vec3f(7, 8, 9); + mNiNode.boundPos = osg::Vec3f(-7, -8, -9); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNode), Nif::NodePtr(&mNode2)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(4, 5, 6); + expected.mCollisionBoxTranslate = osg::Vec3f(-4, -5, -6); + std::unique_ptr box(new btBoxShape(btVector3(4, 5, 6))); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(btTransform(btMatrix3x3::getIdentity(), btVector3(-4, -5, -6)), box.release()); + expected.mCollisionShape = shape.release(); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_nif_node_with_bounds_but_without_flag_should_return_shape_with_bounds_but_with_null_collision_shape) + { + mNode.hasBounds = true; + mNode.boundXYZ = osg::Vec3f(1, 2, 3); + mNode.boundPos = osg::Vec3f(-1, -2, -3); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_root_node_should_return_shape_with_triangle_mesh_shape) + { + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_root_node_with_bounds_should_return_shape_with_bounds_but_with_null_collision_shape) + { + mNiTriShape.hasBounds = true; + mNiTriShape.boundXYZ = osg::Vec3f(1, 2, 3); + mNiTriShape.boundPos = osg::Vec3f(-1, -2, -3); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + expected.mCollisionBoxHalfExtents = osg::Vec3f(1, 2, 3); + expected.mCollisionBoxTranslate = osg::Vec3f(-1, -2, -3); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_should_return_shape_with_triangle_mesh_shape) + { + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_nested_tri_shape_child_should_return_shape_with_triangle_mesh_shape) + { + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); + mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_two_tri_shape_children_should_return_shape_with_triangle_mesh_shape_with_all_meshes) + { + mNiNode.children = Nif::NodeList(std::vector({ + Nif::NodePtr(&mNiTriShape), + Nif::NodePtr(&mNiTriShape2) + })); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_and_not_empty_skin_should_return_shape_with_triangle_mesh_shape) + { + mNiTriShape.skin = Nif::NiSkinInstancePtr(&mNiSkinInstance); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_root_node_and_filename_starting_with_x_should_return_shape_with_compound_shape) + { + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiTriShape)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(3, 3, 3)); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform, mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_and_filename_starting_with_x_should_return_shape_with_compound_shape) + { + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + mNiTriShape.parent = &mNiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.trafo.scale = 4; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(12, 12, 12)); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform2, mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_two_tri_shape_children_nodes_and_filename_starting_with_x_should_return_shape_with_compound_shape) + { + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + + copy(mTransform, mNiTriShape2.trafo); + mNiTriShape2.trafo.scale = 3; + + mNiNode.children = Nif::NodeList(std::vector({ + Nif::NodePtr(&mNiTriShape), + Nif::NodePtr(&mNiTriShape2), + })); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("xtest.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(3, 3, 3)); + + std::unique_ptr triangles2(new btTriangleMesh(false)); + triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); + mesh2->setLocalScaling(btVector3(3, 3, 3)); + + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform, mesh.release()); + shape->addChildShape(mResultTransform, mesh2.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_controller_should_return_shape_with_compound_shape) + { + mController.recType = Nif::RC_NiKeyframeController; + mController.flags |= Nif::NiNode::ControllerFlag_Active; + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + mNiTriShape.parent = &mNiNode; + mNiTriShape.controller = Nif::ControllerPtr(&mController); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.trafo.scale = 4; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(12, 12, 12)); + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform2, mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_two_tri_shape_children_nodes_where_one_with_controller_should_return_shape_with_compound_shape) + { + mController.recType = Nif::RC_NiKeyframeController; + mController.flags |= Nif::NiNode::ControllerFlag_Active; + copy(mTransform, mNiTriShape.trafo); + mNiTriShape.trafo.scale = 3; + copy(mTransform, mNiTriShape2.trafo); + mNiTriShape2.trafo.scale = 3; + mNiTriShape2.parent = &mNiNode; + mNiTriShape2.controller = Nif::ControllerPtr(&mController); + mNiNode.children = Nif::NodeList(std::vector({ + Nif::NodePtr(&mNiTriShape), + Nif::NodePtr(&mNiTriShape2), + })); + mNiNode.trafo.scale = 4; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(1, 2, 3), btVector3(4, 2, 3), btVector3(4, 4.632747650146484375, 1.56172335147857666015625)); + std::unique_ptr mesh(new Resource::TriangleMeshShape(triangles.release(), true)); + mesh->setLocalScaling(btVector3(1, 1, 1)); + + std::unique_ptr triangles2(new btTriangleMesh(false)); + triangles2->addTriangle(btVector3(0, 0, 1), btVector3(1, 0, 1), btVector3(1, 1, 1)); + std::unique_ptr mesh2(new Resource::TriangleMeshShape(triangles2.release(), true)); + mesh2->setLocalScaling(btVector3(12, 12, 12)); + + std::unique_ptr shape(new btCompoundShape); + shape->addChildShape(mResultTransform2, mesh2.release()); + shape->addChildShape(btTransform::getIdentity(), mesh.release()); + Resource::BulletShape expected; + expected.mCollisionShape = shape.release(); + expected.mAnimatedShapes = {{-1, 0}}; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_root_avoid_node_and_tri_shape_child_node_should_return_shape_with_null_collision_shape) + { + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode.recType = Nif::RC_AvoidNode; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_should_return_shape_with_null_collision_shape) + { + mNiTriShape.data = Nif::NiTriShapeDataPtr(nullptr); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_empty_data_triangles_should_return_shape_with_null_collision_shape) + { + mNiTriShape.data->triangles.clear(); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape) + { + mNiStringExtraData.string = "NC___"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_not_first_extra_data_string_starting_with_nc_should_return_shape_with_null_collision_shape) + { + mNiStringExtraData.extra = Nif::ExtraPtr(&mNiStringExtraData2); + mNiStringExtraData2.string = "NC___"; + mNiStringExtraData2.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_mrk_should_return_shape_with_null_collision_shape) + { + mNiStringExtraData.string = "MRK"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + Resource::BulletShape expected; + + EXPECT_EQ(*result, expected); + } + + TEST_F(TestBulletNifLoader, for_tri_shape_child_node_with_extra_data_string_mrk_and_other_collision_node_should_return_shape_with_triangle_mesh_shape_with_all_meshes) + { + mNiStringExtraData.string = "MRK"; + mNiStringExtraData.recType = Nif::RC_NiStringExtraData; + mNiTriShape.extra = Nif::ExtraPtr(&mNiStringExtraData); + mNiNode3.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiTriShape)})); + mNiNode3.recType = Nif::RC_RootCollisionNode; + mNiNode2.children = Nif::NodeList(std::vector({Nif::NodePtr(nullptr), Nif::NodePtr(&mNiNode3)})); + mNiNode2.recType = Nif::RC_NiNode; + mNiNode.children = Nif::NodeList(std::vector({Nif::NodePtr(&mNiNode2)})); + mNiNode.recType = Nif::RC_NiNode; + + EXPECT_CALL(mNifFile, numRoots()).WillOnce(Return(1)); + EXPECT_CALL(mNifFile, getRoot(0)).WillOnce(Return(&mNiNode)); + EXPECT_CALL(mNifFile, getFilename()).WillOnce(Return("test.nif")); + const auto result = mLoader.load(mNifFile); + + std::unique_ptr triangles(new btTriangleMesh(false)); + triangles->addTriangle(btVector3(0, 0, 0), btVector3(1, 0, 0), btVector3(1, 1, 0)); + Resource::BulletShape expected; + expected.mCollisionShape = new Resource::TriangleMeshShape(triangles.release(), true); + + EXPECT_EQ(*result, expected); + } +} diff --git a/cmake/FindGMock.cmake b/cmake/FindGMock.cmake new file mode 100644 index 0000000000..8d73242423 --- /dev/null +++ b/cmake/FindGMock.cmake @@ -0,0 +1,515 @@ +# Get the Google C++ Mocking Framework. +# (This file is almost an copy of the original FindGTest.cmake file, +# altered to download and compile GMock and GTest if not found +# in GMOCK_ROOT or GTEST_ROOT respectively, +# feel free to use it as it is or modify it for your own needs.) +# +# Defines the following variables: +# +# GMOCK_FOUND - Found or got the Google Mocking framework +# GTEST_FOUND - Found or got the Google Testing framework +# GMOCK_INCLUDE_DIRS - GMock include directory +# GTEST_INCLUDE_DIRS - GTest include direcotry +# +# Also defines the library variables below as normal variables +# +# GMOCK_BOTH_LIBRARIES - Both libgmock & libgmock_main +# GMOCK_LIBRARIES - libgmock +# GMOCK_MAIN_LIBRARIES - libgmock-main +# +# GTEST_BOTH_LIBRARIES - Both libgtest & libgtest_main +# GTEST_LIBRARIES - libgtest +# GTEST_MAIN_LIBRARIES - libgtest_main +# +# Accepts the following variables as input: +# +# GMOCK_ROOT - The root directory of the gmock install prefix +# GTEST_ROOT - The root directory of the gtest install prefix +# GMOCK_SRC_DIR -The directory of the gmock sources +# GMOCK_VER - The version of the gmock sources to be downloaded +# +#----------------------- +# Example Usage: +# +# set(GMOCK_ROOT "~/gmock") +# find_package(GMock REQUIRED) +# include_directories(${GMOCK_INCLUDE_DIRS}) +# +# add_executable(foo foo.cc) +# target_link_libraries(foo ${GMOCK_BOTH_LIBRARIES}) +# +#============================================================================= +# Copyright (c) 2016 Michel Estermann +# Copyright (c) 2016 Kamil Strzempowicz +# Copyright (c) 2011 Matej Svec +# +# CMake - Cross Platform Makefile Generator +# Copyright 2000-2016 Kitware, Inc. +# Copyright 2000-2011 Insight Software Consortium +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the names of Kitware, Inc., the Insight Software Consortium, +# nor the names of their contributors may be used to endorse or promote +# products derived from this software without specific prior written +# permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# ------------------------------------------------------------------------------ +# +# The above copyright and license notice applies to distributions of +# CMake in source and binary form. Some source files contain additional +# notices of original copyright by their contributors; see each source +# for details. Third-party software packages supplied with CMake under +# compatible licenses provide their own copyright notices documented in +# corresponding subdirectories. +# +# ------------------------------------------------------------------------------ +# +# CMake was initially developed by Kitware with the following sponsorship: +# +# * National Library of Medicine at the National Institutes of Health +# as part of the Insight Segmentation and Registration Toolkit (ITK). +# +# * US National Labs (Los Alamos, Livermore, Sandia) ASC Parallel +# Visualization Initiative. +# +# * National Alliance for Medical Image Computing (NAMIC) is funded by the +# National Institutes of Health through the NIH Roadmap for Medical Research, +# Grant U54 EB005149. +# +# * Kitware, Inc. +#============================================================================= +# Thanks to Daniel Blezek for the GTEST_ADD_TESTS code + +function(gtest_add_tests executable extra_args) + if(NOT ARGN) + message(FATAL_ERROR "Missing ARGN: Read the documentation for GTEST_ADD_TESTS") + endif() + if(ARGN STREQUAL "AUTO") + # obtain sources used for building that executable + get_property(ARGN TARGET ${executable} PROPERTY SOURCES) + endif() + set(gtest_case_name_regex ".*\\( *([A-Za-z_0-9]+) *, *([A-Za-z_0-9]+) *\\).*") + set(gtest_test_type_regex "(TYPED_TEST|TEST_?[FP]?)") + foreach(source ${ARGN}) + file(READ "${source}" contents) + string(REGEX MATCHALL "${gtest_test_type_regex} *\\(([A-Za-z_0-9 ,]+)\\)" found_tests ${contents}) + foreach(hit ${found_tests}) + string(REGEX MATCH "${gtest_test_type_regex}" test_type ${hit}) + + # Parameterized tests have a different signature for the filter + if("x${test_type}" STREQUAL "xTEST_P") + string(REGEX REPLACE ${gtest_case_name_regex} "*/\\1.\\2/*" test_name ${hit}) + elseif("x${test_type}" STREQUAL "xTEST_F" OR "x${test_type}" STREQUAL "xTEST") + string(REGEX REPLACE ${gtest_case_name_regex} "\\1.\\2" test_name ${hit}) + elseif("x${test_type}" STREQUAL "xTYPED_TEST") + string(REGEX REPLACE ${gtest_case_name_regex} "\\1/*.\\2" test_name ${hit}) + else() + message(WARNING "Could not parse GTest ${hit} for adding to CTest.") + continue() + endif() + add_test(NAME ${test_name} COMMAND ${executable} --gtest_filter=${test_name} ${extra_args}) + endforeach() + endforeach() +endfunction() + +function(_append_debugs _endvar _library) + if(${_library} AND ${_library}_DEBUG) + set(_output optimized ${${_library}} debug ${${_library}_DEBUG}) + else() + set(_output ${${_library}}) + endif() + set(${_endvar} ${_output} PARENT_SCOPE) +endfunction() + +function(_gmock_find_library _name) + find_library(${_name} + NAMES ${ARGN} + HINTS + ENV GMOCK_ROOT + ${GMOCK_ROOT} + PATH_SUFFIXES ${_gmock_libpath_suffixes} + ) + mark_as_advanced(${_name}) +endfunction() + +function(_gtest_find_library _name) + find_library(${_name} + NAMES ${ARGN} + HINTS + ENV GTEST_ROOT + ${GTEST_ROOT} + PATH_SUFFIXES ${_gtest_libpath_suffixes} + ) + mark_as_advanced(${_name}) +endfunction() + +if(NOT DEFINED GMOCK_MSVC_SEARCH) + set(GMOCK_MSVC_SEARCH MD) +endif() + +set(_gmock_libpath_suffixes lib) +set(_gtest_libpath_suffixes lib) +if(MSVC) + if(GMOCK_MSVC_SEARCH STREQUAL "MD") + list(APPEND _gmock_libpath_suffixes + msvc/gmock-md/Debug + msvc/gmock-md/Release) + list(APPEND _gtest_libpath_suffixes + msvc/gtest-md/Debug + msvc/gtest-md/Release) + elseif(GMOCK_MSVC_SEARCH STREQUAL "MT") + list(APPEND _gmock_libpath_suffixes + msvc/gmock/Debug + msvc/gmock/Release) + list(APPEND _gtest_libpath_suffixes + msvc/gtest/Debug + msvc/gtest/Release) + endif() +endif() + +find_path(GMOCK_INCLUDE_DIR gmock/gmock.h + HINTS + $ENV{GMOCK_ROOT}/include + ${GMOCK_ROOT}/include + ) +mark_as_advanced(GMOCK_INCLUDE_DIR) + +find_path(GTEST_INCLUDE_DIR gtest/gtest.h + HINTS + $ENV{GTEST_ROOT}/include + ${GTEST_ROOT}/include + ) +mark_as_advanced(GTEST_INCLUDE_DIR) + +if(MSVC AND GMOCK_MSVC_SEARCH STREQUAL "MD") + # The provided /MD project files for Google Mock add -md suffixes to the + # library names. + _gmock_find_library(GMOCK_LIBRARY gmock-md gmock) + _gmock_find_library(GMOCK_LIBRARY_DEBUG gmock-mdd gmockd) + _gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main-md gmock_main) + _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_main-mdd gmock_maind) + + _gtest_find_library(GTEST_LIBRARY gtest-md gtest) + _gtest_find_library(GTEST_LIBRARY_DEBUG gtest-mdd gtestd) + _gtest_find_library(GTEST_MAIN_LIBRARY gtest_main-md gtest_main) + _gtest_find_library(GTEST_MAIN_LIBRARY_DEBUG gtest_main-mdd gtest_maind) +else() + _gmock_find_library(GMOCK_LIBRARY gmock) + _gmock_find_library(GMOCK_LIBRARY_DEBUG gmockd) + _gmock_find_library(GMOCK_MAIN_LIBRARY gmock_main) + _gmock_find_library(GMOCK_MAIN_LIBRARY_DEBUG gmock_maind) + + _gtest_find_library(GTEST_LIBRARY gtest) + _gtest_find_library(GTEST_LIBRARY_DEBUG gtestd) + _gtest_find_library(GTEST_MAIN_LIBRARY gtest_main) + _gtest_find_library(GTEST_MAIN_LIBRARY_DEBUG gtest_maind) +endif() + +if(NOT TARGET GTest::GTest) + add_library(GTest::GTest UNKNOWN IMPORTED) +endif() +if(NOT TARGET GTest::Main) + add_library(GTest::Main UNKNOWN IMPORTED) +endif() + +if(NOT TARGET GMock::GMock) + add_library(GMock::GMock UNKNOWN IMPORTED) +endif() + +if(NOT TARGET GMock::Main) + add_library(GMock::Main UNKNOWN IMPORTED) +endif() + +set(GMOCK_LIBRARY_EXISTS OFF) +set(GTEST_LIBRARY_EXISTS OFF) + +if(EXISTS "${GMOCK_LIBRARY}" OR EXISTS "${GMOCK_LIBRARY_DEBUG}" AND GMOCK_INCLUDE_DIR) + set(GMOCK_LIBRARY_EXISTS ON) +endif() + +if(EXISTS "${GTEST_LIBRARY}" OR EXISTS "${GTEST_LIBRARY_DEBUG}" AND GTEST_INCLUDE_DIR) + set(GTEST_LIBRARY_EXISTS ON) +endif() + +if(NOT (${GMOCK_LIBRARY_EXISTS} AND ${GTEST_LIBRARY_EXISTS})) + + include(ExternalProject) + + if(GTEST_USE_STATIC_LIBS) + set(GTEST_CMAKE_ARGS -Dgtest_force_shared_crt:BOOL=ON -DBUILD_SHARED_LIBS=OFF) + if(BUILD_SHARED_LIBS) + list(APPEND GTEST_CMAKE_ARGS + -DCMAKE_POSITION_INDEPENDENT_CODE=ON + -Dgtest_hide_internal_symbols=ON + -DCMAKE_CXX_VISIBILITY_PRESET=hidden + -DCMAKE_VISIBILITY_INLINES_HIDDEN=ON + -DCMAKE_POLICY_DEFAULT_CMP0063=NEW + ) + endif() + set(GTEST_LIBRARY_PREFIX ${CMAKE_STATIC_LIBRARY_PREFIX}) + else() + set(GTEST_CMAKE_ARGS -DBUILD_SHARED_LIBS=ON) + set(GTEST_LIBRARY_PREFIX ${CMAKE_SHARED_LIBRARY_PREFIX}) + endif() + if(WIN32) + list(APPEND GTEST_CMAKE_ARGS -Dgtest_disable_pthreads=ON) + endif() + + if("${GMOCK_SRC_DIR}" STREQUAL "") + message(STATUS "Downloading GMock / GTest version ${GMOCK_VER} from git") + if("${GMOCK_VER}" STREQUAL "1.6.0" OR "${GMOCK_VER}" STREQUAL "1.7.0") + set(GTEST_BIN_DIR "${GMOCK_ROOT}/src/gtest-build") + set(GTEST_LIBRARY "${GTEST_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GTEST_MAIN_LIBRARY "${GTEST_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + mark_as_advanced(GTEST_LIBRARY) + mark_as_advanced(GTEST_MAIN_LIBRARY) + + externalproject_add( + gtest + GIT_REPOSITORY "https://github.com/google/googletest.git" + GIT_TAG "release-${GMOCK_VER}" + PREFIX ${GMOCK_ROOT} + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON + CMAKE_ARGS + ${GTEST_CMAKE_ARGS} + BINARY_DIR ${GTEST_BIN_DIR} + BUILD_BYPRODUCTS + "${GTEST_LIBRARY}" + "${GTEST_MAIN_LIBRARY}" + ) + + set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build") + set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + mark_as_advanced(GMOCK_LIBRARY) + mark_as_advanced(GMOCK_MAIN_LIBRARY) + + externalproject_add( + gmock + GIT_REPOSITORY "https://github.com/google/googlemock.git" + GIT_TAG "release-${GMOCK_VER}" + PREFIX ${GMOCK_ROOT} + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON + CMAKE_ARGS + ${GTEST_CMAKE_ARGS} + BINARY_DIR ${GMOCK_BIN_DIR} + BUILD_BYPRODUCTS + "${GMOCK_LIBRARY}" + "${GMOCK_MAIN_LIBRARY}" + ) + + add_dependencies(gmock gtest) + + add_dependencies(GTest::GTest gtest) + add_dependencies(GTest::Main gtest) + add_dependencies(GMock::GMock gmock) + add_dependencies(GMock::Main gmock) + + externalproject_get_property(gtest source_dir) + set(GTEST_INCLUDE_DIR "${source_dir}/include") + mark_as_advanced(GTEST_INCLUDE_DIR) + externalproject_get_property(gmock source_dir) + set(GMOCK_INCLUDE_DIR "${source_dir}/include") + mark_as_advanced(GMOCK_INCLUDE_DIR) + else() #1.8.0 + set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build") + set(GTEST_LIBRARY "${GMOCK_BIN_DIR}/googlemock/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GTEST_MAIN_LIBRARY "${GMOCK_BIN_DIR}/googlemock/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/googlemock/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/googlemock/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + mark_as_advanced(GTEST_LIBRARY) + mark_as_advanced(GTEST_MAIN_LIBRARY) + mark_as_advanced(GMOCK_LIBRARY) + mark_as_advanced(GMOCK_MAIN_LIBRARY) + + externalproject_add( + gmock + GIT_REPOSITORY "https://github.com/google/googletest.git" + GIT_TAG "release-${GMOCK_VER}" + PREFIX ${GMOCK_ROOT} + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON + CMAKE_ARGS + ${GTEST_CMAKE_ARGS} + BINARY_DIR "${GMOCK_BIN_DIR}" + BUILD_BYPRODUCTS + "${GTEST_LIBRARY}" + "${GTEST_MAIN_LIBRARY}" + "${GMOCK_LIBRARY}" + "${GMOCK_MAIN_LIBRARY}" + ) + + add_dependencies(GTest::GTest gmock) + add_dependencies(GTest::Main gmock) + add_dependencies(GMock::GMock gmock) + add_dependencies(GMock::Main gmock) + + externalproject_get_property(gmock source_dir) + set(GTEST_INCLUDE_DIR "${source_dir}/googletest/include") + set(GMOCK_INCLUDE_DIR "${source_dir}/googlemock/include") + mark_as_advanced(GMOCK_INCLUDE_DIR) + mark_as_advanced(GTEST_INCLUDE_DIR) + endif() + + # Prevent CMake from complaining about these directories missing when the libgtest/libgmock targets get used as dependencies + file(MAKE_DIRECTORY ${GTEST_INCLUDE_DIR} ${GMOCK_INCLUDE_DIR}) + else() + message(STATUS "Building Gmock / Gtest from dir ${GMOCK_SRC_DIR}") + + set(GMOCK_BIN_DIR "${GMOCK_ROOT}/src/gmock-build") + set(GTEST_LIBRARY "${GMOCK_BIN_DIR}/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GTEST_MAIN_LIBRARY "${GMOCK_BIN_DIR}/gtest/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gtest_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GMOCK_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock${CMAKE_STATIC_LIBRARY_SUFFIX}") + set(GMOCK_MAIN_LIBRARY "${GMOCK_BIN_DIR}/${CMAKE_CFG_INTDIR}/${GTEST_LIBRARY_PREFIX}gmock_main${CMAKE_STATIC_LIBRARY_SUFFIX}") + mark_as_advanced(GTEST_LIBRARY) + mark_as_advanced(GTEST_MAIN_LIBRARY) + mark_as_advanced(GMOCK_LIBRARY) + mark_as_advanced(GMOCK_MAIN_LIBRARY) + + if(EXISTS "${GMOCK_SRC_DIR}/gtest/include/gtest/gtest.h") + set(GTEST_INCLUDE_DIR "${GMOCK_SRC_DIR}/gtest/include") + mark_as_advanced(GTEST_INCLUDE_DIR) + endif() + if(EXISTS "${GMOCK_SRC_DIR}/include/gmock/gmock.h") + set(GMOCK_INCLUDE_DIR "${GMOCK_SRC_DIR}/include") + mark_as_advanced(GMOCK_INCLUDE_DIR) + elseif(EXISTS "${GMOCK_SRC_DIR}/../../include/gmock/gmock.h") + set(GMOCK_INCLUDE_DIR "${GMOCK_SRC_DIR}/../../include") + if(IS_ABSOLUTE "${GMOCK_INCLUDE_DIR}") + get_filename_component(GMOCK_INCLUDE_DIR "${GMOCK_INCLUDE_DIR}" ABSOLUTE) + endif() + mark_as_advanced(GMOCK_INCLUDE_DIR) + endif() + + externalproject_add( + gmock + SOURCE_DIR ${GMOCK_SRC_DIR} + PREFIX ${GMOCK_ROOT} + INSTALL_COMMAND "" + LOG_DOWNLOAD ON + LOG_CONFIGURE ON + LOG_BUILD ON + CMAKE_ARGS + ${GTEST_CMAKE_ARGS} + BINARY_DIR "${GMOCK_BIN_DIR}" + BUILD_BYPRODUCTS + "${GTEST_LIBRARY}" + "${GTEST_MAIN_LIBRARY}" + "${GMOCK_LIBRARY}" + "${GMOCK_MAIN_LIBRARY}" + ) + + add_dependencies(GTest::GTest gmock) + add_dependencies(GTest::Main gmock) + add_dependencies(GMock::GMock gmock) + add_dependencies(GMock::Main gmock) + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GTest DEFAULT_MSG GTEST_LIBRARY GTEST_INCLUDE_DIR GTEST_MAIN_LIBRARY) +find_package_handle_standard_args(GMock DEFAULT_MSG GMOCK_LIBRARY GMOCK_INCLUDE_DIR GMOCK_MAIN_LIBRARY) + +include(CMakeFindDependencyMacro) +find_dependency(Threads) + +set_target_properties(GTest::GTest PROPERTIES + INTERFACE_LINK_LIBRARIES "Threads::Threads" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${GTEST_LIBRARY}" + ) + +if(GTEST_INCLUDE_DIR) + set_target_properties(GTest::GTest PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}" + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}" + ) +endif() + +set_target_properties(GTest::Main PROPERTIES + INTERFACE_LINK_LIBRARIES "GTest::GTest" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${GTEST_MAIN_LIBRARY}") + +set_target_properties(GMock::GMock PROPERTIES + INTERFACE_LINK_LIBRARIES "Threads::Threads" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${GMOCK_LIBRARY}") + +if(GMOCK_INCLUDE_DIR) + set_target_properties(GMock::GMock PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GMOCK_INCLUDE_DIR}" + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GMOCK_INCLUDE_DIR}" + ) + if(GMOCK_VER VERSION_LESS "1.7") + # GMock 1.6 still has GTest as an external link-time dependency, + # so just specify it on the link interface. + set_property(TARGET GMock::GMock APPEND PROPERTY + INTERFACE_LINK_LIBRARIES GTest::GTest) + elseif(GTEST_INCLUDE_DIR) + # GMock 1.7 and beyond doesn't have it as a link-time dependency anymore, + # so merge it's compile-time interface (include dirs) with ours. + set_property(TARGET GMock::GMock APPEND PROPERTY + INTERFACE_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}") + set_property(TARGET GMock::GMock APPEND PROPERTY + INTERFACE_SYSTEM_INCLUDE_DIRECTORIES "${GTEST_INCLUDE_DIR}") + endif() +endif() + +set_target_properties(GMock::Main PROPERTIES + INTERFACE_LINK_LIBRARIES "GMock::GMock" + IMPORTED_LINK_INTERFACE_LANGUAGES "CXX" + IMPORTED_LOCATION "${GMOCK_MAIN_LIBRARY}") + +if(GTEST_FOUND) + set(GTEST_INCLUDE_DIRS ${GTEST_INCLUDE_DIR}) + set(GTEST_LIBRARIES GTest::GTest) + set(GTEST_MAIN_LIBRARIES GTest::Main) + set(GTEST_BOTH_LIBRARIES ${GTEST_LIBRARIES} ${GTEST_MAIN_LIBRARIES}) + if(VERBOSE) + message(STATUS "GTest includes: ${GTEST_INCLUDE_DIRS}") + message(STATUS "GTest libs: ${GTEST_BOTH_LIBRARIES}") + endif() +endif() + +if(GMOCK_FOUND) + set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR}) + set(GMOCK_LIBRARIES GMock::GMock) + set(GMOCK_MAIN_LIBRARIES GMock::Main) + set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARIES} ${GMOCK_MAIN_LIBRARIES}) + if(VERBOSE) + message(STATUS "GMock includes: ${GMOCK_INCLUDE_DIRS}") + message(STATUS "GMock libs: ${GMOCK_BOTH_LIBRARIES}") + endif() +endif() diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index e2e6b97bb1..a941190998 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -242,6 +242,11 @@ if (UNIX AND NOT APPLE) target_link_libraries(components ${CMAKE_THREAD_LIBS_INIT}) endif() +if (BUILD_WITH_CODE_COVERAGE) + add_definitions(--coverage) + target_link_libraries(components gcov) +endif() + # Make the variable accessible for other subdirectories set(COMPONENT_FILES ${COMPONENT_FILES} PARENT_SCOPE) diff --git a/components/bullethelpers/processtrianglecallback.hpp b/components/bullethelpers/processtrianglecallback.hpp new file mode 100644 index 0000000000..ee005b459e --- /dev/null +++ b/components/bullethelpers/processtrianglecallback.hpp @@ -0,0 +1,34 @@ +#ifndef OPENMW_COMPONENTS_BULLETHELPERS_PROCESSTRIANGLECALLBACK_H +#define OPENMW_COMPONENTS_BULLETHELPERS_PROCESSTRIANGLECALLBACK_H + +#include + +#include + +namespace BulletHelpers +{ + template + class ProcessTriangleCallback : public btTriangleCallback + { + public: + ProcessTriangleCallback(Impl impl) + : mImpl(std::move(impl)) + {} + + void processTriangle(btVector3* triangle, int partId, int triangleIndex) override final + { + return mImpl(triangle, partId, triangleIndex); + } + + private: + Impl mImpl; + }; + + template + ProcessTriangleCallback::type> makeProcessTriangleCallback(Impl&& impl) + { + return ProcessTriangleCallback::type>(std::forward(impl)); + } +} + +#endif diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 9e39a37abf..cab2e98807 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -14,7 +14,30 @@ namespace Nif { -class NIFFile +struct File +{ + virtual ~File() = default; + + virtual void fail(const std::string &msg) const = 0; + + virtual void warn(const std::string &msg) const = 0; + + virtual Record *getRecord(size_t index) const = 0; + + virtual size_t numRecords() const = 0; + + virtual Record *getRoot(size_t index = 0) const = 0; + + virtual size_t numRoots() const = 0; + + virtual void setUseSkinning(bool skinning) = 0; + + virtual bool getUseSkinning() const = 0; + + virtual std::string getFilename() const = 0; +}; + +class NIFFile final : public File { enum NIFVersion { VER_MW = 0x04000002 // Morrowind NIFs @@ -48,14 +71,14 @@ class NIFFile public: /// Used if file parsing fails - void fail(const std::string &msg) const + void fail(const std::string &msg) const override { std::string err = " NIFFile Error: " + msg; err += "\nFile: " + filename; throw std::runtime_error(err); } /// Used when something goes wrong, but not catastrophically so - void warn(const std::string &msg) const + void warn(const std::string &msg) const override { std::cerr << " NIFFile Warning: " << msg < NIFFilePtr; diff --git a/components/nif/recordptr.hpp b/components/nif/recordptr.hpp index 25beaf098b..09c3809872 100644 --- a/components/nif/recordptr.hpp +++ b/components/nif/recordptr.hpp @@ -23,6 +23,8 @@ class RecordPtrT public: RecordPtrT() : index(-2) {} + RecordPtrT(X* ptr) : ptr(ptr) {} + /// Read the index from the nif void read(NIFStream *nif) { @@ -87,6 +89,12 @@ class RecordListT std::vector list; public: + RecordListT() = default; + + RecordListT(std::vector list) + : list(std::move(list)) + {} + void read(NIFStream *nif) { int len = nif->getInt(); diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 6f8c8f2c0d..82d27269f8 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -47,8 +47,8 @@ namespace NifBullet { BulletNifLoader::BulletNifLoader() - : mCompoundShape(NULL) - , mStaticMesh(NULL) + : mCompoundShape() + , mStaticMesh() { } @@ -56,20 +56,20 @@ BulletNifLoader::~BulletNifLoader() { } -osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr& nif) +osg::ref_ptr BulletNifLoader::load(const Nif::File& nif) { mShape = new Resource::BulletShape; mCompoundShape = NULL; mStaticMesh = NULL; - if (nif->numRoots() < 1) + if (nif.numRoots() < 1) { warn("Found no root nodes in NIF."); return mShape; } - Nif::Record *r = nif->getRoot(0); + Nif::Record *r = nif.getRoot(0); assert(r != NULL); Nif::Node *node = dynamic_cast(r); @@ -84,10 +84,11 @@ osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr& { std::unique_ptr compound (new btCompoundShape); - btBoxShape* boxShape = new btBoxShape(getbtVector(mShape->mCollisionBoxHalfExtents)); + std::unique_ptr boxShape(new btBoxShape(getbtVector(mShape->mCollisionBoxHalfExtents))); btTransform transform = btTransform::getIdentity(); transform.setOrigin(getbtVector(mShape->mCollisionBoxTranslate)); - compound->addChildShape(transform, boxShape); + compound->addChildShape(transform, boxShape.get()); + boxShape.release(); mShape->mCollisionShape = compound.release(); return mShape; @@ -96,27 +97,32 @@ osg::ref_ptr BulletNifLoader::load(const Nif::NIFFilePtr& { // files with the name convention xmodel.nif usually have keyframes stored in a separate file xmodel.kf (see Animation::addAnimSource). // assume all nodes in the file will be animated - const bool isAnimated = pathFileNameStartsWithX(nif->getFilename()); + const auto filename = nif.getFilename(); + const bool isAnimated = pathFileNameStartsWithX(filename); // If the mesh has RootCollisionNode, attached to actual root node, use it as collision mesh const Nif::Node* rootCollisionNode = getCollisionNode(node); if (rootCollisionNode) - handleNode(nif->getFilename(), rootCollisionNode, 0, false, isAnimated, false); + handleNode(filename, rootCollisionNode, 0, false, isAnimated, false); else - handleNode(nif->getFilename(), node, 0, true, isAnimated, true); + handleNode(filename, node, 0, true, isAnimated, true); if (mCompoundShape) { - mShape->mCollisionShape = mCompoundShape; if (mStaticMesh) { btTransform trans; trans.setIdentity(); - mCompoundShape->addChildShape(trans, new Resource::TriangleMeshShape(mStaticMesh,true)); + mCompoundShape->addChildShape(trans, new Resource::TriangleMeshShape(mStaticMesh.get(), true)); + mStaticMesh.release(); } + mShape->mCollisionShape = mCompoundShape.release(); } else if (mStaticMesh) - mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh,true); + { + mShape->mCollisionShape = new Resource::TriangleMeshShape(mStaticMesh.get(), true); + mStaticMesh.release(); + } return mShape; } @@ -276,9 +282,9 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, if (isAnimated) { if (!mCompoundShape) - mCompoundShape = new btCompoundShape(); + mCompoundShape.reset(new btCompoundShape); - btTriangleMesh* childMesh = new btTriangleMesh(); + std::unique_ptr childMesh(new btTriangleMesh); const Nif::NiTriShapeData *data = shape->data.getPtr(); @@ -296,7 +302,8 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, childMesh->addTriangle(getbtVector(b1), getbtVector(b2), getbtVector(b3)); } - Resource::TriangleMeshShape* childShape = new Resource::TriangleMeshShape(childMesh,true); + std::unique_ptr childShape(new Resource::TriangleMeshShape(childMesh.get(), true)); + childMesh.release(); float scale = shape->trafo.scale; const Nif::Node* parent = shape; @@ -313,12 +320,13 @@ void BulletNifLoader::handleNiTriShape(const Nif::NiTriShape *shape, int flags, mShape->mAnimatedShapes.insert(std::make_pair(shape->recIndex, mCompoundShape->getNumChildShapes())); - mCompoundShape->addChildShape(trans, childShape); + mCompoundShape->addChildShape(trans, childShape.get()); + childShape.release(); } else { if (!mStaticMesh) - mStaticMesh = new btTriangleMesh(false); + mStaticMesh.reset(new btTriangleMesh(false)); // Static shape, just transform all vertices into position const Nif::NiTriShapeData *data = shape->data.getPtr(); diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index 8fd9cc2a18..44134fd238 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -50,7 +50,7 @@ public: abort(); } - osg::ref_ptr load(const Nif::NIFFilePtr& file); + osg::ref_ptr load(const Nif::File& file); private: bool findBoundingBox(const Nif::Node* node, int flags = 0); @@ -61,9 +61,9 @@ private: void handleNiTriShape(const Nif::NiTriShape *shape, int flags, const osg::Matrixf& transform, bool isAnimated); - btCompoundShape* mCompoundShape; + std::unique_ptr mCompoundShape; - btTriangleMesh* mStaticMesh; + std::unique_ptr mStaticMesh; osg::ref_ptr mShape; }; diff --git a/components/resource/bulletshapemanager.cpp b/components/resource/bulletshapemanager.cpp index a3d09311a5..622506d6b0 100644 --- a/components/resource/bulletshapemanager.cpp +++ b/components/resource/bulletshapemanager.cpp @@ -127,7 +127,7 @@ osg::ref_ptr BulletShapeManager::getShape(const std::string & if (ext == "nif") { NifBullet::BulletNifLoader loader; - shape = loader.load(mNifFileManager->get(normalized)); + shape = loader.load(*mNifFileManager->get(normalized)); } else {