BlockTypeRegistry: Initial skeleton
This commit is contained in:
parent
c00b365617
commit
3722a239bf
153
src/BlockTypeRegistry.cpp
Normal file
153
src/BlockTypeRegistry.cpp
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
|
||||||
|
#include "Globals.h"
|
||||||
|
#include "BlockTypeRegistry.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// BlockInfo:
|
||||||
|
|
||||||
|
BlockInfo::BlockInfo(
|
||||||
|
const AString & aPluginName,
|
||||||
|
const AString & aBlockTypeName,
|
||||||
|
std::shared_ptr<cBlockHandler> aHandler,
|
||||||
|
const std::map<AString, AString> & aHints,
|
||||||
|
const std::map<AString, BlockInfo::HintCallback> & aHintCallbacks
|
||||||
|
):
|
||||||
|
mPluginName(aPluginName),
|
||||||
|
mBlockTypeName(aBlockTypeName),
|
||||||
|
mHandler(aHandler),
|
||||||
|
mHints(aHints),
|
||||||
|
mHintCallbacks(aHintCallbacks)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
AString BlockInfo::hintValue(
|
||||||
|
const AString & aHintName,
|
||||||
|
const BlockState & aBlockState
|
||||||
|
)
|
||||||
|
{
|
||||||
|
// Search the hint callbacks first:
|
||||||
|
auto itrC = mHintCallbacks.find(aHintName);
|
||||||
|
if (itrC != mHintCallbacks.end())
|
||||||
|
{
|
||||||
|
// Hint callback found, use it:
|
||||||
|
return itrC->second(mBlockTypeName, aBlockState);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Search the static hints:
|
||||||
|
auto itr = mHints.find(aHintName);
|
||||||
|
if (itr != mHints.end())
|
||||||
|
{
|
||||||
|
// Hint found, use it:
|
||||||
|
return itr->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nothing found, return empty string:
|
||||||
|
return AString();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// BlockTypeRegistry:
|
||||||
|
|
||||||
|
void BlockTypeRegistry::registerBlockType(
|
||||||
|
const AString & aPluginName,
|
||||||
|
const AString & aBlockTypeName,
|
||||||
|
std::shared_ptr<cBlockHandler> aHandler,
|
||||||
|
const std::map<AString, AString> & aHints,
|
||||||
|
const std::map<AString, BlockInfo::HintCallback> & aHintCallbacks
|
||||||
|
)
|
||||||
|
{
|
||||||
|
auto blockInfo = std::make_shared<BlockInfo>(aPluginName, aBlockTypeName, aHandler, aHints, aHintCallbacks);
|
||||||
|
|
||||||
|
// Check previous registrations:
|
||||||
|
cCSLock lock(mCSRegistry);
|
||||||
|
auto itr = mRegistry.find(aBlockTypeName);
|
||||||
|
if (itr != mRegistry.end())
|
||||||
|
{
|
||||||
|
if (itr->second->pluginName() != aPluginName)
|
||||||
|
{
|
||||||
|
throw AlreadyRegisteredException(itr->second, blockInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Store the registration:
|
||||||
|
mRegistry[aBlockTypeName] = blockInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
std::shared_ptr<BlockInfo> BlockTypeRegistry::blockInfo(const AString & aBlockTypeName)
|
||||||
|
{
|
||||||
|
cCSLock lock(mCSRegistry);
|
||||||
|
auto itr = mRegistry.find(aBlockTypeName);
|
||||||
|
if (itr == mRegistry.end())
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return itr->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void BlockTypeRegistry::removeAllByPlugin(const AString & aPluginName)
|
||||||
|
{
|
||||||
|
cCSLock lock(mCSRegistry);
|
||||||
|
for (auto itr = mRegistry.begin(); itr != mRegistry.end();)
|
||||||
|
{
|
||||||
|
if (itr->second->pluginName() == aPluginName)
|
||||||
|
{
|
||||||
|
itr = mRegistry.erase(itr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
++itr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// BlockTypeRegistry::AlreadyRegisteredException:
|
||||||
|
|
||||||
|
BlockTypeRegistry::AlreadyRegisteredException::AlreadyRegisteredException(
|
||||||
|
std::shared_ptr<BlockInfo> aPreviousRegistration,
|
||||||
|
std::shared_ptr<BlockInfo> aNewRegistration
|
||||||
|
) :
|
||||||
|
Super(message(aPreviousRegistration, aNewRegistration)),
|
||||||
|
mPreviousRegistration(aPreviousRegistration),
|
||||||
|
mNewRegistration(aNewRegistration)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
AString BlockTypeRegistry::AlreadyRegisteredException::message(
|
||||||
|
std::shared_ptr<BlockInfo> aPreviousRegistration,
|
||||||
|
std::shared_ptr<BlockInfo> aNewRegistration
|
||||||
|
)
|
||||||
|
{
|
||||||
|
return Printf("Attempting to register BlockTypeName %s from plugin %s, while it is already registered in plugin %s",
|
||||||
|
aNewRegistration->blockTypeName().c_str(),
|
||||||
|
aNewRegistration->pluginName().c_str(),
|
||||||
|
aPreviousRegistration->pluginName().c_str()
|
||||||
|
);
|
||||||
|
}
|
158
src/BlockTypeRegistry.h
Normal file
158
src/BlockTypeRegistry.h
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// fwd:
|
||||||
|
class BlockState;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** Complete information about a single block type.
|
||||||
|
The BlockTypeRegistry uses this structure to store the registered information. */
|
||||||
|
class BlockInfo
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Callback is used to query block hints dynamically, based on the current BlockState.
|
||||||
|
Useful for example for redstone lamps that can be turned on or off. */
|
||||||
|
using HintCallback = std::function<AString(const AString & aTypeName, const BlockState & aBlockState)>;
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates a new instance with the specified BlockTypeName and handler / hints / callbacks.
|
||||||
|
aPluginName specifies the name of the plugin to associate with the block type (to allow unload / reload). */
|
||||||
|
BlockInfo(
|
||||||
|
const AString & aPluginName,
|
||||||
|
const AString & aBlockTypeName,
|
||||||
|
std::shared_ptr<cBlockHandler> aHandler,
|
||||||
|
const std::map<AString, AString> & aHints = std::map<AString, AString>(),
|
||||||
|
const std::map<AString, HintCallback> & aHintCallbacks = std::map<AString, HintCallback>()
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
/** Retrieves the value associated with the specified hint for this specific BlockTypeName and BlockState.
|
||||||
|
Queries callbacks first, then hints if a callback doesn't exist.
|
||||||
|
Returns an empty string if hint not found at all. */
|
||||||
|
AString hintValue(
|
||||||
|
const AString & aHintName,
|
||||||
|
const BlockState & aBlockState
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simple getters:
|
||||||
|
const AString & pluginName() const { return mPluginName; }
|
||||||
|
const AString & blockTypeName() const { return mBlockTypeName; }
|
||||||
|
std::shared_ptr<cBlockHandler> handler() const { return mHandler; }
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** The name of the plugin that registered the block. */
|
||||||
|
AString mPluginName;
|
||||||
|
|
||||||
|
/** The name of the block type, such as "minecraft:redstone_lamp" */
|
||||||
|
AString mBlockTypeName;
|
||||||
|
|
||||||
|
/** The callbacks to call for various interaction. */
|
||||||
|
std::shared_ptr<cBlockHandler> mHandler;
|
||||||
|
|
||||||
|
/** Optional hints for any subsystem to use, such as "IsSnowable" -> "1". */
|
||||||
|
std::map<AString, AString> mHints;
|
||||||
|
|
||||||
|
/** The callbacks for dynamic evaluation of hints, such as "LightValue" -> function(BlockTypeName, BlockState). */
|
||||||
|
std::map<AString, HintCallback> mHintCallbacks;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** Stores information on all known block types.
|
||||||
|
Can dynamically add and remove block types.
|
||||||
|
Block types are identified using BlockTypeName.
|
||||||
|
Supports unregistering and re-registering the same type by the same plugin.
|
||||||
|
Stores the name of the plugin that registered the type, for better plugin error messages ("already registered in X")
|
||||||
|
and so that we can unload and reload plugins. */
|
||||||
|
class BlockTypeRegistry
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// fwd:
|
||||||
|
class AlreadyRegisteredException;
|
||||||
|
|
||||||
|
|
||||||
|
/** Creates an empty new instance of the block type registry */
|
||||||
|
BlockTypeRegistry() = default;
|
||||||
|
|
||||||
|
/** Registers the specified block type.
|
||||||
|
If the block type already exists and the plugin is the same, updates the registration.
|
||||||
|
If the block type already exists and the plugin is different, throws an AlreadyRegisteredException. */
|
||||||
|
void registerBlockType(
|
||||||
|
const AString & aPluginName,
|
||||||
|
const AString & aBlockTypeName,
|
||||||
|
std::shared_ptr<cBlockHandler> aHandler,
|
||||||
|
const std::map<AString, AString> & aHints = std::map<AString, AString>(),
|
||||||
|
const std::map<AString, BlockInfo::HintCallback> & aHintCallbacks = std::map<AString, BlockInfo::HintCallback>()
|
||||||
|
);
|
||||||
|
|
||||||
|
/** Returns the registration information for the specified BlockTypeName.
|
||||||
|
Returns nullptr if BlockTypeName not found. */
|
||||||
|
std::shared_ptr<BlockInfo> blockInfo(const AString & aBlockTypeName);
|
||||||
|
|
||||||
|
/** Removes all registrations done by the specified plugin. */
|
||||||
|
void removeAllByPlugin(const AString & aPluginName);
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
/** The actual block type registry.
|
||||||
|
Maps the BlockTypeName to the BlockInfo instance. */
|
||||||
|
std::map<AString, std::shared_ptr<BlockInfo>> mRegistry;
|
||||||
|
|
||||||
|
/** The CS that protects mRegistry against multithreaded access. */
|
||||||
|
cCriticalSection mCSRegistry;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** The exception thrown from BlockTypeRegistry::registerBlockType() if the same block type is being registered from a different plugin. */
|
||||||
|
class BlockTypeRegistry::AlreadyRegisteredException: public std::runtime_error
|
||||||
|
{
|
||||||
|
using Super = std::runtime_error;
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
/** Creates a new instance of the exception that provides info on both the original registration and the newly attempted
|
||||||
|
registration that caused the failure. */
|
||||||
|
AlreadyRegisteredException(
|
||||||
|
std::shared_ptr<BlockInfo> aPreviousRegistration,
|
||||||
|
std::shared_ptr<BlockInfo> aNewRegistration
|
||||||
|
);
|
||||||
|
|
||||||
|
// Simple getters:
|
||||||
|
std::shared_ptr<BlockInfo> previousRegistration() const { return mPreviousRegistration; }
|
||||||
|
std::shared_ptr<BlockInfo> newRegistration() const { return mNewRegistration; }
|
||||||
|
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
std::shared_ptr<BlockInfo> mPreviousRegistration;
|
||||||
|
std::shared_ptr<BlockInfo> mNewRegistration;
|
||||||
|
|
||||||
|
|
||||||
|
/** Returns the general exception message formatted by the two registrations.
|
||||||
|
The output is used when logging. */
|
||||||
|
static AString message(
|
||||||
|
std::shared_ptr<BlockInfo> aPreviousRegistration,
|
||||||
|
std::shared_ptr<BlockInfo> aNewRegistration
|
||||||
|
);
|
||||||
|
};
|
@ -17,6 +17,7 @@ SET (SRCS
|
|||||||
BlockArea.cpp
|
BlockArea.cpp
|
||||||
BlockID.cpp
|
BlockID.cpp
|
||||||
BlockInfo.cpp
|
BlockInfo.cpp
|
||||||
|
BlockTypeRegistry.cpp
|
||||||
BrewingRecipes.cpp
|
BrewingRecipes.cpp
|
||||||
Broadcaster.cpp
|
Broadcaster.cpp
|
||||||
BoundingBox.cpp
|
BoundingBox.cpp
|
||||||
@ -84,6 +85,7 @@ SET (HDRS
|
|||||||
BlockInServerPluginInterface.h
|
BlockInServerPluginInterface.h
|
||||||
BlockInfo.h
|
BlockInfo.h
|
||||||
BlockTracer.h
|
BlockTracer.h
|
||||||
|
BlockTypeRegistry.h
|
||||||
BrewingRecipes.h
|
BrewingRecipes.h
|
||||||
BoundingBox.h
|
BoundingBox.h
|
||||||
BuildInfo.h
|
BuildInfo.h
|
||||||
|
228
tests/BlockTypeRegistry/BlockTypeRegistryTest.cpp
Normal file
228
tests/BlockTypeRegistry/BlockTypeRegistryTest.cpp
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
40
tests/BlockTypeRegistry/CMakeLists.txt
Normal file
40
tests/BlockTypeRegistry/CMakeLists.txt
Normal file
@ -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
|
||||||
|
)
|
@ -6,6 +6,7 @@ endif()
|
|||||||
|
|
||||||
add_definitions(-DTEST_GLOBALS=1)
|
add_definitions(-DTEST_GLOBALS=1)
|
||||||
|
|
||||||
|
add_subdirectory(BlockTypeRegistry)
|
||||||
add_subdirectory(BoundingBox)
|
add_subdirectory(BoundingBox)
|
||||||
add_subdirectory(ByteBuffer)
|
add_subdirectory(ByteBuffer)
|
||||||
add_subdirectory(ChunkData)
|
add_subdirectory(ChunkData)
|
||||||
|
79
tests/TestHelpers.h
Normal file
79
tests/TestHelpers.h
Normal file
@ -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 \
|
||||||
|
)); \
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user