summaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tests/BlockTypeRegistry/BlockTypeRegistryTest.cpp228
-rw-r--r--tests/BlockTypeRegistry/CMakeLists.txt40
-rw-r--r--tests/CMakeLists.txt1
-rw-r--r--tests/TestHelpers.h79
4 files changed, 348 insertions, 0 deletions
diff --git a/tests/BlockTypeRegistry/BlockTypeRegistryTest.cpp b/tests/BlockTypeRegistry/BlockTypeRegistryTest.cpp
new file mode 100644
index 000000000..c2d8717e5
--- /dev/null
+++ b/tests/BlockTypeRegistry/BlockTypeRegistryTest.cpp
@@ -0,0 +1,228 @@
+
+#include "Globals.h"
+#include <thread>
+#include "BlockTypeRegistry.h"
+#include "../TestHelpers.h"
+
+
+
+
+/** Dummy BlockState implementation */
+class BlockState
+{
+public:
+ BlockState() = default;
+};
+
+
+
+
+/** Dummy cBlockHandler implementation that allows simple checking for equality through mIdent. */
+class cBlockHandler
+{
+public:
+ cBlockHandler(UInt32 aIdent):
+ mIdent(aIdent)
+ {
+ }
+
+ UInt32 mIdent;
+};
+
+
+
+
+
+/** Tests simple block type name registration.
+Registers a block type, checks that the type is then registered. */
+static void testSimpleReg()
+{
+ LOGD("Testing simple registration...");
+
+ // Register the block type:
+ BlockTypeRegistry reg;
+ AString blockTypeName("test:block1");
+ AString pluginName("testPlugin");
+ AString hint1("testHint1");
+ AString hint1Value("value1");
+ std::shared_ptr<cBlockHandler> handler(new cBlockHandler(0x12345678));
+ std::map<AString, AString> hints = {{hint1, hint1Value}, {"testHint2", "value2"}};
+ std::map<AString, BlockInfo::HintCallback> hintCallbacks;
+ reg.registerBlockType(pluginName, blockTypeName, handler, hints, hintCallbacks);
+
+ // Query the registration:
+ auto blockInfo = reg.blockInfo(blockTypeName);
+ TEST_NOTEQUAL(blockInfo, nullptr);
+ TEST_EQUAL(blockInfo->blockTypeName(), blockTypeName);
+ TEST_EQUAL(blockInfo->pluginName(), pluginName);
+ TEST_EQUAL(blockInfo->handler(), handler);
+ TEST_EQUAL(blockInfo->hintValue(hint1, BlockState()), hint1Value);
+ TEST_EQUAL(blockInfo->hintValue("nonexistent", BlockState()), "");
+}
+
+
+
+
+
+/** Tests that the plugin-based information is used correctly for registration.
+Registers two different block types with two different plugins, then tries to re-register them from a different plugin.
+Finally removes the registration through removeAllByPlugin() and checks its success. */
+static void testPlugins()
+{
+ LOGD("Testing plugin-based checks / removal...");
+
+ // Register the block types:
+ BlockTypeRegistry reg;
+ AString blockTypeName1("test:block1");
+ AString pluginName1("testPlugin1");
+ AString hint1("testHint1");
+ AString hint1Value("value1");
+ std::shared_ptr<cBlockHandler> handler1(new cBlockHandler(1));
+ std::map<AString, AString> hints = {{hint1, hint1Value}, {"testHint2", "value2"}};
+ std::map<AString, BlockInfo::HintCallback> hintCallbacks;
+ reg.registerBlockType(pluginName1, blockTypeName1, handler1, hints, hintCallbacks);
+ AString blockTypeName2("test:block2");
+ AString pluginName2("testPlugin2");
+ std::shared_ptr<cBlockHandler> handler2(new cBlockHandler(2));
+ reg.registerBlockType(pluginName2, blockTypeName2, handler2, hints, hintCallbacks);
+
+ // Test the refusal to register under a different plugin:
+ TEST_THROWS(reg.registerBlockType(pluginName2, blockTypeName1, handler2, hints, hintCallbacks), BlockTypeRegistry::AlreadyRegisteredException);
+ TEST_EQUAL(reg.blockInfo(blockTypeName1)->handler()->mIdent, 1); // Did we overwrite the old registration?
+ reg.registerBlockType(pluginName1, blockTypeName1, handler1, hints, hintCallbacks); // Re-registering must succeed
+
+ // Unregister by plugin, then re-register from a different plugin:
+ reg.removeAllByPlugin(pluginName1);
+ TEST_EQUAL(reg.blockInfo(blockTypeName1), nullptr); // Unregistered successfully
+ TEST_NOTEQUAL(reg.blockInfo(blockTypeName2), nullptr); // Didn't unregister from the other plugin
+ std::shared_ptr<cBlockHandler> handler3(new cBlockHandler(3));
+ reg.registerBlockType(pluginName2, blockTypeName1, handler3, hints, hintCallbacks);
+ TEST_NOTEQUAL(reg.blockInfo(blockTypeName1), nullptr); // Registered successfully
+ TEST_EQUAL(reg.blockInfo(blockTypeName1)->pluginName(), pluginName2);
+ TEST_EQUAL(reg.blockInfo(blockTypeName1)->handler()->mIdent, 3);
+ TEST_EQUAL(reg.blockInfo(blockTypeName2)->handler()->mIdent, 2);
+ reg.removeAllByPlugin(pluginName2);
+ TEST_EQUAL(reg.blockInfo(blockTypeName1), nullptr); // Unregistered successfully
+ TEST_EQUAL(reg.blockInfo(blockTypeName2), nullptr); // Unregistered successfully
+}
+
+
+
+
+/** Tests that the callback-based hints work properly. */
+static void testHintCallbacks()
+{
+ LOGD("Testing hint callbacks...");
+
+ // Register the block type:
+ BlockTypeRegistry reg;
+ AString blockTypeName("test:block1");
+ AString pluginName("testPlugin");
+ AString hint1("testHint1");
+ AString hint1Value("value1");
+ AString hc1("hintCallback1");
+ int callbackCount = 0;
+ auto callback1 = [&callbackCount](const AString & aBlockType, const BlockState & aBlockState)
+ {
+ callbackCount = callbackCount + 1;
+ return aBlockType + "_hint";
+ };
+ std::shared_ptr<cBlockHandler> handler(new cBlockHandler(0x12345678));
+ std::map<AString, AString> hints = {{hint1, hint1Value}, {"testHint2", "value2"}};
+ std::map<AString, BlockInfo::HintCallback> hintCallbacks = {{hc1, callback1}};
+ reg.registerBlockType(pluginName, blockTypeName, handler, hints, hintCallbacks);
+
+ // Check that querying the hint using a callback works:
+ TEST_EQUAL(reg.blockInfo(blockTypeName)->hintValue(hc1, BlockState()), blockTypeName + "_hint");
+ TEST_EQUAL(callbackCount, 1); // Called exactly once
+}
+
+
+
+
+
+/** Tests whether thread-locking works properly by running two threads,
+one constantly (re-)registering and the other one constantly querying the same block type. */
+static void testThreadLocking()
+{
+ LOGD("Testing thread locking...");
+
+ // Register the block type:
+ BlockTypeRegistry reg;
+ AString blockTypeName("test:block1");
+ AString pluginName("testPlugin");
+ AString hint1("testHint1");
+ AString hint1Value("value1");
+ std::shared_ptr<cBlockHandler> handler(new cBlockHandler(0x12345678));
+ std::map<AString, AString> hints = {{hint1, hint1Value}, {"testHint2", "value2"}};
+ std::map<AString, BlockInfo::HintCallback> hintCallbacks;
+ reg.registerBlockType(pluginName, blockTypeName, handler, hints, hintCallbacks);
+
+ // Run the two threads for at least a second:
+ auto endTime = time(nullptr) + 2;
+ auto keepRegistering = [&]()
+ {
+ while (time(nullptr) < endTime)
+ {
+ reg.registerBlockType(pluginName, blockTypeName, handler, hints, hintCallbacks);
+ }
+ };
+ auto keepQuerying = [&]()
+ {
+ unsigned numQueries = 0;
+ while (time(nullptr) < endTime)
+ {
+ TEST_NOTEQUAL(reg.blockInfo(blockTypeName), nullptr);
+ numQueries += 1;
+ }
+ LOGD("%u queries have been executed", numQueries);
+ };
+ std::thread thr1(keepRegistering);
+ std::thread thr2(keepQuerying);
+ thr1.join();
+ thr2.join();
+}
+
+
+
+
+
+static void testBlockTypeRegistry()
+{
+ testSimpleReg();
+ testPlugins();
+ testHintCallbacks();
+ testThreadLocking();
+}
+
+
+
+
+
+int main()
+{
+ LOGD("BlockTypeRegistryTest started");
+
+ try
+ {
+ testBlockTypeRegistry();
+ }
+ catch (const TestException & exc)
+ {
+ LOGERROR("BlockTypeRegistryTest has failed, an unhandled exception was thrown: %s", exc.mMessage.c_str());
+ return 1;
+ }
+ catch (...)
+ {
+ LOGERROR("BlockTypeRegistryTest has failed, an unhandled exception was thrown.");
+ return 1;
+ }
+
+ LOGD("BlockTypeRegistryTest finished");
+
+ return 0;
+}
+
+
+
+
diff --git a/tests/BlockTypeRegistry/CMakeLists.txt b/tests/BlockTypeRegistry/CMakeLists.txt
new file mode 100644
index 000000000..25b18c373
--- /dev/null
+++ b/tests/BlockTypeRegistry/CMakeLists.txt
@@ -0,0 +1,40 @@
+cmake_minimum_required(VERSION 3.0.2)
+enable_testing()
+
+include_directories(${CMAKE_SOURCE_DIR}/src/)
+
+add_definitions(-DTEST_GLOBALS=1)
+
+
+
+
+
+# Define individual test executables:
+
+# BlockTypeRegistryTest: Verify that the BlockTypeRegistry class works as intended:
+add_executable(BlockTypeRegistryTest
+ BlockTypeRegistryTest.cpp
+ ../TestHelpers.h
+ ${CMAKE_SOURCE_DIR}/src/BlockTypeRegistry.cpp
+ ${CMAKE_SOURCE_DIR}/src/StringUtils.cpp
+ ${CMAKE_SOURCE_DIR}/src/OSSupport/CriticalSection.cpp
+)
+target_link_libraries(BlockTypeRegistryTest fmt::fmt)
+
+
+
+
+
+# Define individual tests:
+
+add_test(NAME BlockTypeRegistryTest COMMAND BlockTypeRegistryTest)
+
+
+
+
+
+# Put all the tests into a solution folder (MSVC):
+set_target_properties(
+ BlockTypeRegistryTest
+ PROPERTIES FOLDER Tests/BlockTypeRegistry
+)
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 20ae1bfa3..74e4323ec 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -6,6 +6,7 @@ endif()
add_definitions(-DTEST_GLOBALS=1)
+add_subdirectory(BlockTypeRegistry)
add_subdirectory(BoundingBox)
add_subdirectory(ByteBuffer)
add_subdirectory(ChunkData)
diff --git a/tests/TestHelpers.h b/tests/TestHelpers.h
new file mode 100644
index 000000000..2e43e1e0d
--- /dev/null
+++ b/tests/TestHelpers.h
@@ -0,0 +1,79 @@
+// Helper macros for writing exception-based tests
+
+
+
+
+/** The exception that is thrown if a test fails.
+It doesn't inherit from any type so that it is not possible to catch it by a mistake,
+it needs to be caught explicitly (used in the TEST_THROWS).
+It bears a single message that is to be displayed to stderr. */
+class TestException
+{
+public:
+ TestException(const AString & aMessage):
+ mMessage(aMessage)
+ {
+ }
+
+ AString mMessage;
+};
+
+
+
+
+
+/** Checks that the two values are equal; if not, throws a TestException. */
+#define TEST_EQUAL(VAL1, VAL2) \
+ if (VAL1 != VAL2) \
+ { \
+ throw TestException(Printf("%s (line %d): Equality test failed: %s != %s", \
+ __FUNCTION__, __LINE__, \
+ #VAL1, #VAL2 \
+ )); \
+ }
+
+
+
+/** Checks that the two values are not equal; if they are, throws a TestException. */
+#define TEST_NOTEQUAL(VAL1, VAL2) \
+ if (VAL1 == VAL2) \
+ { \
+ throw TestException(Printf("%s (line %d): Inequality test failed: %s == %s", \
+ __FUNCTION__, __LINE__, \
+ #VAL1, #VAL2 \
+ )); \
+ }
+
+
+
+/** Checks that the statement throws an exception of the specified class. */
+#define TEST_THROWS(Stmt, ExcClass) \
+ try \
+ { \
+ Stmt; \
+ throw TestException(Printf("%s (line %d): Failed to throw an exception of type %s", \
+ __FUNCTION__, __LINE__, \
+ #ExcClass \
+ )); \
+ } \
+ catch (const ExcClass &) \
+ { \
+ /* This is the expected case. */ \
+ } \
+ catch (const std::exception & exc) \
+ { \
+ throw TestException(Printf("%s (line %d): An unexpected std::exception descendant was thrown, was expecting type %s. Message is: %s", \
+ __FUNCTION__, __LINE__, \
+ #ExcClass, exc.what() \
+ )); \
+ } \
+ catch (...) \
+ { \
+ throw TestException(Printf("%s (line %d): An unexpected exception object was thrown, was expecting type %s", \
+ __FUNCTION__, __LINE__, \
+ #ExcClass \
+ )); \
+ }
+
+
+