Implement GTest and change windows output path

Implement GTest as a testing infrastructure.
Make windows output binaries to the build folder instead of the release type folder (potentially issue further down the line)
Add a simple unit test for DestroyableComponent
This commit is contained in:
David Markowitz
2022-11-07 00:12:35 -08:00
committed by GitHub
parent 9c58ea5c41
commit 1464762bcd
30 changed files with 763 additions and 325 deletions

View File

@@ -1,34 +1,21 @@
# create the testing file and list of tests
create_test_sourcelist (Tests
CommonCxxTests.cpp
AMFDeserializeTests.cpp
TestNiPoint3.cpp
TestLDFFormat.cpp
TestEncoding.cpp
message (STATUS "Testing is enabled. Fetching gtest...")
enable_testing()
include(FetchContent)
FetchContent_Declare(
googletest
GIT_REPOSITORY https://github.com/google/googletest.git
GIT_TAG release-1.12.1
)
# add the executable
add_executable (CommonCxxTests ${Tests})
target_link_libraries(CommonCxxTests ${COMMON_LIBRARIES})
# For Windows: Prevent overriding the parent project's compiler/linker settings
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
# remove the test driver source file
set (TestsToRun ${Tests})
remove (TestsToRun CommonCxxTests.cpp)
FetchContent_MakeAvailable(GoogleTest)
include(GoogleTest)
# Copy test files to testing directory
configure_file(
${CMAKE_SOURCE_DIR}/tests/TestBitStreams/AMFBitStreamTest.bin ${PROJECT_BINARY_DIR}/tests/AMFBitStreamTest.bin
COPYONLY
)
message(STATUS "gtest fetched and is now ready.")
configure_file(
${CMAKE_SOURCE_DIR}/tests/TestBitStreams/AMFBitStreamUnimplementedTest.bin ${PROJECT_BINARY_DIR}/tests/AMFBitStreamUnimplementedTest.bin
COPYONLY
)
# Add all the ADD_TEST for each test
foreach (test ${TestsToRun})
get_filename_component (TName ${test} NAME_WE)
add_test (NAME ${TName} COMMAND CommonCxxTests ${TName})
set_property(TEST ${TName} PROPERTY ENVIRONMENT CTEST_OUTPUT_ON_FAILURE=1)
endforeach ()
# Add the subdirectories
add_subdirectory(dCommonTests)
add_subdirectory(dGameTests)

View File

@@ -1,4 +0,0 @@
#include <cstdio>
#define ASSERT_EQ(a,b) { if (!(a == b)) { printf("Failed assertion: " #a " == " #b " \n in %s:%d\n", __FILE__, __LINE__); return 1; }}
#define ASSERT_NE(a,b) { if (!(a != b)) { printf("Failed assertion: " #a " != " #b " \n in %s:%d\n", __FILE__, __LINE__); return 1; }}

View File

@@ -1,52 +0,0 @@
#include <stdexcept>
#include <string>
#include "GeneralUtils.h"
#include "CommonCxxTests.h"
int TestEncoding(int argc, char** const argv) {
std::string x = "Hello World!";
std::string_view v(x);
uint32_t out;
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 'H');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 'e');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 'l');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 'l');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 'o');
ASSERT_EQ(GeneralUtils::_NextUTF8Char(v, out), true);
x = u8"Frühling";
v = x;
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'F');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'r');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'ü');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'h');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'l');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'i');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'n');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'g');
ASSERT_EQ(GeneralUtils::_NextUTF8Char(v, out), false);
x = "中文字";
v = x;
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'');
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, U'');
ASSERT_EQ(GeneralUtils::_NextUTF8Char(v, out), false);
x = "👨‍⚖️";
v = x;
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 0x1F468);
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 0x200D);
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 0x2696);
GeneralUtils::_NextUTF8Char(v, out); ASSERT_EQ(out, 0xFE0F);
ASSERT_EQ(GeneralUtils::_NextUTF8Char(v, out), false);
ASSERT_EQ(GeneralUtils::UTF8ToUTF16("Hello World!"), u"Hello World!");
ASSERT_EQ(GeneralUtils::UTF8ToUTF16("Frühling"), u"Frühling");
ASSERT_EQ(GeneralUtils::UTF8ToUTF16("中文字"), u"中文字");
ASSERT_EQ(GeneralUtils::UTF8ToUTF16("👨‍⚖️"), u"👨‍⚖️");
return 0;
}

View File

@@ -1,52 +1,65 @@
#include <chrono>
#include <fstream>
#include <iostream>
#include <memory>
#include <gtest/gtest.h>
#include "AMFDeserialize.h"
#include "AMFFormat.h"
#include "CommonCxxTests.h"
/**
* Helper method that all tests use to get their respective AMF.
*/
std::unique_ptr<AMFValue> ReadFromBitStream(RakNet::BitStream* bitStream) {
AMFDeserialize deserializer;
std::unique_ptr<AMFValue> returnValue(deserializer.Read(bitStream));
return returnValue;
}
int ReadAMFUndefinedFromBitStream() {
CBITSTREAM;
/**
* @brief Test reading an AMFUndefined value from a BitStream.
*/
TEST(dCommonTests, AMFDeserializeAMFUndefinedTest) {
CBITSTREAM
bitStream.Write<uint8_t>(0x00);
std::unique_ptr<AMFValue> res(ReadFromBitStream(&bitStream));
ASSERT_EQ(res->GetValueType(), AMFValueType::AMFUndefined);
return 0;
}
int ReadAMFNullFromBitStream() {
CBITSTREAM;
/**
* @brief Test reading an AMFNull value from a BitStream.
*
*/
TEST(dCommonTests, AMFDeserializeAMFNullTest) {
CBITSTREAM
bitStream.Write<uint8_t>(0x01);
std::unique_ptr<AMFValue> res(ReadFromBitStream(&bitStream));
ASSERT_EQ(res->GetValueType(), AMFValueType::AMFNull);
return 0;
}
int ReadAMFFalseFromBitStream() {
CBITSTREAM;
/**
* @brief Test reading an AMFFalse value from a BitStream.
*/
TEST(dCommonTests, AMFDeserializeAMFFalseTest) {
CBITSTREAM
bitStream.Write<uint8_t>(0x02);
std::unique_ptr<AMFValue> res(ReadFromBitStream(&bitStream));
ASSERT_EQ(res->GetValueType(), AMFValueType::AMFFalse);
return 0;
}
int ReadAMFTrueFromBitStream() {
CBITSTREAM;
/**
* @brief Test reading an AMFTrue value from a BitStream.
*/
TEST(dCommonTests, AMFDeserializeAMFTrueTest) {
CBITSTREAM
bitStream.Write<uint8_t>(0x03);
std::unique_ptr<AMFValue> res(ReadFromBitStream(&bitStream));
ASSERT_EQ(res->GetValueType(), AMFValueType::AMFTrue);
return 0;
}
int ReadAMFIntegerFromBitStream() {
CBITSTREAM;
/**
* @brief Test reading an AMFInteger value from a BitStream.
*/
TEST(dCommonTests, AMFDeserializeAMFIntegerTest) {
CBITSTREAM
{
bitStream.Write<uint8_t>(0x04);
// 127 == 01111111
@@ -91,21 +104,25 @@ int ReadAMFIntegerFromBitStream() {
// Check that 2 byte max can be read correctly
ASSERT_EQ(static_cast<AMFIntegerValue*>(res.get())->GetIntegerValue(), 16383);
}
return 0;
}
int ReadAMFDoubleFromBitStream() {
CBITSTREAM;
/**
* @brief Test reading an AMFDouble value from a BitStream.
*/
TEST(dCommonTests, AMFDeserializeAMFDoubleTest) {
CBITSTREAM
bitStream.Write<uint8_t>(0x05);
bitStream.Write<double>(25346.4f);
std::unique_ptr<AMFValue> res(ReadFromBitStream(&bitStream));
ASSERT_EQ(res->GetValueType(), AMFValueType::AMFDouble);
ASSERT_EQ(static_cast<AMFDoubleValue*>(res.get())->GetDoubleValue(), 25346.4f);
return 0;
}
int ReadAMFStringFromBitStream() {
CBITSTREAM;
/**
* @brief Test reading an AMFString value from a BitStream.
*/
TEST(dCommonTests, AMFDeserializeAMFStringTest) {
CBITSTREAM
bitStream.Write<uint8_t>(0x06);
bitStream.Write<uint8_t>(0x0F);
std::string toWrite = "stateID";
@@ -113,11 +130,13 @@ int ReadAMFStringFromBitStream() {
std::unique_ptr<AMFValue> res(ReadFromBitStream(&bitStream));
ASSERT_EQ(res->GetValueType(), AMFValueType::AMFString);
ASSERT_EQ(static_cast<AMFStringValue*>(res.get())->GetStringValue(), "stateID");
return 0;
}
int ReadAMFArrayFromBitStream() {
CBITSTREAM;
/**
* @brief Test reading an AMFArray value from a BitStream.
*/
TEST(dCommonTests, AMFDeserializeAMFArrayTest) {
CBITSTREAM
// Test empty AMFArray
bitStream.Write<uint8_t>(0x09);
bitStream.Write<uint8_t>(0x01);
@@ -149,15 +168,15 @@ int ReadAMFArrayFromBitStream() {
ASSERT_EQ(static_cast<AMFArrayValue*>(res.get())->FindValue<AMFStringValue>("BehaviorID")->GetStringValue(), "10447");
ASSERT_EQ(static_cast<AMFArrayValue*>(res.get())->GetValueAt<AMFStringValue>(0)->GetStringValue(), "10447");
}
// Test a dense array
return 0;
}
/**
* This test checks that if we recieve an unimplemented AMFValueType
* @brief This test checks that if we recieve an unimplemented AMFValueType
* we correctly throw an error and can actch it.
*
*/
int TestUnimplementedAMFValues() {
#pragma message("-- The AMFDeserializeUnimplementedValuesTest causes a known memory leak of 880 bytes since it throws errors! --")
TEST(dCommonTests, AMFDeserializeUnimplementedValuesTest) {
std::vector<AMFValueType> unimplementedValues = {
AMFValueType::AMFXMLDoc,
AMFValueType::AMFDate,
@@ -196,13 +215,15 @@ int TestUnimplementedAMFValues() {
} catch (AMFValueType unimplementedValueType) {
caughtException = true;
}
std::cout << "Testing unimplemented value " << amfValueType << " Did we catch an exception: " << (caughtException ? "YES" : "NO") << std::endl;
ASSERT_EQ(caughtException, true);
}
return 0;
}
int TestLiveCapture() {
/**
* @brief Test reading a packet capture from live from a BitStream
*/
TEST(dCommonTests, AMFDeserializeLivePacketTest) {
std::ifstream testFileStream;
testFileStream.open("AMFBitStreamTest.bin", std::ios::binary);
@@ -267,9 +288,9 @@ int TestLiveCapture() {
auto actionID = firstStrip->FindValue<AMFDoubleValue>("id");
ASSERT_EQ(actionID->GetDoubleValue(), 0.0f)
ASSERT_EQ(actionID->GetDoubleValue(), 0.0f);
auto uiArray = firstStrip->FindValue<AMFArrayValue>("ui");
auto uiArray = firstStrip->FindValue<AMFArrayValue>("ui");
auto xPos = uiArray->FindValue<AMFDoubleValue>("x");
auto yPos = uiArray->FindValue<AMFDoubleValue>("y");
@@ -279,9 +300,9 @@ int TestLiveCapture() {
auto stripID = firstStrip->FindValue<AMFDoubleValue>("id");
ASSERT_EQ(stripID->GetDoubleValue(), 0.0f)
ASSERT_EQ(stripID->GetDoubleValue(), 0.0f);
auto firstAction = dynamic_cast<AMFArrayValue*>(actionsInFirstStrip[0]);
auto firstAction = dynamic_cast<AMFArrayValue*>(actionsInFirstStrip[0]);
auto firstType = firstAction->FindValue<AMFStringValue>("Type");
@@ -318,17 +339,17 @@ int TestLiveCapture() {
auto thirdDistance = thirdAction->FindValue<AMFDoubleValue>("Distance");
ASSERT_EQ(thirdDistance->GetDoubleValue(), 25.0f);
return 0;
}
int TestNullStream() {
/**
* @brief Tests that having no BitStream returns a nullptr.
*/
TEST(dCommonTests, AMFDeserializeNullTest) {
auto result = ReadFromBitStream(nullptr);
ASSERT_EQ(result.get(), nullptr);
return 0;
}
int TestBadConversion() {
TEST(dCommonTests, AMFBadConversionTest) {
std::ifstream testFileStream;
testFileStream.open("AMFBitStreamTest.bin", std::ios::binary);
@@ -360,30 +381,6 @@ int TestBadConversion() {
// Value is out of bounds
ASSERT_EQ(result->GetValueAt<AMFTrueValue>(1), nullptr);
return 0;
}
int AMFDeserializeTests(int argc, char** const argv) {
std::cout << "Checking that using a null bitstream doesnt cause exception" << std::endl;
if (TestNullStream()) return 1;
std::cout << "passed nullptr test, checking basic tests" << std::endl;
if (ReadAMFUndefinedFromBitStream() != 0) return 1;
if (ReadAMFNullFromBitStream() != 0) return 1;
if (ReadAMFFalseFromBitStream() != 0) return 1;
if (ReadAMFTrueFromBitStream() != 0) return 1;
if (ReadAMFIntegerFromBitStream() != 0) return 1;
if (ReadAMFDoubleFromBitStream() != 0) return 1;
if (ReadAMFStringFromBitStream() != 0) return 1;
if (ReadAMFArrayFromBitStream() != 0) return 1;
std::cout << "Passed basic test, checking live capture" << std::endl;
if (TestLiveCapture() != 0) return 1;
std::cout << "Passed live capture, checking unimplemented amf values" << std::endl;
if (TestUnimplementedAMFValues() != 0) return 1;
std::cout << "Passed unimplemented values, checking poor casting" << std::endl;
if (TestBadConversion() != 0) return 1;
std::cout << "Passed all tests." << std::endl;
return 0;
}
/**

View File

@@ -0,0 +1,19 @@
set(DCOMMONTEST_SOURCES
"AMFDeserializeTests.cpp"
"TestLDFFormat.cpp"
"TestNiPoint3.cpp"
"TestEncoding.cpp"
)
# Set our executable
add_executable(dCommonTests ${DCOMMONTEST_SOURCES})
# Link needed libraries
target_link_libraries(dCommonTests ${COMMON_LIBRARIES} GTest::gtest_main)
# Copy test files to testing directory
add_subdirectory(TestBitStreams)
file(COPY ${TESTBITSTREAMS} DESTINATION ${CMAKE_CURRENT_BINARY_DIR})
# Discover the tests
gtest_discover_tests(dCommonTests)

View File

@@ -0,0 +1,11 @@
set(TESTBITSTREAMS
"AMFBitStreamTest.bin"
"AMFBitStreamUnimplementedTest.bin"
)
# Get the folder name and prepend it to the files above
get_filename_component(thisFolderName ${CMAKE_CURRENT_SOURCE_DIR} NAME)
list(TRANSFORM TESTBITSTREAMS PREPEND "${thisFolderName}/")
# Export our list of files
set(TESTBITSTREAMS ${TESTBITSTREAMS} PARENT_SCOPE)

View File

@@ -0,0 +1,68 @@
#include <string>
#include <gtest/gtest.h>
#include <string_view>
#include "GeneralUtils.h"
class EncodingTest : public ::testing::Test {
protected:
std::string originalWord;
std::string_view originalWordSv;
uint32_t out;
};
TEST_F(EncodingTest, TestEncodingHello) {
originalWord = "Hello World!";
originalWordSv = originalWord;
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'H');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'e');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'l');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 'o');
EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), true);
EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Hello World!"), u"Hello World!");
};
TEST_F(EncodingTest, TestEncodingUmlaut) {
originalWord = u8"Frühling";
originalWordSv = originalWord;
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'F');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'r');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'ü');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'h');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'l');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'i');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'n');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'g');
EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false);
EXPECT_EQ(GeneralUtils::UTF8ToUTF16("Frühling"), u"Frühling");
};
TEST_F(EncodingTest, TestEncodingChinese) {
originalWord = "中文字";
originalWordSv = originalWord;
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'');
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, U'');
EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false);
EXPECT_EQ(GeneralUtils::UTF8ToUTF16("中文字"), u"中文字");
};
TEST_F(EncodingTest, TestEncodingEmoji) {
originalWord = "👨‍⚖️";
originalWordSv = originalWord;
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x1F468);
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x200D);
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0x2696);
GeneralUtils::_NextUTF8Char(originalWordSv, out); EXPECT_EQ(out, 0xFE0F);
EXPECT_EQ(GeneralUtils::_NextUTF8Char(originalWordSv, out), false);
EXPECT_EQ(GeneralUtils::UTF8ToUTF16("👨‍⚖️"), u"👨‍⚖️");
};

View File

@@ -1,14 +1,10 @@
#include "LDFFormat.h"
#include "CommonCxxTests.h"
#include <gtest/gtest.h>
/**
* @brief Test parsing an LDF value
*
* @param argc Number of command line arguments for this test
* @param argv Command line arguments
* @return 0 on success, non-zero on failure
*/
int TestLDFFormat(int argc, char** const argv) {
TEST(dCommonTests, LDFTest) {
// Create
auto* data = LDFBaseData::DataFromString("KEY=0:VALUE");
@@ -26,6 +22,4 @@ int TestLDFFormat(int argc, char** const argv) {
// Cleanup the object
delete data;
return 0;
}

View File

@@ -1,13 +1,14 @@
#include <stdexcept>
#include <gtest/gtest.h>
#include "NiPoint3.h"
#include "CommonCxxTests.h"
int TestNiPoint3(int argc, char** const argv) {
/**
* @brief Basic test for NiPoint3 functionality
*
*/
TEST(dCommonTests, NiPoint3Test) {
// Check that Unitize works
ASSERT_EQ(NiPoint3(3, 0, 0).Unitize(), NiPoint3::UNIT_X);
// Check what unitize does to a vector of length 0
ASSERT_EQ(NiPoint3::ZERO.Unitize(), NiPoint3::ZERO);
// If we get here, all was successful
return 0;
}

View File

@@ -0,0 +1,14 @@
set(DGAMETEST_SOURCES
"GameDependencies.cpp"
)
add_subdirectory(dComponentsTests)
list(APPEND DGAMETEST_SOURCES ${DCOMPONENTS_TESTS})
# Add the executable. Remember to add all tests above this!
add_executable(dGameTests ${DGAMETEST_SOURCES})
target_link_libraries(dGameTests ${COMMON_LIBRARIES} GTest::gtest_main dGame dZoneManager dPhysics Detour Recast tinyxml2 dWorldServer dChatFilter dNavigation)
# Discover the tests
gtest_discover_tests(dGameTests)

View File

@@ -0,0 +1,15 @@
#include "GameDependencies.h"
namespace Game {
dLogger* logger;
dServer* server;
dZoneManager* zoneManager;
dpWorld* physicsWorld;
dChatFilter* chatFilter;
dConfig* config;
dLocale* locale;
std::mt19937 randomEngine;
RakPeerInterface* chatServer;
AssetManager* assetManager;
SystemAddress chatSysAddr;
}

View File

@@ -0,0 +1,43 @@
#ifndef __GAMEDEPENDENCIES__H__
#define __GAMEDEPENDENCIES__H__
#include "Game.h"
#include "dLogger.h"
#include "dServer.h"
#include "EntityManager.h"
class dZoneManager;
class AssetManager;
#include <gtest/gtest.h>
class dServerMock : public dServer {
public:
dServerMock() {};
~dServerMock() {};
void Send(RakNet::BitStream* bitStream, const SystemAddress& sysAddr, bool broadcast) override {};
};
class GameDependenciesTest : public ::testing::Test {
protected:
void SetUpDependencies() {
info.pos = NiPoint3::ZERO;
info.rot = NiQuaternion::IDENTITY;
info.scale = 1.0f;
info.spawner = nullptr;
info.lot = 999;
Game::logger = new dLogger("./testing.log", true, true);
Game::server = new dServerMock();
}
void TearDownDependencies() {
if (Game::server) delete Game::server;
delete EntityManager::Instance();
if (Game::logger) {
Game::logger->Flush();
delete Game::logger;
}
}
EntityInfo info;
};
#endif //!__GAMEDEPENDENCIES__H__

View File

@@ -0,0 +1,10 @@
set(DCOMPONENTS_TESTS
"DestroyableComponentTests.cpp"
)
# Get the folder name and prepend it to the files above
get_filename_component(thisFolderName ${CMAKE_CURRENT_SOURCE_DIR} NAME)
list(TRANSFORM DCOMPONENTS_TESTS PREPEND "${thisFolderName}/")
# Export to parent scope
set(DCOMPONENTS_TESTS ${DCOMPONENTS_TESTS} PARENT_SCOPE)

View File

@@ -0,0 +1,300 @@
#include "GameDependencies.h"
#include <gtest/gtest.h>
#include "BitStream.h"
#include "DestroyableComponent.h"
#include "Entity.h"
class DestroyableTest : public GameDependenciesTest {
protected:
Entity* baseEntity;
DestroyableComponent* destroyableComponent;
CBITSTREAM
uint32_t flags = 0;
void SetUp() override {
SetUpDependencies();
baseEntity = new Entity(15, GameDependenciesTest::info);
destroyableComponent = new DestroyableComponent(baseEntity);
baseEntity->AddComponent(COMPONENT_TYPE_DESTROYABLE, destroyableComponent);
// Initialize some values to be not default
destroyableComponent->SetMaxHealth(12345.0f);
destroyableComponent->SetHealth(23);
destroyableComponent->SetMaxArmor(14.0f);
destroyableComponent->SetArmor(7);
destroyableComponent->SetMaxImagination(14000.0f);
destroyableComponent->SetImagination(6000);
destroyableComponent->SetIsSmashable(true);
destroyableComponent->SetExplodeFactor(1.1f);
destroyableComponent->AddFactionNoLookup(-1);
destroyableComponent->AddFactionNoLookup(6);
}
void TearDown() override {
delete baseEntity;
TearDownDependencies();
}
};
/**
* Test Construction of a DestroyableComponent
*/
TEST_F(DestroyableTest, DestroyableComponentSerializeConstructionTest) {
destroyableComponent->Serialize(&bitStream, true, flags);
// Assert that the full number of bits are present
ASSERT_EQ(bitStream.GetNumberOfUnreadBits(), 460);
{
// Now read in the full serialized construction BitStream
bool optionStatusImmunityInfo{}; // Values under this option are unused.
bool optionStatsInfo{};
uint32_t currentHealth{};
float maxHealth{};
uint32_t currentArmor{};
float maxArmor{};
uint32_t currentImagination{};
float maxImagination{};
uint32_t damageAbsorptionPoints{};
bool hasImmunity{};
bool isGmImmune{};
bool isShielded{};
float actualMaxHealth{};
float actualMaxArmor{};
float actualMaxImagination{};
uint32_t factionsSize{};
std::vector<int32_t> factions{};
bool isSmashable{};
bool isDead{};
bool isSmashed{};
bool isModuleAssembly{};
bool optionExplodeFactor{};
float explodeFactor{};
bool optionIsOnThreatList{};
bool isThreatened{};
bitStream.Read(optionStatusImmunityInfo);
bitStream.Read(optionStatsInfo);
bitStream.Read(currentHealth);
bitStream.Read(maxHealth);
bitStream.Read(currentArmor);
bitStream.Read(maxArmor);
bitStream.Read(currentImagination);
bitStream.Read(maxImagination);
bitStream.Read(damageAbsorptionPoints);
bitStream.Read(hasImmunity);
bitStream.Read(isGmImmune);
bitStream.Read(isShielded);
bitStream.Read(actualMaxHealth);
bitStream.Read(actualMaxArmor);
bitStream.Read(actualMaxImagination);
bitStream.Read(factionsSize);
for (uint32_t i = 0; i < factionsSize; i++) {
int32_t factionID{};
bitStream.Read(factionID);
factions.push_back(factionID);
}
bitStream.Read(isSmashable); // This is an option later and also a flag at this spot
bitStream.Read(isDead);
bitStream.Read(isSmashed);
// if IsSmashable is true, read the next bits.
bitStream.Read(isModuleAssembly);
bitStream.Read(optionExplodeFactor);
bitStream.Read(explodeFactor);
bitStream.Read(optionIsOnThreatList);
bitStream.Read(isThreatened);
EXPECT_EQ(optionStatusImmunityInfo, false);
EXPECT_EQ(optionStatsInfo, true);
EXPECT_EQ(currentHealth, 23);
EXPECT_EQ(maxHealth, 12345.0f);
EXPECT_EQ(currentArmor, 7);
EXPECT_EQ(maxArmor, 14.0f);
EXPECT_EQ(currentImagination, 6000);
EXPECT_EQ(maxImagination, 14000.0f);
EXPECT_EQ(damageAbsorptionPoints, 0.0f);
EXPECT_EQ(hasImmunity, false);
EXPECT_EQ(isGmImmune, false);
EXPECT_EQ(isShielded, false);
EXPECT_EQ(actualMaxHealth, 12345.0f);
EXPECT_EQ(actualMaxArmor, 14.0f);
EXPECT_EQ(actualMaxImagination, 14000.0f);
EXPECT_EQ(factionsSize, 2);
EXPECT_NE(std::find(factions.begin(), factions.end(), -1), factions.end());
EXPECT_NE(std::find(factions.begin(), factions.end(), 6), factions.end());
EXPECT_EQ(isSmashable, true);
EXPECT_EQ(isDead, false);
EXPECT_EQ(isSmashed, false);
EXPECT_EQ(isSmashable, true); // For the sake of readability with the struct viewers, we will test this twice since its used as an option here, but as a bool above.
EXPECT_EQ(isModuleAssembly, false);
EXPECT_EQ(optionExplodeFactor, true);
EXPECT_EQ(explodeFactor, 1.1f);
EXPECT_EQ(optionIsOnThreatList, true);
EXPECT_EQ(isThreatened, false);
}
bitStream.Reset();
}
/**
* Test serialization of a DestroyableComponent
*/
TEST_F(DestroyableTest, DestroyableComponentSerializeTest) {
bitStream.Reset();
// Initialize some values to be not default so we can test a full serialization
destroyableComponent->SetMaxHealth(1233.0f);
// Now we test a serialization for correctness.
destroyableComponent->Serialize(&bitStream, false, flags);
ASSERT_EQ(bitStream.GetNumberOfUnreadBits(), 422);
{
// Now read in the full serialized BitStream
bool optionStatsInfo{};
uint32_t currentHealth{};
float maxHealth{};
uint32_t currentArmor{};
float maxArmor{};
uint32_t currentImagination{};
float maxImagination{};
uint32_t damageAbsorptionPoints{};
bool hasImmunity{};
bool isGmImmune{};
bool isShielded{};
float actualMaxHealth{};
float actualMaxArmor{};
float actualMaxImagination{};
uint32_t factionsSize{};
std::vector<int32_t> factions{};
bool isSmashable{};
bool optionIsOnThreatList{};
bitStream.Read(optionStatsInfo);
bitStream.Read(currentHealth);
bitStream.Read(maxHealth);
bitStream.Read(currentArmor);
bitStream.Read(maxArmor);
bitStream.Read(currentImagination);
bitStream.Read(maxImagination);
bitStream.Read(damageAbsorptionPoints);
bitStream.Read(hasImmunity);
bitStream.Read(isGmImmune);
bitStream.Read(isShielded);
bitStream.Read(actualMaxHealth);
bitStream.Read(actualMaxArmor);
bitStream.Read(actualMaxImagination);
bitStream.Read(factionsSize);
for (uint32_t i = 0; i < factionsSize; i++) {
int32_t factionID{};
bitStream.Read(factionID);
factions.push_back(factionID);
}
bitStream.Read(isSmashable);
bitStream.Read(optionIsOnThreatList);
EXPECT_EQ(optionStatsInfo, true);
EXPECT_EQ(currentHealth, 23);
EXPECT_EQ(maxHealth, 1233.0f);
EXPECT_EQ(currentArmor, 7);
EXPECT_EQ(maxArmor, 14.0f);
EXPECT_EQ(currentImagination, 6000);
EXPECT_EQ(maxImagination, 14000.0f);
EXPECT_EQ(damageAbsorptionPoints, 0.0f);
EXPECT_EQ(hasImmunity, false);
EXPECT_EQ(isGmImmune, false);
EXPECT_EQ(isShielded, false);
EXPECT_EQ(actualMaxHealth, 1233.0f);
EXPECT_EQ(actualMaxArmor, 14.0f);
EXPECT_EQ(actualMaxImagination, 14000.0f);
EXPECT_EQ(factionsSize, 2);
EXPECT_NE(std::find(factions.begin(), factions.end(), -1), factions.end());
EXPECT_NE(std::find(factions.begin(), factions.end(), 6), factions.end());
EXPECT_EQ(isSmashable, true);
EXPECT_EQ(optionIsOnThreatList, false); // Always zero for now on serialization
}
}
/**
* Test the Damage method of DestroyableComponent
*/
TEST_F(DestroyableTest, DestroyableComponentDamageTest) {
// Do some actions
destroyableComponent->SetMaxHealth(100.0f);
destroyableComponent->SetHealth(100);
destroyableComponent->SetMaxArmor(0.0f);
destroyableComponent->Damage(10, LWOOBJID_EMPTY);
// Check that we take damage
ASSERT_EQ(destroyableComponent->GetHealth(), 90);
// Check that if we have armor, we take the correct amount of damage
destroyableComponent->SetMaxArmor(10.0f);
destroyableComponent->SetArmor(5);
destroyableComponent->Damage(10, LWOOBJID_EMPTY);
ASSERT_EQ(destroyableComponent->GetHealth(), 85);
// Check that if we have damage absorption we take the correct damage
destroyableComponent->SetDamageToAbsorb(10);
destroyableComponent->Damage(9, LWOOBJID_EMPTY);
ASSERT_EQ(destroyableComponent->GetHealth(), 85);
ASSERT_EQ(destroyableComponent->GetDamageToAbsorb(), 1);
destroyableComponent->Damage(6, LWOOBJID_EMPTY);
ASSERT_EQ(destroyableComponent->GetHealth(), 80);
// Check that we take the correct reduced damage if we take reduced damage
destroyableComponent->SetDamageReduction(2);
destroyableComponent->Damage(7, LWOOBJID_EMPTY);
ASSERT_EQ(destroyableComponent->GetHealth(), 75);
destroyableComponent->Damage(2, LWOOBJID_EMPTY);
ASSERT_EQ(destroyableComponent->GetHealth(), 74);
ASSERT_EQ(destroyableComponent->GetDamageReduction(), 2);
destroyableComponent->SetDamageReduction(0);
// Check that blocking works
destroyableComponent->SetAttacksToBlock(1);
destroyableComponent->Damage(UINT32_MAX, LWOOBJID_EMPTY);
ASSERT_EQ(destroyableComponent->GetHealth(), 74);
destroyableComponent->Damage(4, LWOOBJID_EMPTY);
ASSERT_EQ(destroyableComponent->GetHealth(), 70);
// Check that immunity works
destroyableComponent->SetIsImmune(true);
destroyableComponent->Damage(UINT32_MAX, LWOOBJID_EMPTY);
ASSERT_EQ(destroyableComponent->GetHealth(), 70);
ASSERT_TRUE(destroyableComponent->IsImmune());
destroyableComponent->SetIsImmune(false);
destroyableComponent->SetIsGMImmune(true);
destroyableComponent->Damage(UINT32_MAX, LWOOBJID_EMPTY);
ASSERT_EQ(destroyableComponent->GetHealth(), 70);
ASSERT_TRUE(destroyableComponent->IsImmune());
destroyableComponent->SetIsGMImmune(false);
// Check knockback immunity
destroyableComponent->SetIsShielded(true);
ASSERT_TRUE(destroyableComponent->IsKnockbackImmune());
// Finally deal enough damage to kill the Entity
destroyableComponent->Damage(71, LWOOBJID_EMPTY);
ASSERT_EQ(destroyableComponent->GetHealth(), 0);
// Now lets heal some stats
destroyableComponent->Heal(15);
ASSERT_EQ(destroyableComponent->GetHealth(), 15);
destroyableComponent->Heal(15000);
ASSERT_EQ(destroyableComponent->GetHealth(), 100);
destroyableComponent->Repair(10);
ASSERT_EQ(destroyableComponent->GetArmor(), 10);
destroyableComponent->Repair(15000);
ASSERT_EQ(destroyableComponent->GetArmor(), 10);
destroyableComponent->SetMaxImagination(100.0f);
destroyableComponent->SetImagination(0);
destroyableComponent->Imagine(99);
ASSERT_EQ(destroyableComponent->GetImagination(), 99);
destroyableComponent->Imagine(4);
ASSERT_EQ(destroyableComponent->GetImagination(), 100);
}
TEST_F(DestroyableTest, DestroyableComponentFactionTest) {
ASSERT_TRUE(destroyableComponent->HasFaction(-1));
ASSERT_TRUE(destroyableComponent->HasFaction(6));
}
TEST_F(DestroyableTest, DestroyableComponentValiditiyTest) {
auto* enemyEntity = new Entity(19, info);
auto* enemyDestroyableComponent = new DestroyableComponent(enemyEntity);
enemyEntity->AddComponent(COMPONENT_TYPE_DESTROYABLE, enemyDestroyableComponent);
enemyDestroyableComponent->AddFactionNoLookup(16);
destroyableComponent->AddEnemyFaction(16);
EXPECT_TRUE(destroyableComponent->IsEnemy(enemyEntity));
EXPECT_FALSE(destroyableComponent->IsFriend(enemyEntity));
delete enemyEntity;
}