Add files via upload

This commit is contained in:
VoidCosmos 2021-09-19 15:56:54 +05:30 committed by GitHub
parent 39c1945fe8
commit 954489e276
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 11638 additions and 0 deletions

View File

@ -0,0 +1,9 @@
set(server_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/activeobjectmgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/luaentity_sao.cpp
${CMAKE_CURRENT_SOURCE_DIR}/mods.cpp
${CMAKE_CURRENT_SOURCE_DIR}/player_sao.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serveractiveobject.cpp
${CMAKE_CURRENT_SOURCE_DIR}/serverinventorymgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/unit_sao.cpp
PARENT_SCOPE)

View File

@ -0,0 +1,169 @@
/*
Minetest
Copyright (C) 2010-2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <log.h>
#include "mapblock.h"
#include "profiler.h"
#include "activeobjectmgr.h"
namespace server
{
void ActiveObjectMgr::clear(const std::function<bool(ServerActiveObject *, u16)> &cb)
{
std::vector<u16> objects_to_remove;
for (auto &it : m_active_objects) {
if (cb(it.second, it.first)) {
// Id to be removed from m_active_objects
objects_to_remove.push_back(it.first);
}
}
// Remove references from m_active_objects
for (u16 i : objects_to_remove) {
m_active_objects.erase(i);
}
}
void ActiveObjectMgr::step(
float dtime, const std::function<void(ServerActiveObject *)> &f)
{
g_profiler->avg("ActiveObjectMgr: SAO count [#]", m_active_objects.size());
for (auto &ao_it : m_active_objects) {
f(ao_it.second);
}
}
// clang-format off
bool ActiveObjectMgr::registerObject(ServerActiveObject *obj)
{
assert(obj); // Pre-condition
if (obj->getId() == 0) {
u16 new_id = getFreeId();
if (new_id == 0) {
errorstream << "Server::ActiveObjectMgr::addActiveObjectRaw(): "
<< "no free id available" << std::endl;
if (obj->environmentDeletes())
delete obj;
return false;
}
obj->setId(new_id);
} else {
verbosestream << "Server::ActiveObjectMgr::addActiveObjectRaw(): "
<< "supplied with id " << obj->getId() << std::endl;
}
if (!isFreeId(obj->getId())) {
errorstream << "Server::ActiveObjectMgr::addActiveObjectRaw(): "
<< "id is not free (" << obj->getId() << ")" << std::endl;
if (obj->environmentDeletes())
delete obj;
return false;
}
if (objectpos_over_limit(obj->getBasePosition())) {
v3f p = obj->getBasePosition();
warningstream << "Server::ActiveObjectMgr::addActiveObjectRaw(): "
<< "object position (" << p.X << "," << p.Y << "," << p.Z
<< ") outside maximum range" << std::endl;
if (obj->environmentDeletes())
delete obj;
return false;
}
m_active_objects[obj->getId()] = obj;
verbosestream << "Server::ActiveObjectMgr::addActiveObjectRaw(): "
<< "Added id=" << obj->getId() << "; there are now "
<< m_active_objects.size() << " active objects." << std::endl;
return true;
}
void ActiveObjectMgr::removeObject(u16 id)
{
verbosestream << "Server::ActiveObjectMgr::removeObject(): "
<< "id=" << id << std::endl;
ServerActiveObject *obj = getActiveObject(id);
if (!obj) {
infostream << "Server::ActiveObjectMgr::removeObject(): "
<< "id=" << id << " not found" << std::endl;
return;
}
m_active_objects.erase(id);
delete obj;
}
// clang-format on
void ActiveObjectMgr::getObjectsInsideRadius(const v3f &pos, float radius,
std::vector<ServerActiveObject *> &result,
std::function<bool(ServerActiveObject *obj)> include_obj_cb)
{
float r2 = radius * radius;
for (auto &activeObject : m_active_objects) {
ServerActiveObject *obj = activeObject.second;
const v3f &objectpos = obj->getBasePosition();
if (objectpos.getDistanceFromSQ(pos) > r2)
continue;
if (!include_obj_cb || include_obj_cb(obj))
result.push_back(obj);
}
}
void ActiveObjectMgr::getAddedActiveObjectsAroundPos(const v3f &player_pos, f32 radius,
f32 player_radius, std::set<u16> &current_objects,
std::queue<u16> &added_objects)
{
/*
Go through the object list,
- discard removed/deactivated objects,
- discard objects that are too far away,
- discard objects that are found in current_objects.
- add remaining objects to added_objects
*/
for (auto &ao_it : m_active_objects) {
u16 id = ao_it.first;
// Get object
ServerActiveObject *object = ao_it.second;
if (!object)
continue;
if (object->isGone())
continue;
f32 distance_f = object->getBasePosition().getDistanceFrom(player_pos);
if (object->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
// Discard if too far
if (distance_f > player_radius && player_radius != 0)
continue;
} else if (distance_f > radius)
continue;
// Discard if already on current_objects
auto n = current_objects.find(id);
if (n != current_objects.end())
continue;
// Add to added_objects
added_objects.push(id);
}
}
} // namespace server

View File

@ -0,0 +1,46 @@
/*
Minetest
Copyright (C) 2010-2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <functional>
#include <vector>
#include "../activeobjectmgr.h"
#include "serveractiveobject.h"
namespace server
{
class ActiveObjectMgr : public ::ActiveObjectMgr<ServerActiveObject>
{
public:
void clear(const std::function<bool(ServerActiveObject *, u16)> &cb);
void step(float dtime,
const std::function<void(ServerActiveObject *)> &f) override;
bool registerObject(ServerActiveObject *obj) override;
void removeObject(u16 id) override;
void getObjectsInsideRadius(const v3f &pos, float radius,
std::vector<ServerActiveObject *> &result,
std::function<bool(ServerActiveObject *obj)> include_obj_cb);
void getAddedActiveObjectsAroundPos(const v3f &player_pos, f32 radius,
f32 player_radius, std::set<u16> &current_objects,
std::queue<u16> &added_objects);
};
} // namespace server

View File

@ -0,0 +1,541 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013-2020 Minetest core developers & community
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "luaentity_sao.h"
#include "collision.h"
#include "constants.h"
#include "player_sao.h"
#include "scripting_server.h"
#include "server.h"
#include "serverenvironment.h"
LuaEntitySAO::LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &data)
: UnitSAO(env, pos)
{
std::string name;
std::string state;
u16 hp = 1;
v3f velocity;
v3f rotation;
while (!data.empty()) { // breakable, run for one iteration
std::istringstream is(data, std::ios::binary);
// 'version' does not allow to incrementally extend the parameter list thus
// we need another variable to build on top of 'version=1'. Ugly hack but works™
u8 version2 = 0;
u8 version = readU8(is);
name = deSerializeString16(is);
state = deSerializeString32(is);
if (version < 1)
break;
hp = readU16(is);
velocity = readV3F1000(is);
// yaw must be yaw to be backwards-compatible
rotation.Y = readF1000(is);
if (is.good()) // EOF for old formats
version2 = readU8(is);
if (version2 < 1) // PROTOCOL_VERSION < 37
break;
// version2 >= 1
rotation.X = readF1000(is);
rotation.Z = readF1000(is);
// if (version2 < 2)
// break;
// <read new values>
break;
}
// create object
infostream << "LuaEntitySAO::create(name=\"" << name << "\" state=\""
<< state << "\")" << std::endl;
m_init_name = name;
m_init_state = state;
m_hp = hp;
m_velocity = velocity;
m_rotation = rotation;
}
LuaEntitySAO::~LuaEntitySAO()
{
if(m_registered){
m_env->getScriptIface()->luaentity_Remove(m_id);
}
for (u32 attached_particle_spawner : m_attached_particle_spawners) {
m_env->deleteParticleSpawner(attached_particle_spawner, false);
}
}
void LuaEntitySAO::addedToEnvironment(u32 dtime_s)
{
ServerActiveObject::addedToEnvironment(dtime_s);
// Create entity from name
m_registered = m_env->getScriptIface()->
luaentity_Add(m_id, m_init_name.c_str());
if(m_registered){
// Get properties
m_env->getScriptIface()->
luaentity_GetProperties(m_id, this, &m_prop);
// Initialize HP from properties
m_hp = m_prop.hp_max;
// Activate entity, supplying serialized state
m_env->getScriptIface()->
luaentity_Activate(m_id, m_init_state, dtime_s);
} else {
m_prop.infotext = m_init_name;
}
}
void LuaEntitySAO::step(float dtime, bool send_recommended)
{
if(!m_properties_sent)
{
m_properties_sent = true;
std::string str = getPropertyPacket();
// create message and add to list
m_messages_out.emplace(getId(), true, str);
}
// If attached, check that our parent is still there. If it isn't, detach.
if (m_attachment_parent_id && !isAttached()) {
// This is handled when objects are removed from the map
warningstream << "LuaEntitySAO::step() id=" << m_id <<
" is attached to nonexistent parent. This is a bug." << std::endl;
clearParentAttachment();
sendPosition(false, true);
}
m_last_sent_position_timer += dtime;
collisionMoveResult moveresult, *moveresult_p = nullptr;
// Each frame, parent position is copied if the object is attached, otherwise it's calculated normally
// If the object gets detached this comes into effect automatically from the last known origin
if(isAttached())
{
v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
m_base_position = pos;
m_velocity = v3f(0,0,0);
m_acceleration = v3f(0,0,0);
}
else
{
if(m_prop.physical){
aabb3f box = m_prop.collisionbox;
box.MinEdge *= BS;
box.MaxEdge *= BS;
f32 pos_max_d = BS*0.25; // Distance per iteration
v3f p_pos = m_base_position;
v3f p_velocity = m_velocity;
v3f p_acceleration = m_acceleration;
moveresult = collisionMoveSimple(m_env, m_env->getGameDef(),
pos_max_d, box, m_prop.stepheight, dtime,
&p_pos, &p_velocity, p_acceleration,
this, m_prop.collideWithObjects);
moveresult_p = &moveresult;
// Apply results
m_base_position = p_pos;
m_velocity = p_velocity;
m_acceleration = p_acceleration;
} else {
m_base_position += dtime * m_velocity + 0.5 * dtime
* dtime * m_acceleration;
m_velocity += dtime * m_acceleration;
}
if (m_prop.automatic_face_movement_dir &&
(fabs(m_velocity.Z) > 0.001 || fabs(m_velocity.X) > 0.001)) {
float target_yaw = atan2(m_velocity.Z, m_velocity.X) * 180 / M_PI
+ m_prop.automatic_face_movement_dir_offset;
float max_rotation_per_sec =
m_prop.automatic_face_movement_max_rotation_per_sec;
if (max_rotation_per_sec > 0) {
m_rotation.Y = wrapDegrees_0_360(m_rotation.Y);
wrappedApproachShortest(m_rotation.Y, target_yaw,
dtime * max_rotation_per_sec, 360.f);
} else {
// Negative values of max_rotation_per_sec mean disabled.
m_rotation.Y = target_yaw;
}
}
}
if(m_registered) {
m_env->getScriptIface()->luaentity_Step(m_id, dtime, moveresult_p);
}
if (!send_recommended)
return;
if(!isAttached())
{
// TODO: force send when acceleration changes enough?
float minchange = 0.2*BS;
if(m_last_sent_position_timer > 1.0){
minchange = 0.01*BS;
} else if(m_last_sent_position_timer > 0.2){
minchange = 0.05*BS;
}
float move_d = m_base_position.getDistanceFrom(m_last_sent_position);
move_d += m_last_sent_move_precision;
float vel_d = m_velocity.getDistanceFrom(m_last_sent_velocity);
if (move_d > minchange || vel_d > minchange ||
std::fabs(m_rotation.X - m_last_sent_rotation.X) > 1.0f ||
std::fabs(m_rotation.Y - m_last_sent_rotation.Y) > 1.0f ||
std::fabs(m_rotation.Z - m_last_sent_rotation.Z) > 1.0f) {
sendPosition(true, false);
}
}
sendOutdatedData();
}
std::string LuaEntitySAO::getClientInitializationData(u16 protocol_version)
{
std::ostringstream os(std::ios::binary);
// PROTOCOL_VERSION >= 37
writeU8(os, 1); // version
os << serializeString16(""); // name
writeU8(os, 0); // is_player
writeU16(os, getId()); //id
writeV3F32(os, m_base_position);
writeV3F32(os, m_rotation);
writeU16(os, m_hp);
std::ostringstream msg_os(std::ios::binary);
msg_os << serializeString32(getPropertyPacket()); // message 1
msg_os << serializeString32(generateUpdateArmorGroupsCommand()); // 2
msg_os << serializeString32(generateUpdateAnimationCommand()); // 3
for (const auto &bone_pos : m_bone_position) {
msg_os << serializeString32(generateUpdateBonePositionCommand(
bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // 3 + N
}
msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + m_bone_position.size
int message_count = 4 + m_bone_position.size();
for (const auto &id : getAttachmentChildIds()) {
if (ServerActiveObject *obj = m_env->getActiveObject(id)) {
message_count++;
msg_os << serializeString32(obj->generateUpdateInfantCommand(
id, protocol_version));
}
}
msg_os << serializeString32(generateSetTextureModCommand());
message_count++;
writeU8(os, message_count);
std::string serialized = msg_os.str();
os.write(serialized.c_str(), serialized.size());
// return result
return os.str();
}
void LuaEntitySAO::getStaticData(std::string *result) const
{
verbosestream<<FUNCTION_NAME<<std::endl;
std::ostringstream os(std::ios::binary);
// version must be 1 to keep backwards-compatibility. See version2
writeU8(os, 1);
// name
os<<serializeString16(m_init_name);
// state
if(m_registered){
std::string state = m_env->getScriptIface()->
luaentity_GetStaticdata(m_id);
os<<serializeString32(state);
} else {
os<<serializeString32(m_init_state);
}
writeU16(os, m_hp);
writeV3F1000(os, m_velocity);
// yaw
writeF1000(os, m_rotation.Y);
// version2. Increase this variable for new values
writeU8(os, 1); // PROTOCOL_VERSION >= 37
writeF1000(os, m_rotation.X);
writeF1000(os, m_rotation.Z);
// <write new values>
*result = os.str();
}
u16 LuaEntitySAO::punch(v3f dir,
const ToolCapabilities *toolcap,
ServerActiveObject *puncher,
float time_from_last_punch)
{
if (!m_registered) {
// Delete unknown LuaEntities when punched
m_pending_removal = true;
return 0;
}
FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
s32 old_hp = getHP();
ItemStack selected_item, hand_item;
ItemStack tool_item = puncher->getWieldedItem(&selected_item, &hand_item);
PunchDamageResult result = getPunchDamage(
m_armor_groups,
toolcap,
&tool_item,
time_from_last_punch);
bool damage_handled = m_env->getScriptIface()->luaentity_Punch(m_id, puncher,
time_from_last_punch, toolcap, dir, result.did_punch ? result.damage : 0);
if (!damage_handled) {
if (result.did_punch) {
setHP((s32)getHP() - result.damage,
PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
// create message and add to list
sendPunchCommand();
}
}
if (getHP() == 0 && !isGone()) {
clearParentAttachment();
clearChildAttachments();
m_env->getScriptIface()->luaentity_on_death(m_id, puncher);
m_pending_removal = true;
}
actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
", hp=" << puncher->getHP() << ") punched " <<
getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
"), damage=" << (old_hp - (s32)getHP()) <<
(damage_handled ? " (handled by Lua)" : "") << std::endl;
// TODO: give Lua control over wear
return result.wear;
}
void LuaEntitySAO::rightClick(ServerActiveObject *clicker)
{
if (!m_registered)
return;
m_env->getScriptIface()->luaentity_Rightclick(m_id, clicker);
}
void LuaEntitySAO::setPos(const v3f &pos)
{
if(isAttached())
return;
m_base_position = pos;
sendPosition(false, true);
}
void LuaEntitySAO::moveTo(v3f pos, bool continuous)
{
if(isAttached())
return;
m_base_position = pos;
if(!continuous)
sendPosition(true, true);
}
float LuaEntitySAO::getMinimumSavedMovement()
{
return 0.1 * BS;
}
std::string LuaEntitySAO::getDescription()
{
std::ostringstream oss;
oss << "LuaEntitySAO \"" << m_init_name << "\" ";
auto pos = floatToInt(m_base_position, BS);
oss << "at " << PP(pos);
return oss.str();
}
void LuaEntitySAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
{
m_hp = rangelim(hp, 0, U16_MAX);
}
u16 LuaEntitySAO::getHP() const
{
return m_hp;
}
void LuaEntitySAO::setVelocity(v3f velocity)
{
m_velocity = velocity;
}
v3f LuaEntitySAO::getVelocity()
{
return m_velocity;
}
void LuaEntitySAO::setAcceleration(v3f acceleration)
{
m_acceleration = acceleration;
}
v3f LuaEntitySAO::getAcceleration()
{
return m_acceleration;
}
void LuaEntitySAO::setTextureMod(const std::string &mod)
{
m_current_texture_modifier = mod;
// create message and add to list
m_messages_out.emplace(getId(), true, generateSetTextureModCommand());
}
std::string LuaEntitySAO::getTextureMod() const
{
return m_current_texture_modifier;
}
std::string LuaEntitySAO::generateSetTextureModCommand() const
{
std::ostringstream os(std::ios::binary);
// command
writeU8(os, AO_CMD_SET_TEXTURE_MOD);
// parameters
os << serializeString16(m_current_texture_modifier);
return os.str();
}
std::string LuaEntitySAO::generateSetSpriteCommand(v2s16 p, u16 num_frames,
f32 framelength, bool select_horiz_by_yawpitch)
{
std::ostringstream os(std::ios::binary);
// command
writeU8(os, AO_CMD_SET_SPRITE);
// parameters
writeV2S16(os, p);
writeU16(os, num_frames);
writeF32(os, framelength);
writeU8(os, select_horiz_by_yawpitch);
return os.str();
}
void LuaEntitySAO::setSprite(v2s16 p, int num_frames, float framelength,
bool select_horiz_by_yawpitch)
{
std::string str = generateSetSpriteCommand(
p,
num_frames,
framelength,
select_horiz_by_yawpitch
);
// create message and add to list
m_messages_out.emplace(getId(), true, str);
}
std::string LuaEntitySAO::getName()
{
return m_init_name;
}
std::string LuaEntitySAO::getPropertyPacket()
{
return generateSetPropertiesCommand(m_prop);
}
void LuaEntitySAO::sendPosition(bool do_interpolate, bool is_movement_end)
{
// If the object is attached client-side, don't waste bandwidth sending its position to clients
if(isAttached())
return;
m_last_sent_move_precision = m_base_position.getDistanceFrom(
m_last_sent_position);
m_last_sent_position_timer = 0;
m_last_sent_position = m_base_position;
m_last_sent_velocity = m_velocity;
//m_last_sent_acceleration = m_acceleration;
m_last_sent_rotation = m_rotation;
float update_interval = m_env->getSendRecommendedInterval();
std::string str = generateUpdatePositionCommand(
m_base_position,
m_velocity,
m_acceleration,
m_rotation,
do_interpolate,
is_movement_end,
update_interval
);
// create message and add to list
m_messages_out.emplace(getId(), false, str);
}
bool LuaEntitySAO::getCollisionBox(aabb3f *toset) const
{
if (m_prop.physical)
{
//update collision box
toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
toset->MinEdge += m_base_position;
toset->MaxEdge += m_base_position;
return true;
}
return false;
}
bool LuaEntitySAO::getSelectionBox(aabb3f *toset) const
{
if (!m_prop.is_visible || !m_prop.pointable) {
return false;
}
toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
return true;
}
bool LuaEntitySAO::collideWithObjects() const
{
return m_prop.collideWithObjects;
}

View File

@ -0,0 +1,94 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013-2020 Minetest core developers & community
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "unit_sao.h"
class LuaEntitySAO : public UnitSAO
{
public:
LuaEntitySAO() = delete;
// Used by the environment to load SAO
LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &data);
// Used by the Lua API
LuaEntitySAO(ServerEnvironment *env, v3f pos, const std::string &name,
const std::string &state) :
UnitSAO(env, pos),
m_init_name(name), m_init_state(state)
{
}
~LuaEntitySAO();
ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_LUAENTITY; }
ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; }
virtual void addedToEnvironment(u32 dtime_s);
void step(float dtime, bool send_recommended);
std::string getClientInitializationData(u16 protocol_version);
bool isStaticAllowed() const { return m_prop.static_save; }
bool shouldUnload() const { return true; }
void getStaticData(std::string *result) const;
u16 punch(v3f dir, const ToolCapabilities *toolcap = nullptr,
ServerActiveObject *puncher = nullptr,
float time_from_last_punch = 1000000.0f);
void rightClick(ServerActiveObject *clicker);
void setPos(const v3f &pos);
void moveTo(v3f pos, bool continuous);
float getMinimumSavedMovement();
std::string getDescription();
void setHP(s32 hp, const PlayerHPChangeReason &reason);
u16 getHP() const;
/* LuaEntitySAO-specific */
void setVelocity(v3f velocity);
void addVelocity(v3f velocity) { m_velocity += velocity; }
v3f getVelocity();
void setAcceleration(v3f acceleration);
v3f getAcceleration();
void setTextureMod(const std::string &mod);
std::string getTextureMod() const;
void setSprite(v2s16 p, int num_frames, float framelength,
bool select_horiz_by_yawpitch);
std::string getName();
bool getCollisionBox(aabb3f *toset) const;
bool getSelectionBox(aabb3f *toset) const;
bool collideWithObjects() const;
private:
std::string getPropertyPacket();
void sendPosition(bool do_interpolate, bool is_movement_end);
std::string generateSetTextureModCommand() const;
static std::string generateSetSpriteCommand(v2s16 p, u16 num_frames,
f32 framelength, bool select_horiz_by_yawpitch);
std::string m_init_name;
std::string m_init_state;
bool m_registered = false;
v3f m_velocity;
v3f m_acceleration;
v3f m_last_sent_position;
v3f m_last_sent_velocity;
v3f m_last_sent_rotation;
float m_last_sent_position_timer = 0.0f;
float m_last_sent_move_precision = 0.0f;
std::string m_current_texture_modifier = "";
};

108
src/server/mods.cpp Normal file
View File

@ -0,0 +1,108 @@
/*
Minetest
Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "mods.h"
#include "filesys.h"
#include "log.h"
#include "scripting_server.h"
#include "content/subgames.h"
#include "porting.h"
#include "util/metricsbackend.h"
/**
* Manage server mods
*
* All new calls to this class must be tested in test_servermodmanager.cpp
*/
/**
* Creates a ServerModManager which targets worldpath
* @param worldpath
*/
ServerModManager::ServerModManager(const std::string &worldpath) :
ModConfiguration(worldpath)
{
SubgameSpec gamespec = findWorldSubgame(worldpath);
// Add all game mods and all world mods
addModsInPath(gamespec.gamemods_path);
addModsInPath(worldpath + DIR_DELIM + "worldmods");
// Load normal mods
std::string worldmt = worldpath + DIR_DELIM + "world.mt";
addModsFromConfig(worldmt, gamespec.addon_mods_paths);
}
// clang-format off
// This function cannot be currenctly easily tested but it should be ASAP
void ServerModManager::loadMods(ServerScripting *script)
{
// Print mods
infostream << "Server: Loading mods: ";
for (const ModSpec &mod : m_sorted_mods) {
infostream << mod.name << " ";
}
infostream << std::endl;
// Load and run "mod" scripts
for (const ModSpec &mod : m_sorted_mods) {
if (!string_allowed(mod.name, MODNAME_ALLOWED_CHARS)) {
throw ModError("Error loading mod \"" + mod.name +
"\": Mod name does not follow naming "
"conventions: "
"Only characters [a-z0-9_] are allowed.");
}
std::string script_path = mod.path + DIR_DELIM + "init.lua";
auto t = porting::getTimeMs();
script->loadMod(script_path, mod.name);
infostream << "Mod \"" << mod.name << "\" loaded after "
<< (porting::getTimeMs() - t) << " ms" << std::endl;
}
// Run a callback when mods are loaded
script->on_mods_loaded();
}
// clang-format on
const ModSpec *ServerModManager::getModSpec(const std::string &modname) const
{
std::vector<ModSpec>::const_iterator it;
for (it = m_sorted_mods.begin(); it != m_sorted_mods.end(); ++it) {
const ModSpec &mod = *it;
if (mod.name == modname)
return &mod;
}
return NULL;
}
void ServerModManager::getModNames(std::vector<std::string> &modlist) const
{
for (const ModSpec &spec : m_sorted_mods)
modlist.push_back(spec.name);
}
void ServerModManager::getModsMediaPaths(std::vector<std::string> &paths) const
{
for (const ModSpec &spec : m_sorted_mods) {
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "textures");
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "sounds");
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "media");
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "models");
fs::GetRecursiveDirs(paths, spec.path + DIR_DELIM + "locale");
}
}

46
src/server/mods.h Normal file
View File

@ -0,0 +1,46 @@
/*
Minetest
Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "content/mods.h"
#include <memory>
class MetricsBackend;
class MetricCounter;
class ServerScripting;
/**
* Manage server mods
*
* All new calls to this class must be tested in test_servermodmanager.cpp
*/
class ServerModManager : public ModConfiguration
{
public:
/**
* Creates a ServerModManager which targets worldpath
* @param worldpath
*/
ServerModManager(const std::string &worldpath);
void loadMods(ServerScripting *script);
const ModSpec *getModSpec(const std::string &modname) const;
void getModNames(std::vector<std::string> &modlist) const;
void getModsMediaPaths(std::vector<std::string> &paths) const;
};

695
src/server/player_sao.cpp Normal file
View File

@ -0,0 +1,695 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013-2020 Minetest core developers & community
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "player_sao.h"
#include "nodedef.h"
#include "remoteplayer.h"
#include "scripting_server.h"
#include "server.h"
#include "serverenvironment.h"
PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
bool is_singleplayer):
UnitSAO(env_, v3f(0,0,0)),
m_player(player_),
m_peer_id(peer_id_),
m_is_singleplayer(is_singleplayer)
{
SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT);
m_prop.hp_max = PLAYER_MAX_HP_DEFAULT;
m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
m_prop.physical = false;
m_prop.collisionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
m_prop.selectionbox = aabb3f(-0.3f, 0.0f, -0.3f, 0.3f, 1.77f, 0.3f);
m_prop.pointable = true;
// Start of default appearance, this should be overwritten by Lua
m_prop.visual = "upright_sprite";
m_prop.visual_size = v3f(1, 2, 1);
m_prop.textures.clear();
m_prop.textures.emplace_back("player.png");
m_prop.textures.emplace_back("player_back.png");
m_prop.colors.clear();
m_prop.colors.emplace_back(255, 255, 255, 255);
m_prop.spritediv = v2s16(1,1);
m_prop.eye_height = 1.625f;
// End of default appearance
m_prop.is_visible = true;
m_prop.backface_culling = false;
m_prop.makes_footstep_sound = true;
m_prop.stepheight = PLAYER_DEFAULT_STEPHEIGHT * BS;
m_prop.show_on_minimap = true;
m_hp = m_prop.hp_max;
m_breath = m_prop.breath_max;
// Disable zoom in survival mode using a value of 0
m_prop.zoom_fov = g_settings->getBool("creative_mode") ? 15.0f : 0.0f;
if (!g_settings->getBool("enable_damage"))
m_armor_groups["immortal"] = 1;
}
void PlayerSAO::finalize(RemotePlayer *player, const std::set<std::string> &privs)
{
assert(player);
m_player = player;
m_privs = privs;
}
v3f PlayerSAO::getEyeOffset() const
{
return v3f(0, BS * m_prop.eye_height, 0);
}
std::string PlayerSAO::getDescription()
{
return std::string("player ") + m_player->getName();
}
// Called after id has been set and has been inserted in environment
void PlayerSAO::addedToEnvironment(u32 dtime_s)
{
ServerActiveObject::addedToEnvironment(dtime_s);
ServerActiveObject::setBasePosition(m_base_position);
m_player->setPlayerSAO(this);
m_player->setPeerId(m_peer_id);
m_last_good_position = m_base_position;
}
// Called before removing from environment
void PlayerSAO::removingFromEnvironment()
{
ServerActiveObject::removingFromEnvironment();
if (m_player->getPlayerSAO() == this) {
unlinkPlayerSessionAndSave();
for (u32 attached_particle_spawner : m_attached_particle_spawners) {
m_env->deleteParticleSpawner(attached_particle_spawner, false);
}
}
}
std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
{
std::ostringstream os(std::ios::binary);
// Protocol >= 15
writeU8(os, 1); // version
os << serializeString16(m_player->getName()); // name
writeU8(os, 1); // is_player
writeS16(os, getId()); // id
writeV3F32(os, m_base_position);
writeV3F32(os, m_rotation);
writeU16(os, getHP());
std::ostringstream msg_os(std::ios::binary);
msg_os << serializeString32(getPropertyPacket()); // message 1
msg_os << serializeString32(generateUpdateArmorGroupsCommand()); // 2
msg_os << serializeString32(generateUpdateAnimationCommand()); // 3
for (const auto &bone_pos : m_bone_position) {
msg_os << serializeString32(generateUpdateBonePositionCommand(
bone_pos.first, bone_pos.second.X, bone_pos.second.Y)); // 3 + N
}
msg_os << serializeString32(generateUpdateAttachmentCommand()); // 4 + m_bone_position.size
msg_os << serializeString32(generateUpdatePhysicsOverrideCommand()); // 5 + m_bone_position.size
int message_count = 5 + m_bone_position.size();
for (const auto &id : getAttachmentChildIds()) {
if (ServerActiveObject *obj = m_env->getActiveObject(id)) {
message_count++;
msg_os << serializeString32(obj->generateUpdateInfantCommand(
id, protocol_version));
}
}
writeU8(os, message_count);
std::string serialized = msg_os.str();
os.write(serialized.c_str(), serialized.size());
// return result
return os.str();
}
void PlayerSAO::getStaticData(std::string * result) const
{
FATAL_ERROR("Obsolete function");
}
void PlayerSAO::step(float dtime, bool send_recommended)
{
if (!isImmortal() && m_drowning_interval.step(dtime, 2.0f)) {
// Get nose/mouth position, approximate with eye position
v3s16 p = floatToInt(getEyePosition(), BS);
MapNode n = m_env->getMap().getNode(p);
const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
// If node generates drown
if (c.drowning > 0 && m_hp > 0) {
if (m_breath > 0)
setBreath(m_breath - 1);
// No more breath, damage player
if (m_breath == 0) {
PlayerHPChangeReason reason(PlayerHPChangeReason::DROWNING);
setHP(m_hp - c.drowning, reason);
m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
}
}
}
if (m_breathing_interval.step(dtime, 0.5f) && !isImmortal()) {
// Get nose/mouth position, approximate with eye position
v3s16 p = floatToInt(getEyePosition(), BS);
MapNode n = m_env->getMap().getNode(p);
const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
// If player is alive & not drowning & not in ignore & not immortal, breathe
if (m_breath < m_prop.breath_max && c.drowning == 0 &&
n.getContent() != CONTENT_IGNORE && m_hp > 0)
setBreath(m_breath + 1);
}
if (!isImmortal() && m_node_hurt_interval.step(dtime, 1.0f)) {
u32 damage_per_second = 0;
std::string nodename;
// Lowest and highest damage points are 0.1 within collisionbox
float dam_top = m_prop.collisionbox.MaxEdge.Y - 0.1f;
// Sequence of damage points, starting 0.1 above feet and progressing
// upwards in 1 node intervals, stopping below top damage point.
for (float dam_height = 0.1f; dam_height < dam_top; dam_height++) {
v3s16 p = floatToInt(m_base_position +
v3f(0.0f, dam_height * BS, 0.0f), BS);
MapNode n = m_env->getMap().getNode(p);
const ContentFeatures &c = m_env->getGameDef()->ndef()->get(n);
if (c.damage_per_second > damage_per_second) {
damage_per_second = c.damage_per_second;
nodename = c.name;
}
}
// Top damage point
v3s16 ptop = floatToInt(m_base_position +
v3f(0.0f, dam_top * BS, 0.0f), BS);
MapNode ntop = m_env->getMap().getNode(ptop);
const ContentFeatures &c = m_env->getGameDef()->ndef()->get(ntop);
if (c.damage_per_second > damage_per_second) {
damage_per_second = c.damage_per_second;
nodename = c.name;
}
if (damage_per_second != 0 && m_hp > 0) {
s32 newhp = (s32)m_hp - (s32)damage_per_second;
PlayerHPChangeReason reason(PlayerHPChangeReason::NODE_DAMAGE, nodename);
setHP(newhp, reason);
m_env->getGameDef()->SendPlayerHPOrDie(this, reason);
}
}
if (!m_properties_sent) {
m_properties_sent = true;
std::string str = getPropertyPacket();
// create message and add to list
m_messages_out.emplace(getId(), true, str);
m_env->getScriptIface()->player_event(this, "properties_changed");
}
// If attached, check that our parent is still there. If it isn't, detach.
if (m_attachment_parent_id && !isAttached()) {
// This is handled when objects are removed from the map
warningstream << "PlayerSAO::step() id=" << m_id <<
" is attached to nonexistent parent. This is a bug." << std::endl;
clearParentAttachment();
setBasePosition(m_last_good_position);
m_env->getGameDef()->SendMovePlayer(m_peer_id);
}
//dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
// Set lag pool maximums based on estimated lag
const float LAG_POOL_MIN = 5.0f;
float lag_pool_max = m_env->getMaxLagEstimate() * 2.0f;
if(lag_pool_max < LAG_POOL_MIN)
lag_pool_max = LAG_POOL_MIN;
m_dig_pool.setMax(lag_pool_max);
m_move_pool.setMax(lag_pool_max);
// Increment cheat prevention timers
m_dig_pool.add(dtime);
m_move_pool.add(dtime);
m_time_from_last_teleport += dtime;
m_time_from_last_punch += dtime;
m_nocheat_dig_time += dtime;
m_max_speed_override_time = MYMAX(m_max_speed_override_time - dtime, 0.0f);
// Each frame, parent position is copied if the object is attached,
// otherwise it's calculated normally.
// If the object gets detached this comes into effect automatically from
// the last known origin.
if (isAttached()) {
v3f pos = m_env->getActiveObject(m_attachment_parent_id)->getBasePosition();
m_last_good_position = pos;
setBasePosition(pos);
}
if (!send_recommended)
return;
if (m_position_not_sent) {
m_position_not_sent = false;
float update_interval = m_env->getSendRecommendedInterval();
v3f pos;
// When attached, the position is only sent to clients where the
// parent isn't known
if (isAttached())
pos = m_last_good_position;
else
pos = m_base_position;
std::string str = generateUpdatePositionCommand(
pos,
v3f(0.0f, 0.0f, 0.0f),
v3f(0.0f, 0.0f, 0.0f),
m_rotation,
true,
false,
update_interval
);
// create message and add to list
m_messages_out.emplace(getId(), false, str);
}
if (!m_physics_override_sent) {
m_physics_override_sent = true;
// create message and add to list
m_messages_out.emplace(getId(), true, generateUpdatePhysicsOverrideCommand());
}
sendOutdatedData();
}
std::string PlayerSAO::generateUpdatePhysicsOverrideCommand() const
{
std::ostringstream os(std::ios::binary);
// command
writeU8(os, AO_CMD_SET_PHYSICS_OVERRIDE);
// parameters
writeF32(os, m_physics_override_speed);
writeF32(os, m_physics_override_jump);
writeF32(os, m_physics_override_gravity);
// these are sent inverted so we get true when the server sends nothing
writeU8(os, !m_physics_override_sneak);
writeU8(os, !m_physics_override_sneak_glitch);
writeU8(os, !m_physics_override_new_move);
return os.str();
}
void PlayerSAO::setBasePosition(const v3f &position)
{
if (m_player && position != m_base_position)
m_player->setDirty(true);
// This needs to be ran for attachments too
ServerActiveObject::setBasePosition(position);
// Updating is not wanted/required for player migration
if (m_env) {
m_position_not_sent = true;
}
}
void PlayerSAO::setPos(const v3f &pos)
{
if(isAttached())
return;
// Send mapblock of target location
v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE);
m_env->getGameDef()->SendBlock(m_peer_id, blockpos);
setBasePosition(pos);
// Movement caused by this command is always valid
m_last_good_position = pos;
m_move_pool.empty();
m_time_from_last_teleport = 0.0;
m_env->getGameDef()->SendMovePlayer(m_peer_id);
}
void PlayerSAO::moveTo(v3f pos, bool continuous)
{
if(isAttached())
return;
setBasePosition(pos);
// Movement caused by this command is always valid
m_last_good_position = pos;
m_move_pool.empty();
m_time_from_last_teleport = 0.0;
m_env->getGameDef()->SendMovePlayer(m_peer_id);
}
void PlayerSAO::setPlayerYaw(const float yaw)
{
v3f rotation(0, yaw, 0);
if (m_player && yaw != m_rotation.Y)
m_player->setDirty(true);
// Set player model yaw, not look view
UnitSAO::setRotation(rotation);
}
void PlayerSAO::setFov(const float fov)
{
if (m_player && fov != m_fov)
m_player->setDirty(true);
m_fov = fov;
}
void PlayerSAO::setWantedRange(const s16 range)
{
if (m_player && range != m_wanted_range)
m_player->setDirty(true);
m_wanted_range = range;
}
void PlayerSAO::setPlayerYawAndSend(const float yaw)
{
setPlayerYaw(yaw);
m_env->getGameDef()->SendMovePlayer(m_peer_id);
}
void PlayerSAO::setLookPitch(const float pitch)
{
if (m_player && pitch != m_pitch)
m_player->setDirty(true);
m_pitch = pitch;
}
void PlayerSAO::setLookPitchAndSend(const float pitch)
{
setLookPitch(pitch);
m_env->getGameDef()->SendMovePlayer(m_peer_id);
}
u16 PlayerSAO::punch(v3f dir,
const ToolCapabilities *toolcap,
ServerActiveObject *puncher,
float time_from_last_punch)
{
if (!toolcap)
return 0;
FATAL_ERROR_IF(!puncher, "Punch action called without SAO");
// No effect if PvP disabled or if immortal
if (isImmortal() || !g_settings->getBool("enable_pvp")) {
if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
// create message and add to list
sendPunchCommand();
return 0;
}
}
s32 old_hp = getHP();
HitParams hitparams = getHitParams(m_armor_groups, toolcap,
time_from_last_punch);
PlayerSAO *playersao = m_player->getPlayerSAO();
bool damage_handled = m_env->getScriptIface()->on_punchplayer(playersao,
puncher, time_from_last_punch, toolcap, dir,
hitparams.hp);
if (!damage_handled) {
setHP((s32)getHP() - (s32)hitparams.hp,
PlayerHPChangeReason(PlayerHPChangeReason::PLAYER_PUNCH, puncher));
} else { // override client prediction
if (puncher->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
// create message and add to list
sendPunchCommand();
}
}
actionstream << puncher->getDescription() << " (id=" << puncher->getId() <<
", hp=" << puncher->getHP() << ") punched " <<
getDescription() << " (id=" << m_id << ", hp=" << m_hp <<
"), damage=" << (old_hp - (s32)getHP()) <<
(damage_handled ? " (handled by Lua)" : "") << std::endl;
return hitparams.wear;
}
void PlayerSAO::setHP(s32 hp, const PlayerHPChangeReason &reason)
{
if (hp == (s32)m_hp)
return; // Nothing to do
if (m_hp <= 0 && hp < (s32)m_hp)
return; // Cannot take more damage
{
s32 hp_change = m_env->getScriptIface()->on_player_hpchange(this, hp - m_hp, reason);
if (hp_change == 0)
return;
hp = m_hp + hp_change;
}
s32 oldhp = m_hp;
hp = rangelim(hp, 0, m_prop.hp_max);
if (hp < oldhp && isImmortal())
return; // Do not allow immortal players to be damaged
m_hp = hp;
// Update properties on death
if ((hp == 0) != (oldhp == 0))
m_properties_sent = false;
}
void PlayerSAO::setBreath(const u16 breath, bool send)
{
if (m_player && breath != m_breath)
m_player->setDirty(true);
m_breath = rangelim(breath, 0, m_prop.breath_max);
if (send)
m_env->getGameDef()->SendPlayerBreath(this);
}
Inventory *PlayerSAO::getInventory() const
{
return m_player ? &m_player->inventory : nullptr;
}
InventoryLocation PlayerSAO::getInventoryLocation() const
{
InventoryLocation loc;
loc.setPlayer(m_player->getName());
return loc;
}
u16 PlayerSAO::getWieldIndex() const
{
return m_player->getWieldIndex();
}
ItemStack PlayerSAO::getWieldedItem(ItemStack *selected, ItemStack *hand) const
{
return m_player->getWieldedItem(selected, hand);
}
bool PlayerSAO::setWieldedItem(const ItemStack &item)
{
InventoryList *mlist = m_player->inventory.getList(getWieldList());
if (mlist) {
mlist->changeItem(m_player->getWieldIndex(), item);
return true;
}
return false;
}
void PlayerSAO::disconnected()
{
m_peer_id = PEER_ID_INEXISTENT;
m_pending_removal = true;
}
void PlayerSAO::unlinkPlayerSessionAndSave()
{
assert(m_player->getPlayerSAO() == this);
m_player->setPeerId(PEER_ID_INEXISTENT);
m_env->savePlayer(m_player);
m_player->setPlayerSAO(NULL);
m_env->removePlayer(m_player);
}
std::string PlayerSAO::getPropertyPacket()
{
m_prop.is_visible = (true);
return generateSetPropertiesCommand(m_prop);
}
void PlayerSAO::setMaxSpeedOverride(const v3f &vel)
{
if (m_max_speed_override_time == 0.0f)
m_max_speed_override = vel;
else
m_max_speed_override += vel;
if (m_player) {
float accel = MYMIN(m_player->movement_acceleration_default,
m_player->movement_acceleration_air);
m_max_speed_override_time = m_max_speed_override.getLength() / accel / BS;
}
}
bool PlayerSAO::checkMovementCheat()
{
if (m_is_singleplayer ||
g_settings->getBool("disable_anticheat")) {
m_last_good_position = m_base_position;
return false;
}
if (UnitSAO *parent = dynamic_cast<UnitSAO *>(getParent())) {
v3f attachment_pos;
{
int parent_id;
std::string bone;
v3f attachment_rot;
bool force_visible;
getAttachment(&parent_id, &bone, &attachment_pos, &attachment_rot, &force_visible);
}
v3f parent_pos = parent->getBasePosition();
f32 diff = m_base_position.getDistanceFromSQ(parent_pos) - attachment_pos.getLengthSQ();
const f32 maxdiff = 4.0f * BS; // fair trade-off value for various latencies
if (diff > maxdiff * maxdiff) {
setBasePosition(parent_pos);
actionstream << "Server: " << m_player->getName()
<< " moved away from parent; diff=" << sqrtf(diff) / BS
<< " resetting position." << std::endl;
return true;
}
// Player movement is locked to the entity. Skip further checks
return false;
}
bool cheated = false;
/*
Check player movements
NOTE: Actually the server should handle player physics like the
client does and compare player's position to what is calculated
on our side. This is required when eg. players fly due to an
explosion. Altough a node-based alternative might be possible
too, and much more lightweight.
*/
float override_max_H, override_max_V;
if (m_max_speed_override_time > 0.0f) {
override_max_H = MYMAX(fabs(m_max_speed_override.X), fabs(m_max_speed_override.Z));
override_max_V = fabs(m_max_speed_override.Y);
} else {
override_max_H = override_max_V = 0.0f;
}
float player_max_walk = 0; // horizontal movement
float player_max_jump = 0; // vertical upwards movement
if (m_privs.count("fast") != 0)
player_max_walk = m_player->movement_speed_fast; // Fast speed
else
player_max_walk = m_player->movement_speed_walk; // Normal speed
player_max_walk *= m_physics_override_speed;
player_max_walk = MYMAX(player_max_walk, override_max_H);
player_max_jump = m_player->movement_speed_jump * m_physics_override_jump;
// FIXME: Bouncy nodes cause practically unbound increase in Y speed,
// until this can be verified correctly, tolerate higher jumping speeds
player_max_jump *= 2.0;
player_max_jump = MYMAX(player_max_jump, override_max_V);
// Don't divide by zero!
if (player_max_walk < 0.0001f)
player_max_walk = 0.0001f;
if (player_max_jump < 0.0001f)
player_max_jump = 0.0001f;
v3f diff = (m_base_position - m_last_good_position);
float d_vert = diff.Y;
diff.Y = 0;
float d_horiz = diff.getLength();
float required_time = d_horiz / player_max_walk;
// FIXME: Checking downwards movement is not easily possible currently,
// the server could calculate speed differences to examine the gravity
if (d_vert > 0) {
// In certain cases (water, ladders) walking speed is applied vertically
float s = MYMAX(player_max_jump, player_max_walk);
required_time = MYMAX(required_time, d_vert / s);
}
if (m_move_pool.grab(required_time)) {
m_last_good_position = m_base_position;
} else {
const float LAG_POOL_MIN = 5.0;
float lag_pool_max = m_env->getMaxLagEstimate() * 2.0;
lag_pool_max = MYMAX(lag_pool_max, LAG_POOL_MIN);
if (m_time_from_last_teleport > lag_pool_max) {
actionstream << "Server: " << m_player->getName()
<< " moved too fast: V=" << d_vert << ", H=" << d_horiz
<< "; resetting position." << std::endl;
cheated = true;
}
setBasePosition(m_last_good_position);
}
return cheated;
}
bool PlayerSAO::getCollisionBox(aabb3f *toset) const
{
//update collision box
toset->MinEdge = m_prop.collisionbox.MinEdge * BS;
toset->MaxEdge = m_prop.collisionbox.MaxEdge * BS;
toset->MinEdge += m_base_position;
toset->MaxEdge += m_base_position;
return true;
}
bool PlayerSAO::getSelectionBox(aabb3f *toset) const
{
if (!m_prop.is_visible || !m_prop.pointable) {
return false;
}
toset->MinEdge = m_prop.selectionbox.MinEdge * BS;
toset->MaxEdge = m_prop.selectionbox.MaxEdge * BS;
return true;
}
float PlayerSAO::getZoomFOV() const
{
return m_prop.zoom_fov;
}

301
src/server/player_sao.h Normal file
View File

@ -0,0 +1,301 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013-2020 Minetest core developers & community
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "constants.h"
#include "network/networkprotocol.h"
#include "unit_sao.h"
#include "util/numeric.h"
/*
PlayerSAO needs some internals exposed.
*/
class LagPool
{
float m_pool = 15.0f;
float m_max = 15.0f;
public:
LagPool() = default;
void setMax(float new_max)
{
m_max = new_max;
if (m_pool > new_max)
m_pool = new_max;
}
void add(float dtime)
{
m_pool -= dtime;
if (m_pool < 0)
m_pool = 0;
}
void empty() { m_pool = m_max; }
bool grab(float dtime)
{
if (dtime <= 0)
return true;
if (m_pool + dtime > m_max)
return false;
m_pool += dtime;
return true;
}
};
class RemotePlayer;
class PlayerSAO : public UnitSAO
{
public:
PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t peer_id_,
bool is_singleplayer);
ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_PLAYER; }
ActiveObjectType getSendType() const { return ACTIVEOBJECT_TYPE_GENERIC; }
std::string getDescription();
/*
Active object <-> environment interface
*/
void addedToEnvironment(u32 dtime_s);
void removingFromEnvironment();
bool isStaticAllowed() const { return false; }
bool shouldUnload() const { return false; }
std::string getClientInitializationData(u16 protocol_version);
void getStaticData(std::string *result) const;
void step(float dtime, bool send_recommended);
void setBasePosition(const v3f &position);
void setPos(const v3f &pos);
void moveTo(v3f pos, bool continuous);
void setPlayerYaw(const float yaw);
// Data should not be sent at player initialization
void setPlayerYawAndSend(const float yaw);
void setLookPitch(const float pitch);
// Data should not be sent at player initialization
void setLookPitchAndSend(const float pitch);
f32 getLookPitch() const { return m_pitch; }
f32 getRadLookPitch() const { return m_pitch * core::DEGTORAD; }
// Deprecated
f32 getRadLookPitchDep() const { return -1.0 * m_pitch * core::DEGTORAD; }
void setFov(const float pitch);
f32 getFov() const { return m_fov; }
void setWantedRange(const s16 range);
s16 getWantedRange() const { return m_wanted_range; }
/*
Interaction interface
*/
u16 punch(v3f dir, const ToolCapabilities *toolcap, ServerActiveObject *puncher,
float time_from_last_punch);
void rightClick(ServerActiveObject *clicker) {}
void setHP(s32 hp, const PlayerHPChangeReason &reason);
void setHPRaw(u16 hp) { m_hp = hp; }
s16 readDamage();
u16 getBreath() const { return m_breath; }
void setBreath(const u16 breath, bool send = true);
/*
Inventory interface
*/
Inventory *getInventory() const;
InventoryLocation getInventoryLocation() const;
void setInventoryModified() {}
std::string getWieldList() const { return "main"; }
u16 getWieldIndex() const;
ItemStack getWieldedItem(ItemStack *selected, ItemStack *hand = nullptr) const;
bool setWieldedItem(const ItemStack &item);
/*
PlayerSAO-specific
*/
void disconnected();
RemotePlayer *getPlayer() { return m_player; }
session_t getPeerID() const { return m_peer_id; }
// Cheat prevention
v3f getLastGoodPosition() const { return m_last_good_position; }
float resetTimeFromLastPunch()
{
float r = m_time_from_last_punch;
m_time_from_last_punch = 0.0;
return r;
}
void noCheatDigStart(const v3s16 &p)
{
m_nocheat_dig_pos = p;
m_nocheat_dig_time = 0;
}
v3s16 getNoCheatDigPos() { return m_nocheat_dig_pos; }
float getNoCheatDigTime() { return m_nocheat_dig_time; }
void noCheatDigEnd() { m_nocheat_dig_pos = v3s16(32767, 32767, 32767); }
LagPool &getDigPool() { return m_dig_pool; }
void setMaxSpeedOverride(const v3f &vel);
// Returns true if cheated
bool checkMovementCheat();
// Other
void updatePrivileges(const std::set<std::string> &privs, bool is_singleplayer)
{
m_privs = privs;
m_is_singleplayer = is_singleplayer;
}
bool getCollisionBox(aabb3f *toset) const;
bool getSelectionBox(aabb3f *toset) const;
bool collideWithObjects() const { return true; }
void finalize(RemotePlayer *player, const std::set<std::string> &privs);
v3f getEyePosition() const { return m_base_position + getEyeOffset(); }
v3f getEyeOffset() const;
float getZoomFOV() const;
inline Metadata &getMeta() { return m_meta; }
private:
std::string getPropertyPacket();
void unlinkPlayerSessionAndSave();
std::string generateUpdatePhysicsOverrideCommand() const;
RemotePlayer *m_player = nullptr;
session_t m_peer_id = 0;
// Cheat prevention
LagPool m_dig_pool;
LagPool m_move_pool;
v3f m_last_good_position;
float m_time_from_last_teleport = 0.0f;
float m_time_from_last_punch = 0.0f;
v3s16 m_nocheat_dig_pos = v3s16(32767, 32767, 32767);
float m_nocheat_dig_time = 0.0f;
float m_max_speed_override_time = 0.0f;
v3f m_max_speed_override = v3f(0.0f, 0.0f, 0.0f);
// Timers
IntervalLimiter m_breathing_interval;
IntervalLimiter m_drowning_interval;
IntervalLimiter m_node_hurt_interval;
bool m_position_not_sent = false;
// Cached privileges for enforcement
std::set<std::string> m_privs;
bool m_is_singleplayer;
u16 m_breath = PLAYER_MAX_BREATH_DEFAULT;
f32 m_pitch = 0.0f;
f32 m_fov = 0.0f;
s16 m_wanted_range = 0.0f;
Metadata m_meta;
public:
float m_physics_override_speed = 1.0f;
float m_physics_override_jump = 1.0f;
float m_physics_override_gravity = 1.0f;
bool m_physics_override_sneak = true;
bool m_physics_override_sneak_glitch = false;
bool m_physics_override_new_move = true;
bool m_physics_override_sent = false;
};
struct PlayerHPChangeReason
{
enum Type : u8
{
SET_HP,
PLAYER_PUNCH,
FALL,
NODE_DAMAGE,
DROWNING,
RESPAWN
};
Type type = SET_HP;
bool from_mod = false;
int lua_reference = -1;
// For PLAYER_PUNCH
ServerActiveObject *object = nullptr;
// For NODE_DAMAGE
std::string node;
inline bool hasLuaReference() const { return lua_reference >= 0; }
bool setTypeFromString(const std::string &typestr)
{
if (typestr == "set_hp")
type = SET_HP;
else if (typestr == "punch")
type = PLAYER_PUNCH;
else if (typestr == "fall")
type = FALL;
else if (typestr == "node_damage")
type = NODE_DAMAGE;
else if (typestr == "drown")
type = DROWNING;
else if (typestr == "respawn")
type = RESPAWN;
else
return false;
return true;
}
std::string getTypeAsString() const
{
switch (type) {
case PlayerHPChangeReason::SET_HP:
return "set_hp";
case PlayerHPChangeReason::PLAYER_PUNCH:
return "punch";
case PlayerHPChangeReason::FALL:
return "fall";
case PlayerHPChangeReason::NODE_DAMAGE:
return "node_damage";
case PlayerHPChangeReason::DROWNING:
return "drown";
case PlayerHPChangeReason::RESPAWN:
return "respawn";
default:
return "?";
}
}
PlayerHPChangeReason(Type type) : type(type) {}
PlayerHPChangeReason(Type type, ServerActiveObject *object) :
type(type), object(object)
{
}
PlayerHPChangeReason(Type type, std::string node) : type(type), node(node) {}
};

View File

@ -0,0 +1,75 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "serveractiveobject.h"
#include <fstream>
#include "inventory.h"
#include "constants.h" // BS
#include "log.h"
ServerActiveObject::ServerActiveObject(ServerEnvironment *env, v3f pos):
ActiveObject(0),
m_env(env),
m_base_position(pos)
{
}
float ServerActiveObject::getMinimumSavedMovement()
{
return 2.0*BS;
}
ItemStack ServerActiveObject::getWieldedItem(ItemStack *selected, ItemStack *hand) const
{
*selected = ItemStack();
if (hand)
*hand = ItemStack();
return ItemStack();
}
bool ServerActiveObject::setWieldedItem(const ItemStack &item)
{
return false;
}
std::string ServerActiveObject::generateUpdateInfantCommand(u16 infant_id, u16 protocol_version)
{
std::ostringstream os(std::ios::binary);
// command
writeU8(os, AO_CMD_SPAWN_INFANT);
// parameters
writeU16(os, infant_id);
writeU8(os, getSendType());
if (protocol_version < 38) {
// Clients since 4aa9a66 so no longer need this data
// Version 38 is the first bump after that commit.
// See also: ClientEnvironment::addActiveObject
os << serializeString32(getClientInitializationData(protocol_version));
}
return os.str();
}
void ServerActiveObject::dumpAOMessagesToQueue(std::queue<ActiveObjectMessage> &queue)
{
while (!m_messages_out.empty()) {
queue.push(std::move(m_messages_out.front()));
m_messages_out.pop();
}
}

View File

@ -0,0 +1,264 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <unordered_set>
#include "irrlichttypes_bloated.h"
#include "activeobject.h"
#include "inventorymanager.h"
#include "itemgroup.h"
#include "util/container.h"
/*
Some planning
-------------
* Server environment adds an active object, which gets the id 1
* The active object list is scanned for each client once in a while,
and it finds out what objects have been added that are not known
by the client yet. This scan is initiated by the Server class and
the result ends up directly to the server.
* A network packet is created with the info and sent to the client.
* Environment converts objects to static data and static data to
objects, based on how close players are to them.
*/
class ServerEnvironment;
struct ItemStack;
struct ToolCapabilities;
struct ObjectProperties;
struct PlayerHPChangeReason;
class ServerActiveObject : public ActiveObject
{
public:
/*
NOTE: m_env can be NULL, but step() isn't called if it is.
Prototypes are used that way.
*/
ServerActiveObject(ServerEnvironment *env, v3f pos);
virtual ~ServerActiveObject() = default;
virtual ActiveObjectType getSendType() const
{ return getType(); }
// Called after id has been set and has been inserted in environment
virtual void addedToEnvironment(u32 dtime_s){};
// Called before removing from environment
virtual void removingFromEnvironment(){};
// Returns true if object's deletion is the job of the
// environment
virtual bool environmentDeletes() const
{ return true; }
// Create a certain type of ServerActiveObject
static ServerActiveObject* create(ActiveObjectType type,
ServerEnvironment *env, u16 id, v3f pos,
const std::string &data);
/*
Some simple getters/setters
*/
v3f getBasePosition() const { return m_base_position; }
void setBasePosition(v3f pos){ m_base_position = pos; }
ServerEnvironment* getEnv(){ return m_env; }
/*
Some more dynamic interface
*/
virtual void setPos(const v3f &pos)
{ setBasePosition(pos); }
// continuous: if true, object does not stop immediately at pos
virtual void moveTo(v3f pos, bool continuous)
{ setBasePosition(pos); }
// If object has moved less than this and data has not changed,
// saving to disk may be omitted
virtual float getMinimumSavedMovement();
virtual std::string getDescription(){return "SAO";}
/*
Step object in time.
Messages added to messages are sent to client over network.
send_recommended:
True at around 5-10 times a second, same for all objects.
This is used to let objects send most of the data at the
same time so that the data can be combined in a single
packet.
*/
virtual void step(float dtime, bool send_recommended){}
/*
The return value of this is passed to the client-side object
when it is created
*/
virtual std::string getClientInitializationData(u16 protocol_version) {return "";}
/*
The return value of this is passed to the server-side object
when it is created (converted from static to active - actually
the data is the static form)
*/
virtual void getStaticData(std::string *result) const
{
assert(isStaticAllowed());
*result = "";
}
/*
Return false in here to never save and instead remove object
on unload. getStaticData() will not be called in that case.
*/
virtual bool isStaticAllowed() const
{return true;}
/*
Return false here to never unload the object.
isStaticAllowed && shouldUnload -> unload when out of active block range
!isStaticAllowed && shouldUnload -> unload when block is unloaded
*/
virtual bool shouldUnload() const
{ return true; }
// Returns tool wear
virtual u16 punch(v3f dir,
const ToolCapabilities *toolcap = nullptr,
ServerActiveObject *puncher = nullptr,
float time_from_last_punch = 1000000.0f)
{ return 0; }
virtual void rightClick(ServerActiveObject *clicker)
{}
virtual void setHP(s32 hp, const PlayerHPChangeReason &reason)
{}
virtual u16 getHP() const
{ return 0; }
virtual void setArmorGroups(const ItemGroupList &armor_groups)
{}
virtual const ItemGroupList &getArmorGroups() const
{ static ItemGroupList rv; return rv; }
virtual void setPhysicsOverride(float physics_override_speed, float physics_override_jump, float physics_override_gravity)
{}
virtual void setAnimation(v2f frames, float frame_speed, float frame_blend, bool frame_loop)
{}
virtual void getAnimation(v2f *frames, float *frame_speed, float *frame_blend, bool *frame_loop)
{}
virtual void setAnimationSpeed(float frame_speed)
{}
virtual void setBonePosition(const std::string &bone, v3f position, v3f rotation)
{}
virtual void getBonePosition(const std::string &bone, v3f *position, v3f *lotation)
{}
virtual const std::unordered_set<int> &getAttachmentChildIds() const
{ static std::unordered_set<int> rv; return rv; }
virtual ServerActiveObject *getParent() const { return nullptr; }
virtual ObjectProperties* accessObjectProperties()
{ return NULL; }
virtual void notifyObjectPropertiesModified()
{}
// Inventory and wielded item
virtual Inventory *getInventory() const
{ return NULL; }
virtual InventoryLocation getInventoryLocation() const
{ return InventoryLocation(); }
virtual void setInventoryModified()
{}
virtual std::string getWieldList() const
{ return ""; }
virtual u16 getWieldIndex() const
{ return 0; }
virtual ItemStack getWieldedItem(ItemStack *selected,
ItemStack *hand = nullptr) const;
virtual bool setWieldedItem(const ItemStack &item);
inline void attachParticleSpawner(u32 id)
{
m_attached_particle_spawners.insert(id);
}
inline void detachParticleSpawner(u32 id)
{
m_attached_particle_spawners.erase(id);
}
std::string generateUpdateInfantCommand(u16 infant_id, u16 protocol_version);
std::string generateUpdateNametagAttributesCommand(const video::SColor &color) const;
void dumpAOMessagesToQueue(std::queue<ActiveObjectMessage> &queue);
/*
Number of players which know about this object. Object won't be
deleted until this is 0 to keep the id preserved for the right
object.
*/
u16 m_known_by_count = 0;
/*
- Whether this object is to be removed when nobody knows about
it anymore.
- Removal is delayed to preserve the id for the time during which
it could be confused to some other object by some client.
- This is usually set to true by the step() method when the object wants
to be deleted but can be set by anything else too.
*/
bool m_pending_removal = false;
/*
Same purpose as m_pending_removal but for deactivation.
deactvation = save static data in block, remove active object
If this is set alongside with m_pending_removal, removal takes
priority.
*/
bool m_pending_deactivation = false;
/*
A getter that unifies the above to answer the question:
"Can the environment still interact with this object?"
*/
inline bool isGone() const
{ return m_pending_removal || m_pending_deactivation; }
/*
Whether the object's static data has been stored to a block
*/
bool m_static_exists = false;
/*
The block from which the object was loaded from, and in which
a copy of the static data resides.
*/
v3s16 m_static_block = v3s16(1337,1337,1337);
protected:
virtual void onAttach(int parent_id) {}
virtual void onDetach(int parent_id) {}
ServerEnvironment *m_env;
v3f m_base_position;
std::unordered_set<u32> m_attached_particle_spawners;
/*
Queue of messages to be sent to the client
*/
std::queue<ActiveObjectMessage> m_messages_out;
};

View File

@ -0,0 +1,192 @@
/*
Minetest
Copyright (C) 2010-2020 Minetest core development team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "serverinventorymgr.h"
#include "map.h"
#include "nodemetadata.h"
#include "player_sao.h"
#include "remoteplayer.h"
#include "server.h"
#include "serverenvironment.h"
ServerInventoryManager::ServerInventoryManager() : InventoryManager()
{
}
ServerInventoryManager::~ServerInventoryManager()
{
// Delete detached inventories
for (auto &detached_inventory : m_detached_inventories) {
delete detached_inventory.second.inventory;
}
}
Inventory *ServerInventoryManager::getInventory(const InventoryLocation &loc)
{
switch (loc.type) {
case InventoryLocation::UNDEFINED:
case InventoryLocation::CURRENT_PLAYER:
break;
case InventoryLocation::PLAYER: {
RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
if (!player)
return NULL;
PlayerSAO *playersao = player->getPlayerSAO();
if (!playersao)
return NULL;
return playersao->getInventory();
} break;
case InventoryLocation::NODEMETA: {
NodeMetadata *meta = m_env->getMap().getNodeMetadata(loc.p);
if (!meta)
return NULL;
return meta->getInventory();
} break;
case InventoryLocation::DETACHED: {
auto it = m_detached_inventories.find(loc.name);
if (it == m_detached_inventories.end())
return nullptr;
return it->second.inventory;
} break;
default:
sanity_check(false); // abort
break;
}
return NULL;
}
void ServerInventoryManager::setInventoryModified(const InventoryLocation &loc)
{
switch (loc.type) {
case InventoryLocation::UNDEFINED:
break;
case InventoryLocation::PLAYER: {
RemotePlayer *player = m_env->getPlayer(loc.name.c_str());
if (!player)
return;
player->setModified(true);
player->inventory.setModified(true);
// Updates are sent in ServerEnvironment::step()
} break;
case InventoryLocation::NODEMETA: {
MapEditEvent event;
event.type = MEET_BLOCK_NODE_METADATA_CHANGED;
event.p = loc.p;
m_env->getMap().dispatchEvent(event);
} break;
case InventoryLocation::DETACHED: {
// Updates are sent in ServerEnvironment::step()
} break;
default:
sanity_check(false); // abort
break;
}
}
Inventory *ServerInventoryManager::createDetachedInventory(
const std::string &name, IItemDefManager *idef, const std::string &player)
{
if (m_detached_inventories.count(name) > 0) {
infostream << "Server clearing detached inventory \"" << name << "\""
<< std::endl;
delete m_detached_inventories[name].inventory;
} else {
infostream << "Server creating detached inventory \"" << name << "\""
<< std::endl;
}
Inventory *inv = new Inventory(idef);
sanity_check(inv);
m_detached_inventories[name].inventory = inv;
if (!player.empty()) {
m_detached_inventories[name].owner = player;
if (!m_env)
return inv; // Mods are not loaded yet, ignore
RemotePlayer *p = m_env->getPlayer(name.c_str());
// if player is connected, send him the inventory
if (p && p->getPeerId() != PEER_ID_INEXISTENT) {
m_env->getGameDef()->sendDetachedInventory(
inv, name, p->getPeerId());
}
} else {
if (!m_env)
return inv; // Mods are not loaded yet, don't send
// Inventory is for everybody, broadcast
m_env->getGameDef()->sendDetachedInventory(inv, name, PEER_ID_INEXISTENT);
}
return inv;
}
bool ServerInventoryManager::removeDetachedInventory(const std::string &name)
{
const auto &inv_it = m_detached_inventories.find(name);
if (inv_it == m_detached_inventories.end())
return false;
delete inv_it->second.inventory;
const std::string &owner = inv_it->second.owner;
if (!owner.empty()) {
RemotePlayer *player = m_env->getPlayer(owner.c_str());
if (player && player->getPeerId() != PEER_ID_INEXISTENT)
m_env->getGameDef()->sendDetachedInventory(
nullptr, name, player->getPeerId());
} else {
// Notify all players about the change
m_env->getGameDef()->sendDetachedInventory(
nullptr, name, PEER_ID_INEXISTENT);
}
m_detached_inventories.erase(inv_it);
return true;
}
void ServerInventoryManager::sendDetachedInventories(const std::string &peer_name,
bool incremental,
std::function<void(const std::string &, Inventory *)> apply_cb)
{
for (const auto &detached_inventory : m_detached_inventories) {
const DetachedInventory &dinv = detached_inventory.second;
if (incremental) {
if (!dinv.inventory || !dinv.inventory->checkModified())
continue;
}
// if we are pushing inventories to a specific player
// we should filter to send only the right inventories
if (!peer_name.empty()) {
const std::string &attached_player = dinv.owner;
if (!attached_player.empty() && peer_name != attached_player)
continue;
}
apply_cb(detached_inventory.first, detached_inventory.second.inventory);
}
}

View File

@ -0,0 +1,60 @@
/*
Minetest
Copyright (C) 2010-2020 Minetest core development team
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "inventorymanager.h"
#include <functional>
class ServerEnvironment;
class ServerInventoryManager : public InventoryManager
{
public:
ServerInventoryManager();
virtual ~ServerInventoryManager();
void setEnv(ServerEnvironment *env)
{
assert(!m_env);
m_env = env;
}
Inventory *getInventory(const InventoryLocation &loc);
void setInventoryModified(const InventoryLocation &loc);
// Creates or resets inventory
Inventory *createDetachedInventory(const std::string &name, IItemDefManager *idef,
const std::string &player = "");
bool removeDetachedInventory(const std::string &name);
void sendDetachedInventories(const std::string &peer_name, bool incremental,
std::function<void(const std::string &, Inventory *)> apply_cb);
private:
struct DetachedInventory
{
Inventory *inventory;
std::string owner;
};
ServerEnvironment *m_env = nullptr;
std::unordered_map<std::string, DetachedInventory> m_detached_inventories;
};

348
src/server/unit_sao.cpp Normal file
View File

@ -0,0 +1,348 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013-2020 Minetest core developers & community
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "unit_sao.h"
#include "scripting_server.h"
#include "serverenvironment.h"
UnitSAO::UnitSAO(ServerEnvironment *env, v3f pos) : ServerActiveObject(env, pos)
{
// Initialize something to armor groups
m_armor_groups["fleshy"] = 100;
}
ServerActiveObject *UnitSAO::getParent() const
{
if (!m_attachment_parent_id)
return nullptr;
// Check if the parent still exists
ServerActiveObject *obj = m_env->getActiveObject(m_attachment_parent_id);
return obj;
}
void UnitSAO::setArmorGroups(const ItemGroupList &armor_groups)
{
m_armor_groups = armor_groups;
m_armor_groups_sent = false;
}
const ItemGroupList &UnitSAO::getArmorGroups() const
{
return m_armor_groups;
}
void UnitSAO::setAnimation(
v2f frame_range, float frame_speed, float frame_blend, bool frame_loop)
{
// store these so they can be updated to clients
m_animation_range = frame_range;
m_animation_speed = frame_speed;
m_animation_blend = frame_blend;
m_animation_loop = frame_loop;
m_animation_sent = false;
}
void UnitSAO::getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend,
bool *frame_loop)
{
*frame_range = m_animation_range;
*frame_speed = m_animation_speed;
*frame_blend = m_animation_blend;
*frame_loop = m_animation_loop;
}
void UnitSAO::setAnimationSpeed(float frame_speed)
{
m_animation_speed = frame_speed;
m_animation_speed_sent = false;
}
void UnitSAO::setBonePosition(const std::string &bone, v3f position, v3f rotation)
{
// store these so they can be updated to clients
m_bone_position[bone] = core::vector2d<v3f>(position, rotation);
m_bone_position_sent = false;
}
void UnitSAO::getBonePosition(const std::string &bone, v3f *position, v3f *rotation)
{
*position = m_bone_position[bone].X;
*rotation = m_bone_position[bone].Y;
}
// clang-format off
void UnitSAO::sendOutdatedData()
{
if (!m_armor_groups_sent) {
m_armor_groups_sent = true;
m_messages_out.emplace(getId(), true, generateUpdateArmorGroupsCommand());
}
if (!m_animation_sent) {
m_animation_sent = true;
m_animation_speed_sent = true;
m_messages_out.emplace(getId(), true, generateUpdateAnimationCommand());
} else if (!m_animation_speed_sent) {
// Animation speed is also sent when 'm_animation_sent == false'
m_animation_speed_sent = true;
m_messages_out.emplace(getId(), true, generateUpdateAnimationSpeedCommand());
}
if (!m_bone_position_sent) {
m_bone_position_sent = true;
for (const auto &bone_pos : m_bone_position) {
m_messages_out.emplace(getId(), true, generateUpdateBonePositionCommand(
bone_pos.first, bone_pos.second.X, bone_pos.second.Y));
}
}
if (!m_attachment_sent) {
m_attachment_sent = true;
m_messages_out.emplace(getId(), true, generateUpdateAttachmentCommand());
}
}
// clang-format on
void UnitSAO::setAttachment(int parent_id, const std::string &bone, v3f position,
v3f rotation, bool force_visible)
{
// Attachments need to be handled on both the server and client.
// If we just attach on the server, we can only copy the position of the parent.
// Attachments are still sent to clients at an interval so players might see them
// lagging, plus we can't read and attach to skeletal bones. If we just attach on
// the client, the server still sees the child at its original location. This
// breaks some things so we also give the server the most accurate representation
// even if players only see the client changes.
int old_parent = m_attachment_parent_id;
m_attachment_parent_id = parent_id;
m_attachment_bone = bone;
m_attachment_position = position;
m_attachment_rotation = rotation;
m_force_visible = force_visible;
m_attachment_sent = false;
if (parent_id != old_parent) {
onDetach(old_parent);
onAttach(parent_id);
}
}
void UnitSAO::getAttachment(int *parent_id, std::string *bone, v3f *position,
v3f *rotation, bool *force_visible) const
{
*parent_id = m_attachment_parent_id;
*bone = m_attachment_bone;
*position = m_attachment_position;
*rotation = m_attachment_rotation;
*force_visible = m_force_visible;
}
void UnitSAO::clearChildAttachments()
{
for (int child_id : m_attachment_child_ids) {
// Child can be NULL if it was deleted earlier
if (ServerActiveObject *child = m_env->getActiveObject(child_id))
child->setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0), false);
}
m_attachment_child_ids.clear();
}
void UnitSAO::clearParentAttachment()
{
ServerActiveObject *parent = nullptr;
if (m_attachment_parent_id) {
parent = m_env->getActiveObject(m_attachment_parent_id);
setAttachment(0, "", m_attachment_position, m_attachment_rotation, false);
} else {
setAttachment(0, "", v3f(0, 0, 0), v3f(0, 0, 0), false);
}
// Do it
if (parent)
parent->removeAttachmentChild(m_id);
}
void UnitSAO::addAttachmentChild(int child_id)
{
m_attachment_child_ids.insert(child_id);
}
void UnitSAO::removeAttachmentChild(int child_id)
{
m_attachment_child_ids.erase(child_id);
}
const std::unordered_set<int> &UnitSAO::getAttachmentChildIds() const
{
return m_attachment_child_ids;
}
void UnitSAO::onAttach(int parent_id)
{
if (!parent_id)
return;
ServerActiveObject *parent = m_env->getActiveObject(parent_id);
if (!parent || parent->isGone())
return; // Do not try to notify soon gone parent
if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY) {
// Call parent's on_attach field
m_env->getScriptIface()->luaentity_on_attach_child(parent_id, this);
}
}
void UnitSAO::onDetach(int parent_id)
{
if (!parent_id)
return;
ServerActiveObject *parent = m_env->getActiveObject(parent_id);
if (getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
m_env->getScriptIface()->luaentity_on_detach(m_id, parent);
if (!parent || parent->isGone())
return; // Do not try to notify soon gone parent
if (parent->getType() == ACTIVEOBJECT_TYPE_LUAENTITY)
m_env->getScriptIface()->luaentity_on_detach_child(parent_id, this);
}
ObjectProperties *UnitSAO::accessObjectProperties()
{
return &m_prop;
}
void UnitSAO::notifyObjectPropertiesModified()
{
m_properties_sent = false;
}
std::string UnitSAO::generateUpdateAttachmentCommand() const
{
std::ostringstream os(std::ios::binary);
// command
writeU8(os, AO_CMD_ATTACH_TO);
// parameters
writeS16(os, m_attachment_parent_id);
os << serializeString16(m_attachment_bone);
writeV3F32(os, m_attachment_position);
writeV3F32(os, m_attachment_rotation);
writeU8(os, m_force_visible);
return os.str();
}
std::string UnitSAO::generateUpdateBonePositionCommand(
const std::string &bone, const v3f &position, const v3f &rotation)
{
std::ostringstream os(std::ios::binary);
// command
writeU8(os, AO_CMD_SET_BONE_POSITION);
// parameters
os << serializeString16(bone);
writeV3F32(os, position);
writeV3F32(os, rotation);
return os.str();
}
std::string UnitSAO::generateUpdateAnimationSpeedCommand() const
{
std::ostringstream os(std::ios::binary);
// command
writeU8(os, AO_CMD_SET_ANIMATION_SPEED);
// parameters
writeF32(os, m_animation_speed);
return os.str();
}
std::string UnitSAO::generateUpdateAnimationCommand() const
{
std::ostringstream os(std::ios::binary);
// command
writeU8(os, AO_CMD_SET_ANIMATION);
// parameters
writeV2F32(os, m_animation_range);
writeF32(os, m_animation_speed);
writeF32(os, m_animation_blend);
// these are sent inverted so we get true when the server sends nothing
writeU8(os, !m_animation_loop);
return os.str();
}
std::string UnitSAO::generateUpdateArmorGroupsCommand() const
{
std::ostringstream os(std::ios::binary);
writeU8(os, AO_CMD_UPDATE_ARMOR_GROUPS);
writeU16(os, m_armor_groups.size());
for (const auto &armor_group : m_armor_groups) {
os << serializeString16(armor_group.first);
writeS16(os, armor_group.second);
}
return os.str();
}
std::string UnitSAO::generateUpdatePositionCommand(const v3f &position,
const v3f &velocity, const v3f &acceleration, const v3f &rotation,
bool do_interpolate, bool is_movement_end, f32 update_interval)
{
std::ostringstream os(std::ios::binary);
// command
writeU8(os, AO_CMD_UPDATE_POSITION);
// pos
writeV3F32(os, position);
// velocity
writeV3F32(os, velocity);
// acceleration
writeV3F32(os, acceleration);
// rotation
writeV3F32(os, rotation);
// do_interpolate
writeU8(os, do_interpolate);
// is_end_position (for interpolation)
writeU8(os, is_movement_end);
// update_interval (for interpolation)
writeF32(os, update_interval);
return os.str();
}
std::string UnitSAO::generateSetPropertiesCommand(const ObjectProperties &prop) const
{
std::ostringstream os(std::ios::binary);
writeU8(os, AO_CMD_SET_PROPERTIES);
prop.serialize(os);
return os.str();
}
std::string UnitSAO::generatePunchCommand(u16 result_hp) const
{
std::ostringstream os(std::ios::binary);
// command
writeU8(os, AO_CMD_PUNCHED);
// result_hp
writeU16(os, result_hp);
return os.str();
}
void UnitSAO::sendPunchCommand()
{
m_messages_out.emplace(getId(), true, generatePunchCommand(getHP()));
}

137
src/server/unit_sao.h Normal file
View File

@ -0,0 +1,137 @@
/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013-2020 Minetest core developers & community
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include "object_properties.h"
#include "serveractiveobject.h"
class UnitSAO : public ServerActiveObject
{
public:
UnitSAO(ServerEnvironment *env, v3f pos);
virtual ~UnitSAO() = default;
u16 getHP() const { return m_hp; }
// Use a function, if isDead can be defined by other conditions
bool isDead() const { return m_hp == 0; }
// Rotation
void setRotation(v3f rotation) { m_rotation = rotation; }
const v3f &getRotation() const { return m_rotation; }
v3f getRadRotation() { return m_rotation * core::DEGTORAD; }
// Deprecated
f32 getRadYawDep() const { return (m_rotation.Y + 90.) * core::DEGTORAD; }
// Armor groups
inline bool isImmortal() const
{
return itemgroup_get(getArmorGroups(), "immortal");
}
void setArmorGroups(const ItemGroupList &armor_groups);
const ItemGroupList &getArmorGroups() const;
// Animation
void setAnimation(v2f frame_range, float frame_speed, float frame_blend,
bool frame_loop);
void getAnimation(v2f *frame_range, float *frame_speed, float *frame_blend,
bool *frame_loop);
void setAnimationSpeed(float frame_speed);
// Bone position
void setBonePosition(const std::string &bone, v3f position, v3f rotation);
void getBonePosition(const std::string &bone, v3f *position, v3f *rotation);
// Attachments
ServerActiveObject *getParent() const;
inline bool isAttached() const { return getParent(); }
void setAttachment(int parent_id, const std::string &bone, v3f position,
v3f rotation, bool force_visible);
void getAttachment(int *parent_id, std::string *bone, v3f *position,
v3f *rotation, bool *force_visible) const;
void clearChildAttachments();
void clearParentAttachment();
void addAttachmentChild(int child_id);
void removeAttachmentChild(int child_id);
const std::unordered_set<int> &getAttachmentChildIds() const;
// Object properties
ObjectProperties *accessObjectProperties();
void notifyObjectPropertiesModified();
void sendOutdatedData();
// Update packets
std::string generateUpdateAttachmentCommand() const;
std::string generateUpdateAnimationSpeedCommand() const;
std::string generateUpdateAnimationCommand() const;
std::string generateUpdateArmorGroupsCommand() const;
static std::string generateUpdatePositionCommand(const v3f &position,
const v3f &velocity, const v3f &acceleration, const v3f &rotation,
bool do_interpolate, bool is_movement_end, f32 update_interval);
std::string generateSetPropertiesCommand(const ObjectProperties &prop) const;
static std::string generateUpdateBonePositionCommand(const std::string &bone,
const v3f &position, const v3f &rotation);
void sendPunchCommand();
protected:
u16 m_hp = 1;
v3f m_rotation;
ItemGroupList m_armor_groups;
// Object properties
bool m_properties_sent = true;
ObjectProperties m_prop;
// Stores position and rotation for each bone name
std::unordered_map<std::string, core::vector2d<v3f>> m_bone_position;
int m_attachment_parent_id = 0;
private:
void onAttach(int parent_id);
void onDetach(int parent_id);
std::string generatePunchCommand(u16 result_hp) const;
// Armor groups
bool m_armor_groups_sent = false;
// Animation
v2f m_animation_range;
float m_animation_speed = 0.0f;
float m_animation_blend = 0.0f;
bool m_animation_loop = true;
bool m_animation_sent = false;
bool m_animation_speed_sent = false;
// Bone positions
bool m_bone_position_sent = false;
// Attachments
std::unordered_set<int> m_attachment_child_ids;
std::string m_attachment_bone = "";
v3f m_attachment_position;
v3f m_attachment_rotation;
bool m_attachment_sent = false;
bool m_force_visible = false;
};

View File

@ -0,0 +1,6 @@
set(JTHREAD_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/event.cpp
${CMAKE_CURRENT_SOURCE_DIR}/thread.cpp
${CMAKE_CURRENT_SOURCE_DIR}/semaphore.cpp
PARENT_SCOPE)

44
src/threading/event.cpp Normal file
View File

@ -0,0 +1,44 @@
/*
This file is a part of the JThread package, which contains some object-
oriented thread wrappers for different thread implementations.
Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "threading/event.h"
#include "threading/mutex_auto_lock.h"
void Event::wait()
{
MutexAutoLock lock(mutex);
while (!notified) {
cv.wait(lock);
}
notified = false;
}
void Event::signal()
{
MutexAutoLock lock(mutex);
notified = true;
cv.notify_one();
}

46
src/threading/event.h Normal file
View File

@ -0,0 +1,46 @@
/*
This file is a part of the JThread package, which contains some object-
oriented thread wrappers for different thread implementations.
Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <condition_variable>
/** A syncronization primitive that will wake up one waiting thread when signaled.
* Calling @c signal() multiple times before a waiting thread has had a chance
* to notice the signal will wake only one thread. Additionally, if no threads
* are waiting on the event when it is signaled, the next call to @c wait()
* will return (almost) immediately.
*/
class Event
{
public:
void wait();
void signal();
private:
std::condition_variable cv;
std::mutex mutex;
bool notified = false;
};

View File

@ -0,0 +1,30 @@
/*
This file is a part of the JThread package, which contains some object-
oriented thread wrappers for different thread implementations.
Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include <mutex>
using MutexAutoLock = std::unique_lock<std::mutex>;
using RecursiveMutexAutoLock = std::unique_lock<std::recursive_mutex>;

167
src/threading/semaphore.cpp Normal file
View File

@ -0,0 +1,167 @@
/*
Minetest
Copyright (C) 2013 sapier <sapier AT gmx DOT net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "threading/semaphore.h"
#include <iostream>
#include <cstdlib>
#include <cassert>
#define UNUSED(expr) do { (void)(expr); } while (0)
#ifdef _WIN32
#include <climits>
#define MAX_SEMAPHORE_COUNT LONG_MAX - 1
#else
#include <cerrno>
#include <sys/time.h>
#include <pthread.h>
#if defined(__MACH__) && defined(__APPLE__)
#include <mach/mach.h>
#include <mach/task.h>
#include <mach/semaphore.h>
#include <sys/semaphore.h>
#include <unistd.h>
#undef sem_t
#undef sem_init
#undef sem_wait
#undef sem_post
#undef sem_destroy
#define sem_t semaphore_t
#define sem_init(s, p, c) semaphore_create(mach_task_self(), (s), 0, (c))
#define sem_wait(s) semaphore_wait(*(s))
#define sem_post(s) semaphore_signal(*(s))
#define sem_destroy(s) semaphore_destroy(mach_task_self(), *(s))
#endif
#endif
Semaphore::Semaphore(int val)
{
#ifdef _WIN32
semaphore = CreateSemaphore(NULL, val, MAX_SEMAPHORE_COUNT, NULL);
#else
int ret = sem_init(&semaphore, 0, val);
assert(!ret);
UNUSED(ret);
#endif
}
Semaphore::~Semaphore()
{
#ifdef _WIN32
CloseHandle(semaphore);
#else
int ret = sem_destroy(&semaphore);
#ifdef __ANDROID__
// Workaround for broken bionic semaphore implementation!
assert(!ret || errno == EBUSY);
#else
assert(!ret);
#endif
UNUSED(ret);
#endif
}
void Semaphore::post(unsigned int num)
{
assert(num > 0);
#ifdef _WIN32
ReleaseSemaphore(semaphore, num, NULL);
#else
for (unsigned i = 0; i < num; i++) {
int ret = sem_post(&semaphore);
assert(!ret);
UNUSED(ret);
}
#endif
}
void Semaphore::wait()
{
#ifdef _WIN32
WaitForSingleObject(semaphore, INFINITE);
#else
int ret = sem_wait(&semaphore);
assert(!ret);
UNUSED(ret);
#endif
}
bool Semaphore::wait(unsigned int time_ms)
{
#ifdef _WIN32
unsigned int ret = WaitForSingleObject(semaphore, time_ms);
if (ret == WAIT_OBJECT_0) {
return true;
} else {
assert(ret == WAIT_TIMEOUT);
return false;
}
#else
# if defined(__MACH__) && defined(__APPLE__)
mach_timespec_t wait_time;
wait_time.tv_sec = time_ms / 1000;
wait_time.tv_nsec = 1000000 * (time_ms % 1000);
errno = 0;
int ret = semaphore_timedwait(semaphore, wait_time);
switch (ret) {
case KERN_OPERATION_TIMED_OUT:
errno = ETIMEDOUT;
break;
case KERN_ABORTED:
errno = EINTR;
break;
default:
if (ret)
errno = EINVAL;
}
# else
int ret;
if (time_ms > 0) {
struct timespec wait_time;
struct timeval now;
if (gettimeofday(&now, NULL) == -1) {
std::cerr << "Semaphore::wait(ms): Unable to get time with gettimeofday!" << std::endl;
abort();
}
wait_time.tv_nsec = ((time_ms % 1000) * 1000 * 1000) + (now.tv_usec * 1000);
wait_time.tv_sec = (time_ms / 1000) + (wait_time.tv_nsec / (1000 * 1000 * 1000)) + now.tv_sec;
wait_time.tv_nsec %= 1000 * 1000 * 1000;
ret = sem_timedwait(&semaphore, &wait_time);
} else {
ret = sem_trywait(&semaphore);
}
# endif
assert(!ret || (errno == ETIMEDOUT || errno == EINTR || errno == EAGAIN));
return !ret;
#endif
}

52
src/threading/semaphore.h Normal file
View File

@ -0,0 +1,52 @@
/*
Minetest
Copyright (C) 2013 sapier <sapier AT gmx DOT net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#if defined(_WIN32)
#include <windows.h>
#elif defined(__MACH__) && defined(__APPLE__)
#include <mach/semaphore.h>
#else
#include <semaphore.h>
#endif
#include "util/basic_macros.h"
class Semaphore
{
public:
Semaphore(int val = 0);
~Semaphore();
DISABLE_CLASS_COPY(Semaphore);
void post(unsigned int num = 1);
void wait();
bool wait(unsigned int time_ms);
private:
#if defined(WIN32)
HANDLE semaphore;
#elif defined(__MACH__) && defined(__APPLE__)
semaphore_t semaphore;
#else
sem_t semaphore;
#endif
};

347
src/threading/thread.cpp Normal file
View File

@ -0,0 +1,347 @@
/*
This file is a part of the JThread package, which contains some object-
oriented thread wrappers for different thread implementations.
Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "threading/thread.h"
#include "threading/mutex_auto_lock.h"
#include "log.h"
#include "porting.h"
// for setName
#if defined(__linux__)
#include <sys/prctl.h>
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
#include <pthread_np.h>
#elif defined(__NetBSD__)
#include <sched.h>
#elif defined(_MSC_VER)
struct THREADNAME_INFO {
DWORD dwType; // Must be 0x1000
LPCSTR szName; // Pointer to name (in user addr space)
DWORD dwThreadID; // Thread ID (-1=caller thread)
DWORD dwFlags; // Reserved for future use, must be zero
};
#endif
// for bindToProcessor
#if __FreeBSD_version >= 702106
typedef cpuset_t cpu_set_t;
#elif defined(__sun) || defined(sun)
#include <sys/types.h>
#include <sys/processor.h>
#include <sys/procset.h>
#elif defined(_AIX)
#include <sys/processor.h>
#include <sys/thread.h>
#elif defined(__APPLE__)
#include <mach/mach_init.h>
#include <mach/thread_act.h>
#endif
Thread::Thread(const std::string &name) :
m_name(name),
m_request_stop(false),
m_running(false)
{
#ifdef _AIX
m_kernel_thread_id = -1;
#endif
}
Thread::~Thread()
{
// kill the thread if running
if (!m_running) {
wait();
} else {
m_running = false;
#if defined(_WIN32)
// See https://msdn.microsoft.com/en-us/library/hh920601.aspx#thread__native_handle_method
TerminateThread((HANDLE) m_thread_obj->native_handle(), 0);
CloseHandle((HANDLE) m_thread_obj->native_handle());
#else
// We need to pthread_kill instead on Android since NDKv5's pthread
// implementation is incomplete.
# ifdef __ANDROID__
pthread_kill(getThreadHandle(), SIGKILL);
# else
pthread_cancel(getThreadHandle());
# endif
wait();
#endif
}
// Make sure start finished mutex is unlocked before it's destroyed
if (m_start_finished_mutex.try_lock())
m_start_finished_mutex.unlock();
}
bool Thread::start()
{
MutexAutoLock lock(m_mutex);
if (m_running)
return false;
m_request_stop = false;
// The mutex may already be locked if the thread is being restarted
m_start_finished_mutex.try_lock();
try {
m_thread_obj = new std::thread(threadProc, this);
} catch (const std::system_error &e) {
return false;
}
// Allow spawned thread to continue
m_start_finished_mutex.unlock();
while (!m_running)
sleep_ms(1);
m_joinable = true;
return true;
}
bool Thread::stop()
{
m_request_stop = true;
return true;
}
bool Thread::wait()
{
MutexAutoLock lock(m_mutex);
if (!m_joinable)
return false;
m_thread_obj->join();
delete m_thread_obj;
m_thread_obj = nullptr;
assert(m_running == false);
m_joinable = false;
return true;
}
bool Thread::getReturnValue(void **ret)
{
if (m_running)
return false;
*ret = m_retval;
return true;
}
void Thread::threadProc(Thread *thr)
{
#ifdef _AIX
thr->m_kernel_thread_id = thread_self();
#endif
thr->setName(thr->m_name);
g_logger.registerThread(thr->m_name);
thr->m_running = true;
// Wait for the thread that started this one to finish initializing the
// thread handle so that getThreadId/getThreadHandle will work.
thr->m_start_finished_mutex.lock();
thr->m_retval = thr->run();
thr->m_running = false;
// Unlock m_start_finished_mutex to prevent data race condition on Windows.
// On Windows with VS2017 build TerminateThread is called and this mutex is not
// released. We try to unlock it from caller thread and it's refused by system.
thr->m_start_finished_mutex.unlock();
g_logger.deregisterThread();
}
void Thread::setName(const std::string &name)
{
#if defined(__linux__)
// It would be cleaner to do this with pthread_setname_np,
// which was added to glibc in version 2.12, but some major
// distributions are still runing 2.11 and previous versions.
prctl(PR_SET_NAME, name.c_str());
#elif defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
pthread_set_name_np(pthread_self(), name.c_str());
#elif defined(__NetBSD__)
pthread_setname_np(pthread_self(), "%s", const_cast<char*>(name.c_str()));
#elif defined(__APPLE__)
pthread_setname_np(name.c_str());
#elif defined(__HAIKU__)
rename_thread(find_thread(NULL), name.c_str());
#elif defined(_MSC_VER)
// Windows itself doesn't support thread names,
// but the MSVC debugger does...
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = name.c_str();
info.dwThreadID = -1;
info.dwFlags = 0;
__try {
RaiseException(0x406D1388, 0,
sizeof(info) / sizeof(DWORD), (ULONG_PTR *)&info);
} __except (EXCEPTION_CONTINUE_EXECUTION) {
}
#elif defined(_WIN32) || defined(__GNU__)
// These platforms are known to not support thread names.
// Silently ignore the request.
#else
#warning "Unrecognized platform, thread names will not be available."
#endif
}
unsigned int Thread::getNumberOfProcessors()
{
return std::thread::hardware_concurrency();
}
bool Thread::bindToProcessor(unsigned int proc_number)
{
#if defined(__ANDROID__)
return false;
#elif _MSC_VER
return SetThreadAffinityMask(getThreadHandle(), 1 << proc_number);
#elif __MINGW32__
return SetThreadAffinityMask(pthread_gethandle(getThreadHandle()), 1 << proc_number);
#elif __FreeBSD_version >= 702106 || defined(__linux__) || defined(__DragonFly__)
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(proc_number, &cpuset);
return pthread_setaffinity_np(getThreadHandle(), sizeof(cpuset), &cpuset) == 0;
#elif defined(__NetBSD__)
cpuset_t *cpuset = cpuset_create();
if (cpuset == NULL)
return false;
int r = pthread_setaffinity_np(getThreadHandle(), cpuset_size(cpuset), cpuset);
cpuset_destroy(cpuset);
return r == 0;
#elif defined(__sun) || defined(sun)
return processor_bind(P_LWPID, P_MYID, proc_number, NULL) == 0
#elif defined(_AIX)
return bindprocessor(BINDTHREAD, m_kernel_thread_id, proc_number) == 0;
#elif defined(__hpux) || defined(hpux)
pthread_spu_t answer;
return pthread_processor_bind_np(PTHREAD_BIND_ADVISORY_NP,
&answer, proc_number, getThreadHandle()) == 0;
#elif defined(__APPLE__)
struct thread_affinity_policy tapol;
thread_port_t threadport = pthread_mach_thread_np(getThreadHandle());
tapol.affinity_tag = proc_number + 1;
return thread_policy_set(threadport, THREAD_AFFINITY_POLICY,
(thread_policy_t)&tapol,
THREAD_AFFINITY_POLICY_COUNT) == KERN_SUCCESS;
#else
return false;
#endif
}
bool Thread::setPriority(int prio)
{
#ifdef _MSC_VER
return SetThreadPriority(getThreadHandle(), prio);
#elif __MINGW32__
return SetThreadPriority(pthread_gethandle(getThreadHandle()), prio);
#else
struct sched_param sparam;
int policy;
if (pthread_getschedparam(getThreadHandle(), &policy, &sparam) != 0)
return false;
int min = sched_get_priority_min(policy);
int max = sched_get_priority_max(policy);
sparam.sched_priority = min + prio * (max - min) / THREAD_PRIORITY_HIGHEST;
return pthread_setschedparam(getThreadHandle(), policy, &sparam) == 0;
#endif
}

160
src/threading/thread.h Normal file
View File

@ -0,0 +1,160 @@
/*
This file is a part of the JThread package, which contains some object-
oriented thread wrappers for different thread implementations.
Copyright (c) 2000-2006 Jori Liesenborgs (jori.liesenborgs@gmail.com)
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#pragma once
#include "util/basic_macros.h"
#include <string>
#include <atomic>
#include <thread>
#include <mutex>
#ifdef _AIX
#include <sys/thread.h> // for tid_t
#endif
#ifdef __HAIKU__
#include <kernel/OS.h>
#endif
/*
* On platforms using pthreads, these five priority classes correlate to
* even divisions between the minimum and maximum reported thread priority.
*/
#if !defined(_WIN32)
#define THREAD_PRIORITY_LOWEST 0
#define THREAD_PRIORITY_BELOW_NORMAL 1
#define THREAD_PRIORITY_NORMAL 2
#define THREAD_PRIORITY_ABOVE_NORMAL 3
#define THREAD_PRIORITY_HIGHEST 4
#endif
class Thread {
public:
Thread(const std::string &name="");
virtual ~Thread();
DISABLE_CLASS_COPY(Thread)
/*
* Begins execution of a new thread at the pure virtual method Thread::run().
* Execution of the thread is guaranteed to have started after this function
* returns.
*/
bool start();
/*
* Requests that the thread exit gracefully.
* Returns immediately; thread execution is guaranteed to be complete after
* a subsequent call to Thread::wait.
*/
bool stop();
/*
* Waits for thread to finish.
* Note: This does not stop a thread, you have to do this on your own.
* Returns false immediately if the thread is not started or has been waited
* on before.
*/
bool wait();
/*
* Returns true if the calling thread is this Thread object.
*/
bool isCurrentThread() { return std::this_thread::get_id() == getThreadId(); }
bool isRunning() { return m_running; }
bool stopRequested() { return m_request_stop; }
std::thread::id getThreadId() { return m_thread_obj->get_id(); }
/*
* Gets the thread return value.
* Returns true if the thread has exited and the return value was available,
* or false if the thread has yet to finish.
*/
bool getReturnValue(void **ret);
/*
* Binds (if possible, otherwise sets the affinity of) the thread to the
* specific processor specified by proc_number.
*/
bool bindToProcessor(unsigned int proc_number);
/*
* Sets the thread priority to the specified priority.
*
* prio can be one of: THREAD_PRIORITY_LOWEST, THREAD_PRIORITY_BELOW_NORMAL,
* THREAD_PRIORITY_NORMAL, THREAD_PRIORITY_ABOVE_NORMAL, THREAD_PRIORITY_HIGHEST.
* On Windows, any of the other priorites as defined by SetThreadPriority
* are supported as well.
*
* Note that it may be necessary to first set the threading policy or
* scheduling algorithm to one that supports thread priorities if not
* supported by default, otherwise this call will have no effect.
*/
bool setPriority(int prio);
/*
* Sets the currently executing thread's name to where supported; useful
* for debugging.
*/
static void setName(const std::string &name);
/*
* Returns the number of processors/cores configured and active on this machine.
*/
static unsigned int getNumberOfProcessors();
protected:
std::string m_name;
virtual void *run() = 0;
private:
std::thread::native_handle_type getThreadHandle()
{ return m_thread_obj->native_handle(); }
static void threadProc(Thread *thr);
void *m_retval = nullptr;
bool m_joinable = false;
std::atomic<bool> m_request_stop;
std::atomic<bool> m_running;
std::mutex m_mutex;
std::mutex m_start_finished_mutex;
std::thread *m_thread_obj = nullptr;
#ifdef _AIX
// For AIX, there does not exist any mapping from pthread_t to tid_t
// available to us, so we maintain one ourselves. This is set on thread start.
tid_t m_kernel_thread_id;
#endif
};

View File

@ -0,0 +1,51 @@
set (UNITTEST_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/test.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_address.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_authdatabase.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_activeobject.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_areastore.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_ban.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_collision.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_compression.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_connection.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_filepath.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_inventory.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_irrptr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_map_settings_manager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_mapnode.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_modchannels.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_nodedef.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_noderesolver.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_noise.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_objdef.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_player.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_profiler.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_random.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_schematic.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_serialization.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_serveractiveobjectmgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_server_shutdown_state.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_settings.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_socket.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_servermodmanager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_threading.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_utilities.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_voxelarea.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_voxelalgorithms.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_voxelmanipulator.cpp
PARENT_SCOPE)
set (UNITTEST_CLIENT_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/test_clientactiveobjectmgr.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_eventmanager.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_gameui.cpp
${CMAKE_CURRENT_SOURCE_DIR}/test_keycode.cpp
PARENT_SCOPE)
set (TEST_WORLDDIR ${CMAKE_CURRENT_SOURCE_DIR}/test_world)
set (TEST_SUBGAME_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../games/devtest)
configure_file(
"${CMAKE_CURRENT_SOURCE_DIR}/test_config.h.in"
"${PROJECT_BINARY_DIR}/test_config.h"
)

676
src/unittest/test.cpp Normal file
View File

@ -0,0 +1,676 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "client/sound.h"
#include "nodedef.h"
#include "itemdef.h"
#include "gamedef.h"
#include "modchannels.h"
#include "content/mods.h"
#include "util/numeric.h"
#include "porting.h"
content_t t_CONTENT_STONE;
content_t t_CONTENT_GRASS;
content_t t_CONTENT_TORCH;
content_t t_CONTENT_WATER;
content_t t_CONTENT_LAVA;
content_t t_CONTENT_BRICK;
////////////////////////////////////////////////////////////////////////////////
////
//// TestGameDef
////
class TestGameDef : public IGameDef {
public:
TestGameDef();
~TestGameDef();
IItemDefManager *getItemDefManager() { return m_itemdef; }
IWritableItemDefManager *getWritableItemDefManager() { return m_itemdef; }
const NodeDefManager *getNodeDefManager() { return m_nodedef; }
NodeDefManager *getWritableNodeDefManager() { return m_nodedef; }
ICraftDefManager *getCraftDefManager() { return m_craftdef; }
ITextureSource *getTextureSource() { return m_texturesrc; }
IShaderSource *getShaderSource() { return m_shadersrc; }
ISoundManager *getSoundManager() { return m_soundmgr; }
scene::ISceneManager *getSceneManager() { return m_scenemgr; }
IRollbackManager *getRollbackManager() { return m_rollbackmgr; }
EmergeManager *getEmergeManager() { return m_emergemgr; }
scene::IAnimatedMesh *getMesh(const std::string &filename) { return NULL; }
bool checkLocalPrivilege(const std::string &priv) { return false; }
u16 allocateUnknownNodeId(const std::string &name) { return 0; }
void defineSomeNodes();
virtual const std::vector<ModSpec> &getMods() const
{
static std::vector<ModSpec> testmodspec;
return testmodspec;
}
virtual const ModSpec* getModSpec(const std::string &modname) const { return NULL; }
virtual std::string getModStoragePath() const { return "."; }
virtual bool registerModStorage(ModMetadata *meta) { return true; }
virtual void unregisterModStorage(const std::string &name) {}
bool joinModChannel(const std::string &channel);
bool leaveModChannel(const std::string &channel);
bool sendModChannelMessage(const std::string &channel, const std::string &message);
ModChannel *getModChannel(const std::string &channel)
{
return m_modchannel_mgr->getModChannel(channel);
}
private:
IWritableItemDefManager *m_itemdef = nullptr;
NodeDefManager *m_nodedef = nullptr;
ICraftDefManager *m_craftdef = nullptr;
ITextureSource *m_texturesrc = nullptr;
IShaderSource *m_shadersrc = nullptr;
ISoundManager *m_soundmgr = nullptr;
scene::ISceneManager *m_scenemgr = nullptr;
IRollbackManager *m_rollbackmgr = nullptr;
EmergeManager *m_emergemgr = nullptr;
std::unique_ptr<ModChannelMgr> m_modchannel_mgr;
};
TestGameDef::TestGameDef() :
m_modchannel_mgr(new ModChannelMgr())
{
m_itemdef = createItemDefManager();
m_nodedef = createNodeDefManager();
defineSomeNodes();
}
TestGameDef::~TestGameDef()
{
delete m_itemdef;
delete m_nodedef;
}
void TestGameDef::defineSomeNodes()
{
IWritableItemDefManager *idef = (IWritableItemDefManager *)m_itemdef;
NodeDefManager *ndef = (NodeDefManager *)m_nodedef;
ItemDefinition itemdef;
ContentFeatures f;
//// Stone
itemdef = ItemDefinition();
itemdef.type = ITEM_NODE;
itemdef.name = "default:stone";
itemdef.description = "Stone";
itemdef.groups["cracky"] = 3;
itemdef.inventory_image = "[inventorycube"
"{default_stone.png"
"{default_stone.png"
"{default_stone.png";
f = ContentFeatures();
f.name = itemdef.name;
for (TileDef &tiledef : f.tiledef)
tiledef.name = "default_stone.png";
f.is_ground_content = true;
idef->registerItem(itemdef);
t_CONTENT_STONE = ndef->set(f.name, f);
//// Grass
itemdef = ItemDefinition();
itemdef.type = ITEM_NODE;
itemdef.name = "default:dirt_with_grass";
itemdef.description = "Dirt with grass";
itemdef.groups["crumbly"] = 3;
itemdef.inventory_image = "[inventorycube"
"{default_grass.png"
"{default_dirt.png&default_grass_side.png"
"{default_dirt.png&default_grass_side.png";
f = ContentFeatures();
f.name = itemdef.name;
f.tiledef[0].name = "default_grass.png";
f.tiledef[1].name = "default_dirt.png";
for(int i = 2; i < 6; i++)
f.tiledef[i].name = "default_dirt.png^default_grass_side.png";
f.is_ground_content = true;
idef->registerItem(itemdef);
t_CONTENT_GRASS = ndef->set(f.name, f);
//// Torch (minimal definition for lighting tests)
itemdef = ItemDefinition();
itemdef.type = ITEM_NODE;
itemdef.name = "default:torch";
f = ContentFeatures();
f.name = itemdef.name;
f.param_type = CPT_LIGHT;
f.light_propagates = true;
f.sunlight_propagates = true;
f.light_source = LIGHT_MAX-1;
idef->registerItem(itemdef);
t_CONTENT_TORCH = ndef->set(f.name, f);
//// Water
itemdef = ItemDefinition();
itemdef.type = ITEM_NODE;
itemdef.name = "default:water";
itemdef.description = "Water";
itemdef.inventory_image = "[inventorycube"
"{default_water.png"
"{default_water.png"
"{default_water.png";
f = ContentFeatures();
f.name = itemdef.name;
f.alpha = 128;
f.liquid_type = LIQUID_SOURCE;
f.liquid_viscosity = 4;
f.is_ground_content = true;
f.groups["liquids"] = 3;
for (TileDef &tiledef : f.tiledef)
tiledef.name = "default_water.png";
idef->registerItem(itemdef);
t_CONTENT_WATER = ndef->set(f.name, f);
//// Lava
itemdef = ItemDefinition();
itemdef.type = ITEM_NODE;
itemdef.name = "default:lava";
itemdef.description = "Lava";
itemdef.inventory_image = "[inventorycube"
"{default_lava.png"
"{default_lava.png"
"{default_lava.png";
f = ContentFeatures();
f.name = itemdef.name;
f.alpha = 128;
f.liquid_type = LIQUID_SOURCE;
f.liquid_viscosity = 7;
f.light_source = LIGHT_MAX-1;
f.is_ground_content = true;
f.groups["liquids"] = 3;
for (TileDef &tiledef : f.tiledef)
tiledef.name = "default_lava.png";
idef->registerItem(itemdef);
t_CONTENT_LAVA = ndef->set(f.name, f);
//// Brick
itemdef = ItemDefinition();
itemdef.type = ITEM_NODE;
itemdef.name = "default:brick";
itemdef.description = "Brick";
itemdef.groups["cracky"] = 3;
itemdef.inventory_image = "[inventorycube"
"{default_brick.png"
"{default_brick.png"
"{default_brick.png";
f = ContentFeatures();
f.name = itemdef.name;
for (TileDef &tiledef : f.tiledef)
tiledef.name = "default_brick.png";
f.is_ground_content = true;
idef->registerItem(itemdef);
t_CONTENT_BRICK = ndef->set(f.name, f);
}
bool TestGameDef::joinModChannel(const std::string &channel)
{
return m_modchannel_mgr->joinChannel(channel, PEER_ID_SERVER);
}
bool TestGameDef::leaveModChannel(const std::string &channel)
{
return m_modchannel_mgr->leaveChannel(channel, PEER_ID_SERVER);
}
bool TestGameDef::sendModChannelMessage(const std::string &channel,
const std::string &message)
{
if (!m_modchannel_mgr->channelRegistered(channel))
return false;
return true;
}
////
//// run_tests
////
bool run_tests()
{
u64 t1 = porting::getTimeMs();
TestGameDef gamedef;
g_logger.setLevelSilenced(LL_ERROR, true);
u32 num_modules_failed = 0;
u32 num_total_tests_failed = 0;
u32 num_total_tests_run = 0;
std::vector<TestBase *> &testmods = TestManager::getTestModules();
for (size_t i = 0; i != testmods.size(); i++) {
if (!testmods[i]->testModule(&gamedef))
num_modules_failed++;
num_total_tests_failed += testmods[i]->num_tests_failed;
num_total_tests_run += testmods[i]->num_tests_run;
}
u64 tdiff = porting::getTimeMs() - t1;
g_logger.setLevelSilenced(LL_ERROR, false);
const char *overall_status = (num_modules_failed == 0) ? "PASSED" : "FAILED";
rawstream
<< "++++++++++++++++++++++++++++++++++++++++"
<< "++++++++++++++++++++++++++++++++++++++++" << std::endl
<< "Unit Test Results: " << overall_status << std::endl
<< " " << num_modules_failed << " / " << testmods.size()
<< " failed modules (" << num_total_tests_failed << " / "
<< num_total_tests_run << " failed individual tests)." << std::endl
<< " Testing took " << tdiff << "ms total." << std::endl
<< "++++++++++++++++++++++++++++++++++++++++"
<< "++++++++++++++++++++++++++++++++++++++++" << std::endl;
return num_modules_failed;
}
////
//// TestBase
////
bool TestBase::testModule(IGameDef *gamedef)
{
rawstream << "======== Testing module " << getName() << std::endl;
u64 t1 = porting::getTimeMs();
runTests(gamedef);
u64 tdiff = porting::getTimeMs() - t1;
rawstream << "======== Module " << getName() << " "
<< (num_tests_failed ? "failed" : "passed") << " (" << num_tests_failed
<< " failures / " << num_tests_run << " tests) - " << tdiff
<< "ms" << std::endl;
if (!m_test_dir.empty())
fs::RecursiveDelete(m_test_dir);
return num_tests_failed == 0;
}
std::string TestBase::getTestTempDirectory()
{
if (!m_test_dir.empty())
return m_test_dir;
char buf[32];
porting::mt_snprintf(buf, sizeof(buf), "%08X", myrand());
m_test_dir = fs::TempPath() + DIR_DELIM "mttest_" + buf;
if (!fs::CreateDir(m_test_dir))
throw TestFailedException();
return m_test_dir;
}
std::string TestBase::getTestTempFile()
{
char buf[32];
porting::mt_snprintf(buf, sizeof(buf), "%08X", myrand());
return getTestTempDirectory() + DIR_DELIM + buf + ".tmp";
}
/*
NOTE: These tests became non-working then NodeContainer was removed.
These should be redone, utilizing some kind of a virtual
interface for Map (IMap would be fine).
*/
#if 0
struct TestMapBlock: public TestBase
{
class TC : public NodeContainer
{
public:
MapNode node;
bool position_valid;
core::list<v3s16> validity_exceptions;
TC()
{
position_valid = true;
}
virtual bool isValidPosition(v3s16 p)
{
//return position_valid ^ (p==position_valid_exception);
bool exception = false;
for(core::list<v3s16>::Iterator i=validity_exceptions.begin();
i != validity_exceptions.end(); i++)
{
if(p == *i)
{
exception = true;
break;
}
}
return exception ? !position_valid : position_valid;
}
virtual MapNode getNode(v3s16 p)
{
if(isValidPosition(p) == false)
throw InvalidPositionException();
return node;
}
virtual void setNode(v3s16 p, MapNode & n)
{
if(isValidPosition(p) == false)
throw InvalidPositionException();
};
virtual u16 nodeContainerId() const
{
return 666;
}
};
void Run()
{
TC parent;
MapBlock b(&parent, v3s16(1,1,1));
v3s16 relpos(MAP_BLOCKSIZE, MAP_BLOCKSIZE, MAP_BLOCKSIZE);
UASSERT(b.getPosRelative() == relpos);
UASSERT(b.getBox().MinEdge.X == MAP_BLOCKSIZE);
UASSERT(b.getBox().MaxEdge.X == MAP_BLOCKSIZE*2-1);
UASSERT(b.getBox().MinEdge.Y == MAP_BLOCKSIZE);
UASSERT(b.getBox().MaxEdge.Y == MAP_BLOCKSIZE*2-1);
UASSERT(b.getBox().MinEdge.Z == MAP_BLOCKSIZE);
UASSERT(b.getBox().MaxEdge.Z == MAP_BLOCKSIZE*2-1);
UASSERT(b.isValidPosition(v3s16(0,0,0)) == true);
UASSERT(b.isValidPosition(v3s16(-1,0,0)) == false);
UASSERT(b.isValidPosition(v3s16(-1,-142,-2341)) == false);
UASSERT(b.isValidPosition(v3s16(-124,142,2341)) == false);
UASSERT(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true);
UASSERT(b.isValidPosition(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE,MAP_BLOCKSIZE-1)) == false);
/*
TODO: this method should probably be removed
if the block size isn't going to be set variable
*/
/*UASSERT(b.getSizeNodes() == v3s16(MAP_BLOCKSIZE,
MAP_BLOCKSIZE, MAP_BLOCKSIZE));*/
// Changed flag should be initially set
UASSERT(b.getModified() == MOD_STATE_WRITE_NEEDED);
b.resetModified();
UASSERT(b.getModified() == MOD_STATE_CLEAN);
// All nodes should have been set to
// .d=CONTENT_IGNORE and .getLight() = 0
for(u16 z=0; z<MAP_BLOCKSIZE; z++)
for(u16 y=0; y<MAP_BLOCKSIZE; y++)
for(u16 x=0; x<MAP_BLOCKSIZE; x++)
{
//UASSERT(b.getNode(v3s16(x,y,z)).getContent() == CONTENT_AIR);
UASSERT(b.getNode(v3s16(x,y,z)).getContent() == CONTENT_IGNORE);
UASSERT(b.getNode(v3s16(x,y,z)).getLight(LIGHTBANK_DAY) == 0);
UASSERT(b.getNode(v3s16(x,y,z)).getLight(LIGHTBANK_NIGHT) == 0);
}
{
MapNode n(CONTENT_AIR);
for(u16 z=0; z<MAP_BLOCKSIZE; z++)
for(u16 y=0; y<MAP_BLOCKSIZE; y++)
for(u16 x=0; x<MAP_BLOCKSIZE; x++)
{
b.setNode(v3s16(x,y,z), n);
}
}
/*
Parent fetch functions
*/
parent.position_valid = false;
parent.node.setContent(5);
MapNode n;
// Positions in the block should still be valid
UASSERT(b.isValidPositionParent(v3s16(0,0,0)) == true);
UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1)) == true);
n = b.getNodeParent(v3s16(0,MAP_BLOCKSIZE-1,0));
UASSERT(n.getContent() == CONTENT_AIR);
// ...but outside the block they should be invalid
UASSERT(b.isValidPositionParent(v3s16(-121,2341,0)) == false);
UASSERT(b.isValidPositionParent(v3s16(-1,0,0)) == false);
UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == false);
{
bool exception_thrown = false;
try{
// This should throw an exception
MapNode n = b.getNodeParent(v3s16(0,0,-1));
}
catch(InvalidPositionException &e)
{
exception_thrown = true;
}
UASSERT(exception_thrown);
}
parent.position_valid = true;
// Now the positions outside should be valid
UASSERT(b.isValidPositionParent(v3s16(-121,2341,0)) == true);
UASSERT(b.isValidPositionParent(v3s16(-1,0,0)) == true);
UASSERT(b.isValidPositionParent(v3s16(MAP_BLOCKSIZE-1,MAP_BLOCKSIZE-1,MAP_BLOCKSIZE)) == true);
n = b.getNodeParent(v3s16(0,0,MAP_BLOCKSIZE));
UASSERT(n.getContent() == 5);
/*
Set a node
*/
v3s16 p(1,2,0);
n.setContent(4);
b.setNode(p, n);
UASSERT(b.getNode(p).getContent() == 4);
//TODO: Update to new system
/*UASSERT(b.getNodeTile(p) == 4);
UASSERT(b.getNodeTile(v3s16(-1,-1,0)) == 5);*/
/*
propagateSunlight()
*/
// Set lighting of all nodes to 0
for(u16 z=0; z<MAP_BLOCKSIZE; z++){
for(u16 y=0; y<MAP_BLOCKSIZE; y++){
for(u16 x=0; x<MAP_BLOCKSIZE; x++){
MapNode n = b.getNode(v3s16(x,y,z));
n.setLight(LIGHTBANK_DAY, 0);
n.setLight(LIGHTBANK_NIGHT, 0);
b.setNode(v3s16(x,y,z), n);
}
}
}
{
/*
Check how the block handles being a lonely sky block
*/
parent.position_valid = true;
b.setIsUnderground(false);
parent.node.setContent(CONTENT_AIR);
parent.node.setLight(LIGHTBANK_DAY, LIGHT_SUN);
parent.node.setLight(LIGHTBANK_NIGHT, 0);
core::map<v3s16, bool> light_sources;
// The bottom block is invalid, because we have a shadowing node
UASSERT(b.propagateSunlight(light_sources) == false);
UASSERT(b.getNode(v3s16(1,4,0)).getLight(LIGHTBANK_DAY) == LIGHT_SUN);
UASSERT(b.getNode(v3s16(1,3,0)).getLight(LIGHTBANK_DAY) == LIGHT_SUN);
UASSERT(b.getNode(v3s16(1,2,0)).getLight(LIGHTBANK_DAY) == 0);
UASSERT(b.getNode(v3s16(1,1,0)).getLight(LIGHTBANK_DAY) == 0);
UASSERT(b.getNode(v3s16(1,0,0)).getLight(LIGHTBANK_DAY) == 0);
UASSERT(b.getNode(v3s16(1,2,3)).getLight(LIGHTBANK_DAY) == LIGHT_SUN);
UASSERT(b.getFaceLight2(1000, p, v3s16(0,1,0)) == LIGHT_SUN);
UASSERT(b.getFaceLight2(1000, p, v3s16(0,-1,0)) == 0);
UASSERT(b.getFaceLight2(0, p, v3s16(0,-1,0)) == 0);
// According to MapBlock::getFaceLight,
// The face on the z+ side should have double-diminished light
//UASSERT(b.getFaceLight(p, v3s16(0,0,1)) == diminish_light(diminish_light(LIGHT_MAX)));
// The face on the z+ side should have diminished light
UASSERT(b.getFaceLight2(1000, p, v3s16(0,0,1)) == diminish_light(LIGHT_MAX));
}
/*
Check how the block handles being in between blocks with some non-sunlight
while being underground
*/
{
// Make neighbours to exist and set some non-sunlight to them
parent.position_valid = true;
b.setIsUnderground(true);
parent.node.setLight(LIGHTBANK_DAY, LIGHT_MAX/2);
core::map<v3s16, bool> light_sources;
// The block below should be valid because there shouldn't be
// sunlight in there either
UASSERT(b.propagateSunlight(light_sources, true) == true);
// Should not touch nodes that are not affected (that is, all of them)
//UASSERT(b.getNode(v3s16(1,2,3)).getLight() == LIGHT_SUN);
// Should set light of non-sunlighted blocks to 0.
UASSERT(b.getNode(v3s16(1,2,3)).getLight(LIGHTBANK_DAY) == 0);
}
/*
Set up a situation where:
- There is only air in this block
- There is a valid non-sunlighted block at the bottom, and
- Invalid blocks elsewhere.
- the block is not underground.
This should result in bottom block invalidity
*/
{
b.setIsUnderground(false);
// Clear block
for(u16 z=0; z<MAP_BLOCKSIZE; z++){
for(u16 y=0; y<MAP_BLOCKSIZE; y++){
for(u16 x=0; x<MAP_BLOCKSIZE; x++){
MapNode n;
n.setContent(CONTENT_AIR);
n.setLight(LIGHTBANK_DAY, 0);
b.setNode(v3s16(x,y,z), n);
}
}
}
// Make neighbours invalid
parent.position_valid = false;
// Add exceptions to the top of the bottom block
for(u16 x=0; x<MAP_BLOCKSIZE; x++)
for(u16 z=0; z<MAP_BLOCKSIZE; z++)
{
parent.validity_exceptions.push_back(v3s16(MAP_BLOCKSIZE+x, MAP_BLOCKSIZE-1, MAP_BLOCKSIZE+z));
}
// Lighting value for the valid nodes
parent.node.setLight(LIGHTBANK_DAY, LIGHT_MAX/2);
core::map<v3s16, bool> light_sources;
// Bottom block is not valid
UASSERT(b.propagateSunlight(light_sources) == false);
}
}
};
struct TestMapSector: public TestBase
{
class TC : public NodeContainer
{
public:
MapNode node;
bool position_valid;
TC()
{
position_valid = true;
}
virtual bool isValidPosition(v3s16 p)
{
return position_valid;
}
virtual MapNode getNode(v3s16 p)
{
if(position_valid == false)
throw InvalidPositionException();
return node;
}
virtual void setNode(v3s16 p, MapNode & n)
{
if(position_valid == false)
throw InvalidPositionException();
};
virtual u16 nodeContainerId() const
{
return 666;
}
};
void Run()
{
TC parent;
parent.position_valid = false;
// Create one with no heightmaps
ServerMapSector sector(&parent, v2s16(1,1));
UASSERT(sector.getBlockNoCreateNoEx(0) == nullptr);
UASSERT(sector.getBlockNoCreateNoEx(1) == nullptr);
MapBlock * bref = sector.createBlankBlock(-2);
UASSERT(sector.getBlockNoCreateNoEx(0) == nullptr);
UASSERT(sector.getBlockNoCreateNoEx(-2) == bref);
//TODO: Check for AlreadyExistsException
/*bool exception_thrown = false;
try{
sector.getBlock(0);
}
catch(InvalidPositionException &e){
exception_thrown = true;
}
UASSERT(exception_thrown);*/
}
};
#endif

142
src/unittest/test.h Normal file
View File

@ -0,0 +1,142 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#pragma once
#include <exception>
#include <vector>
#include "irrlichttypes_extrabloated.h"
#include "porting.h"
#include "filesys.h"
#include "mapnode.h"
class TestFailedException : public std::exception {
};
// Runs a unit test and reports results
#define TEST(fxn, ...) { \
u64 t1 = porting::getTimeMs(); \
try { \
fxn(__VA_ARGS__); \
rawstream << "[PASS] "; \
} catch (TestFailedException &e) { \
rawstream << "[FAIL] "; \
num_tests_failed++; \
} catch (std::exception &e) { \
rawstream << "Caught unhandled exception: " << e.what() << std::endl; \
rawstream << "[FAIL] "; \
num_tests_failed++; \
} \
num_tests_run++; \
u64 tdiff = porting::getTimeMs() - t1; \
rawstream << #fxn << " - " << tdiff << "ms" << std::endl; \
}
// Asserts the specified condition is true, or fails the current unit test
#define UASSERT(x) \
if (!(x)) { \
rawstream << "Test assertion failed: " #x << std::endl \
<< " at " << fs::GetFilenameFromPath(__FILE__) \
<< ":" << __LINE__ << std::endl; \
throw TestFailedException(); \
}
// Asserts the specified condition is true, or fails the current unit test
// and prints the format specifier fmt
#define UTEST(x, fmt, ...) \
if (!(x)) { \
char utest_buf[1024]; \
snprintf(utest_buf, sizeof(utest_buf), fmt, __VA_ARGS__); \
rawstream << "Test assertion failed: " << utest_buf << std::endl \
<< " at " << fs::GetFilenameFromPath(__FILE__) \
<< ":" << __LINE__ << std::endl; \
throw TestFailedException(); \
}
// Asserts the comparison specified by CMP is true, or fails the current unit test
#define UASSERTCMP(T, CMP, actual, expected) { \
T a = (actual); \
T e = (expected); \
if (!(a CMP e)) { \
rawstream \
<< "Test assertion failed: " << #actual << " " << #CMP << " " \
<< #expected << std::endl \
<< " at " << fs::GetFilenameFromPath(__FILE__) << ":" \
<< __LINE__ << std::endl \
<< " actual: " << a << std::endl << " expected: " \
<< e << std::endl; \
throw TestFailedException(); \
} \
}
#define UASSERTEQ(T, actual, expected) UASSERTCMP(T, ==, actual, expected)
// UASSERTs that the specified exception occurs
#define EXCEPTION_CHECK(EType, code) { \
bool exception_thrown = false; \
try { \
code; \
} catch (EType &e) { \
exception_thrown = true; \
} \
UASSERT(exception_thrown); \
}
class IGameDef;
class TestBase {
public:
bool testModule(IGameDef *gamedef);
std::string getTestTempDirectory();
std::string getTestTempFile();
virtual void runTests(IGameDef *gamedef) = 0;
virtual const char *getName() = 0;
u32 num_tests_failed;
u32 num_tests_run;
private:
std::string m_test_dir;
};
class TestManager {
public:
static std::vector<TestBase *> &getTestModules()
{
static std::vector<TestBase *> m_modules_to_test;
return m_modules_to_test;
}
static void registerTestModule(TestBase *module)
{
getTestModules().push_back(module);
}
};
// A few item and node definitions for those tests that need them
extern content_t t_CONTENT_STONE;
extern content_t t_CONTENT_GRASS;
extern content_t t_CONTENT_TORCH;
extern content_t t_CONTENT_WATER;
extern content_t t_CONTENT_LAVA;
extern content_t t_CONTENT_BRICK;
bool run_tests();

View File

@ -0,0 +1,60 @@
/*
Minetest
Copyright (C) 2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "activeobject.h"
class TestActiveObject : public TestBase
{
public:
TestActiveObject() { TestManager::registerTestModule(this); }
const char *getName() { return "TestActiveObject"; }
void runTests(IGameDef *gamedef);
void testAOAttributes();
};
static TestActiveObject g_test_instance;
void TestActiveObject::runTests(IGameDef *gamedef)
{
TEST(testAOAttributes);
}
class TestAO : public ActiveObject
{
public:
TestAO(u16 id) : ActiveObject(id) {}
virtual ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_TEST; }
virtual bool getCollisionBox(aabb3f *toset) const { return false; }
virtual bool getSelectionBox(aabb3f *toset) const { return false; }
virtual bool collideWithObjects() const { return false; }
};
void TestActiveObject::testAOAttributes()
{
TestAO ao(44);
UASSERT(ao.getId() == 44);
ao.setId(558);
UASSERT(ao.getId() == 558);
}

View File

@ -0,0 +1,67 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "log.h"
#include "settings.h"
#include "network/socket.h"
class TestAddress : public TestBase
{
public:
TestAddress() { TestManager::registerTestModule(this); }
const char *getName() { return "TestAddress"; }
void runTests(IGameDef *gamedef);
void testIsLocalhost();
};
static TestAddress g_test_instance;
void TestAddress::runTests(IGameDef *gamedef)
{
TEST(testIsLocalhost);
}
void TestAddress::testIsLocalhost()
{
// v4
UASSERT(Address(127, 0, 0, 1, 0).isLocalhost());
UASSERT(Address(127, 254, 12, 99, 0).isLocalhost());
UASSERT(Address(127, 188, 255, 247, 0).isLocalhost());
UASSERT(!Address(126, 255, 255, 255, 0).isLocalhost());
UASSERT(!Address(128, 0, 0, 0, 0).isLocalhost());
UASSERT(!Address(1, 0, 0, 0, 0).isLocalhost());
UASSERT(!Address(255, 255, 255, 255, 0).isLocalhost());
UASSERT(!Address(36, 45, 99, 158, 0).isLocalhost());
UASSERT(!Address(172, 45, 37, 68, 0).isLocalhost());
// v6
std::unique_ptr<IPv6AddressBytes> ipv6Bytes(new IPv6AddressBytes());
std::vector<u8> ipv6RawAddr = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1};
memcpy(ipv6Bytes->bytes, &ipv6RawAddr[0], 16);
UASSERT(Address(ipv6Bytes.get(), 0).isLocalhost())
ipv6RawAddr = {16, 34, 0, 0, 0, 0, 29, 0, 0, 0, 188, 0, 0, 0, 0, 14};
memcpy(ipv6Bytes->bytes, &ipv6RawAddr[0], 16);
UASSERT(!Address(ipv6Bytes.get(), 0).isLocalhost())
}

View File

@ -0,0 +1,173 @@
/*
Minetest
Copyright (C) 2015 est31, <MTest31@outlook.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "util/areastore.h"
class TestAreaStore : public TestBase {
public:
TestAreaStore() { TestManager::registerTestModule(this); }
const char *getName() { return "TestAreaStore"; }
void runTests(IGameDef *gamedef);
void genericStoreTest(AreaStore *store);
void testVectorStore();
void testSpatialStore();
void testSerialization();
};
static TestAreaStore g_test_instance;
void TestAreaStore::runTests(IGameDef *gamedef)
{
TEST(testVectorStore);
#if USE_SPATIAL
TEST(testSpatialStore);
#endif
TEST(testSerialization);
}
////////////////////////////////////////////////////////////////////////////////
void TestAreaStore::testVectorStore()
{
VectorAreaStore store;
genericStoreTest(&store);
}
void TestAreaStore::testSpatialStore()
{
#if USE_SPATIAL
SpatialAreaStore store;
genericStoreTest(&store);
#endif
}
void TestAreaStore::genericStoreTest(AreaStore *store)
{
Area a(v3s16(-10, -3, 5), v3s16(0, 29, 7));
Area b(v3s16(-5, -2, 5), v3s16(0, 28, 6));
Area c(v3s16(-7, -3, 6), v3s16(-1, 27, 7));
std::vector<Area *> res;
UASSERTEQ(size_t, store->size(), 0);
store->reserve(2); // sic
store->insertArea(&a);
store->insertArea(&b);
store->insertArea(&c);
UASSERTEQ(size_t, store->size(), 3);
store->getAreasForPos(&res, v3s16(-1, 0, 6));
UASSERTEQ(size_t, res.size(), 3);
res.clear();
store->getAreasForPos(&res, v3s16(0, 0, 7));
UASSERTEQ(size_t, res.size(), 1);
res.clear();
store->removeArea(a.id);
store->getAreasForPos(&res, v3s16(0, 0, 7));
UASSERTEQ(size_t, res.size(), 0);
res.clear();
store->insertArea(&a);
store->getAreasForPos(&res, v3s16(0, 0, 7));
UASSERTEQ(size_t, res.size(), 1);
res.clear();
store->getAreasInArea(&res, v3s16(-10, -3, 5), v3s16(0, 29, 7), false);
UASSERTEQ(size_t, res.size(), 3);
res.clear();
store->getAreasInArea(&res, v3s16(-100, 0, 6), v3s16(200, 0, 6), false);
UASSERTEQ(size_t, res.size(), 0);
res.clear();
store->getAreasInArea(&res, v3s16(-100, 0, 6), v3s16(200, 0, 6), true);
UASSERTEQ(size_t, res.size(), 3);
res.clear();
store->removeArea(a.id);
store->removeArea(b.id);
store->removeArea(c.id);
Area d(v3s16(-100, -300, -200), v3s16(-50, -200, -100));
d.data = "Hi!";
store->insertArea(&d);
store->getAreasForPos(&res, v3s16(-75, -250, -150));
UASSERTEQ(size_t, res.size(), 1);
UASSERTEQ(u16, res[0]->data.size(), 3);
UASSERT(strncmp(res[0]->data.c_str(), "Hi!", 3) == 0);
res.clear();
store->removeArea(d.id);
}
void TestAreaStore::testSerialization()
{
VectorAreaStore store;
Area a(v3s16(-1, 0, 1), v3s16(0, 1, 2));
a.data = "Area AA";
store.insertArea(&a);
Area b(v3s16(123, 456, 789), v3s16(32000, 100, 10));
b.data = "Area BB";
store.insertArea(&b);
std::ostringstream os;
store.serialize(os);
std::string str = os.str();
std::string str_wanted("\x00" // Version
"\x00\x02" // Count
"\xFF\xFF\x00\x00\x00\x01" // Area A min edge
"\x00\x00\x00\x01\x00\x02" // Area A max edge
"\x00\x07" // Area A data length
"Area AA" // Area A data
"\x00\x7B\x00\x64\x00\x0A" // Area B min edge (last two swapped with max edge for sorting)
"\x7D\x00\x01\xC8\x03\x15" // Area B max edge (^)
"\x00\x07" // Area B data length
"Area BB" // Area B data
"\x00\x00\x00\x00" // ID A = 0
"\x00\x00\x00\x01", // ID B = 1
1 + 2 +
(6 + 6 + 2 + 7) * 2 + // min/max edge, length, data
2 * 4); // Area IDs
UASSERTEQ(const std::string &, str, str_wanted);
std::istringstream is(str);
store.deserialize(is);
// deserialize() doesn't clear the store
// But existing IDs are overridden
UASSERTEQ(size_t, store.size(), 2);
Area c(v3s16(33, -2, -6), v3s16(4, 77, -76));
c.data = "Area CC";
store.insertArea(&c);
UASSERTEQ(u32, c.id, 2);
}

View File

@ -0,0 +1,296 @@
/*
Minetest
Copyright (C) 2018 bendeutsch, Ben Deutsch <ben@bendeutsch.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <algorithm>
#include "database/database-files.h"
#include "database/database-sqlite3.h"
#include "util/string.h"
#include "filesys.h"
namespace
{
// Anonymous namespace to create classes that are only
// visible to this file
//
// These are helpers that return a *AuthDatabase and
// allow us to run the same tests on different databases and
// database acquisition strategies.
class AuthDatabaseProvider
{
public:
virtual ~AuthDatabaseProvider() = default;
virtual AuthDatabase *getAuthDatabase() = 0;
};
class FixedProvider : public AuthDatabaseProvider
{
public:
FixedProvider(AuthDatabase *auth_db) : auth_db(auth_db){};
virtual ~FixedProvider(){};
virtual AuthDatabase *getAuthDatabase() { return auth_db; };
private:
AuthDatabase *auth_db;
};
class FilesProvider : public AuthDatabaseProvider
{
public:
FilesProvider(const std::string &dir) : dir(dir){};
virtual ~FilesProvider() { delete auth_db; };
virtual AuthDatabase *getAuthDatabase()
{
delete auth_db;
auth_db = new AuthDatabaseFiles(dir);
return auth_db;
};
private:
std::string dir;
AuthDatabase *auth_db = nullptr;
};
class SQLite3Provider : public AuthDatabaseProvider
{
public:
SQLite3Provider(const std::string &dir) : dir(dir){};
virtual ~SQLite3Provider() { delete auth_db; };
virtual AuthDatabase *getAuthDatabase()
{
delete auth_db;
auth_db = new AuthDatabaseSQLite3(dir);
return auth_db;
};
private:
std::string dir;
AuthDatabase *auth_db = nullptr;
};
}
class TestAuthDatabase : public TestBase
{
public:
TestAuthDatabase() { TestManager::registerTestModule(this); }
const char *getName() { return "TestAuthDatabase"; }
void runTests(IGameDef *gamedef);
void runTestsForCurrentDB();
void testRecallFail();
void testCreate();
void testRecall();
void testChange();
void testRecallChanged();
void testChangePrivileges();
void testRecallChangedPrivileges();
void testListNames();
void testDelete();
private:
AuthDatabaseProvider *auth_provider;
};
static TestAuthDatabase g_test_instance;
void TestAuthDatabase::runTests(IGameDef *gamedef)
{
// fixed directory, for persistence
thread_local const std::string test_dir = getTestTempDirectory();
// Each set of tests is run twice for each database type:
// one where we reuse the same AuthDatabase object (to test local caching),
// and one where we create a new AuthDatabase object for each call
// (to test actual persistence).
rawstream << "-------- Files database (same object)" << std::endl;
AuthDatabase *auth_db = new AuthDatabaseFiles(test_dir);
auth_provider = new FixedProvider(auth_db);
runTestsForCurrentDB();
delete auth_db;
delete auth_provider;
// reset database
fs::DeleteSingleFileOrEmptyDirectory(test_dir + DIR_DELIM + "auth.txt");
rawstream << "-------- Files database (new objects)" << std::endl;
auth_provider = new FilesProvider(test_dir);
runTestsForCurrentDB();
delete auth_provider;
rawstream << "-------- SQLite3 database (same object)" << std::endl;
auth_db = new AuthDatabaseSQLite3(test_dir);
auth_provider = new FixedProvider(auth_db);
runTestsForCurrentDB();
delete auth_db;
delete auth_provider;
// reset database
fs::DeleteSingleFileOrEmptyDirectory(test_dir + DIR_DELIM + "auth.sqlite");
rawstream << "-------- SQLite3 database (new objects)" << std::endl;
auth_provider = new SQLite3Provider(test_dir);
runTestsForCurrentDB();
delete auth_provider;
}
////////////////////////////////////////////////////////////////////////////////
void TestAuthDatabase::runTestsForCurrentDB()
{
TEST(testRecallFail);
TEST(testCreate);
TEST(testRecall);
TEST(testChange);
TEST(testRecallChanged);
TEST(testChangePrivileges);
TEST(testRecallChangedPrivileges);
TEST(testListNames);
TEST(testDelete);
TEST(testRecallFail);
}
void TestAuthDatabase::testRecallFail()
{
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
AuthEntry authEntry;
// no such user yet
UASSERT(!auth_db->getAuth("TestName", authEntry));
}
void TestAuthDatabase::testCreate()
{
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
AuthEntry authEntry;
authEntry.name = "TestName";
authEntry.password = "TestPassword";
authEntry.privileges.emplace_back("shout");
authEntry.privileges.emplace_back("interact");
authEntry.last_login = 1000;
UASSERT(auth_db->createAuth(authEntry));
}
void TestAuthDatabase::testRecall()
{
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
AuthEntry authEntry;
UASSERT(auth_db->getAuth("TestName", authEntry));
UASSERTEQ(std::string, authEntry.name, "TestName");
UASSERTEQ(std::string, authEntry.password, "TestPassword");
// the order of privileges is unimportant
std::sort(authEntry.privileges.begin(), authEntry.privileges.end());
UASSERTEQ(std::string, str_join(authEntry.privileges, ","), "interact,shout");
}
void TestAuthDatabase::testChange()
{
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
AuthEntry authEntry;
UASSERT(auth_db->getAuth("TestName", authEntry));
authEntry.password = "NewPassword";
authEntry.last_login = 1002;
UASSERT(auth_db->saveAuth(authEntry));
}
void TestAuthDatabase::testRecallChanged()
{
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
AuthEntry authEntry;
UASSERT(auth_db->getAuth("TestName", authEntry));
UASSERTEQ(std::string, authEntry.password, "NewPassword");
// the order of privileges is unimportant
std::sort(authEntry.privileges.begin(), authEntry.privileges.end());
UASSERTEQ(std::string, str_join(authEntry.privileges, ","), "interact,shout");
UASSERTEQ(u64, authEntry.last_login, 1002);
}
void TestAuthDatabase::testChangePrivileges()
{
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
AuthEntry authEntry;
UASSERT(auth_db->getAuth("TestName", authEntry));
authEntry.privileges.clear();
authEntry.privileges.emplace_back("interact");
authEntry.privileges.emplace_back("fly");
authEntry.privileges.emplace_back("dig");
UASSERT(auth_db->saveAuth(authEntry));
}
void TestAuthDatabase::testRecallChangedPrivileges()
{
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
AuthEntry authEntry;
UASSERT(auth_db->getAuth("TestName", authEntry));
// the order of privileges is unimportant
std::sort(authEntry.privileges.begin(), authEntry.privileges.end());
UASSERTEQ(std::string, str_join(authEntry.privileges, ","), "dig,fly,interact");
}
void TestAuthDatabase::testListNames()
{
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
std::vector<std::string> list;
AuthEntry authEntry;
authEntry.name = "SecondName";
authEntry.password = "SecondPassword";
authEntry.privileges.emplace_back("shout");
authEntry.privileges.emplace_back("interact");
authEntry.last_login = 1003;
auth_db->createAuth(authEntry);
auth_db->listNames(list);
// not necessarily sorted, so sort before comparing
std::sort(list.begin(), list.end());
UASSERTEQ(std::string, str_join(list, ","), "SecondName,TestName");
}
void TestAuthDatabase::testDelete()
{
AuthDatabase *auth_db = auth_provider->getAuthDatabase();
UASSERT(!auth_db->deleteAuth("NoSuchName"));
UASSERT(auth_db->deleteAuth("TestName"));
// second try, expect failure
UASSERT(!auth_db->deleteAuth("TestName"));
}

170
src/unittest/test_ban.cpp Normal file
View File

@ -0,0 +1,170 @@
/*
Minetest
Copyright (C) 2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "ban.h"
class TestBan : public TestBase
{
public:
TestBan() { TestManager::registerTestModule(this); }
const char *getName() { return "TestBan"; }
void runTests(IGameDef *gamedef);
private:
void testCreate();
void testAdd();
void testRemove();
void testModificationFlag();
void testGetBanName();
void testGetBanDescription();
void reinitTestEnv();
};
static TestBan g_test_instance;
void TestBan::runTests(IGameDef *gamedef)
{
reinitTestEnv();
TEST(testCreate);
reinitTestEnv();
TEST(testAdd);
reinitTestEnv();
TEST(testRemove);
reinitTestEnv();
TEST(testModificationFlag);
reinitTestEnv();
TEST(testGetBanName);
reinitTestEnv();
TEST(testGetBanDescription);
// Delete leftover files
reinitTestEnv();
}
// This module is stateful due to disk writes, add helper to remove files
void TestBan::reinitTestEnv()
{
fs::DeleteSingleFileOrEmptyDirectory("testbm.txt");
fs::DeleteSingleFileOrEmptyDirectory("testbm2.txt");
}
void TestBan::testCreate()
{
// test save on object removal
{
BanManager bm("testbm.txt");
}
UASSERT(std::ifstream("testbm.txt", std::ios::binary).is_open());
// test manual save
{
BanManager bm("testbm2.txt");
bm.save();
UASSERT(std::ifstream("testbm2.txt", std::ios::binary).is_open());
}
}
void TestBan::testAdd()
{
std::string bm_test1_entry = "192.168.0.246";
std::string bm_test1_result = "test_username";
BanManager bm("testbm.txt");
bm.add(bm_test1_entry, bm_test1_result);
UASSERT(bm.getBanName(bm_test1_entry) == bm_test1_result);
}
void TestBan::testRemove()
{
std::string bm_test1_entry = "192.168.0.249";
std::string bm_test1_result = "test_username";
std::string bm_test2_entry = "192.168.0.250";
std::string bm_test2_result = "test_username7";
BanManager bm("testbm.txt");
// init data
bm.add(bm_test1_entry, bm_test1_result);
bm.add(bm_test2_entry, bm_test2_result);
// the test
bm.remove(bm_test1_entry);
UASSERT(bm.getBanName(bm_test1_entry).empty());
bm.remove(bm_test2_result);
UASSERT(bm.getBanName(bm_test2_result).empty());
}
void TestBan::testModificationFlag()
{
BanManager bm("testbm.txt");
bm.add("192.168.0.247", "test_username");
UASSERT(bm.isModified());
bm.remove("192.168.0.247");
UASSERT(bm.isModified());
// Clear the modification flag
bm.save();
// Test modification flag is entry was not present
bm.remove("test_username");
UASSERT(!bm.isModified());
}
void TestBan::testGetBanName()
{
std::string bm_test1_entry = "192.168.0.247";
std::string bm_test1_result = "test_username";
BanManager bm("testbm.txt");
bm.add(bm_test1_entry, bm_test1_result);
// Test with valid entry
UASSERT(bm.getBanName(bm_test1_entry) == bm_test1_result);
// Test with invalid entry
UASSERT(bm.getBanName("---invalid---").empty());
}
void TestBan::testGetBanDescription()
{
std::string bm_test1_entry = "192.168.0.247";
std::string bm_test1_entry2 = "test_username";
std::string bm_test1_result = "192.168.0.247|test_username";
BanManager bm("testbm.txt");
bm.add(bm_test1_entry, bm_test1_entry2);
UASSERT(bm.getBanDescription(bm_test1_entry) == bm_test1_result);
UASSERT(bm.getBanDescription(bm_test1_entry2) == bm_test1_result);
}

View File

@ -0,0 +1,117 @@
/*
Minetest
Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "client/activeobjectmgr.h"
#include <algorithm>
#include "test.h"
#include "profiler.h"
class TestClientActiveObject : public ClientActiveObject
{
public:
TestClientActiveObject() : ClientActiveObject(0, nullptr, nullptr) {}
~TestClientActiveObject() = default;
ActiveObjectType getType() const { return ACTIVEOBJECT_TYPE_TEST; }
};
class TestClientActiveObjectMgr : public TestBase
{
public:
TestClientActiveObjectMgr() { TestManager::registerTestModule(this); }
const char *getName() { return "TestClientActiveObjectMgr"; }
void runTests(IGameDef *gamedef);
void testFreeID();
void testRegisterObject();
void testRemoveObject();
};
static TestClientActiveObjectMgr g_test_instance;
void TestClientActiveObjectMgr::runTests(IGameDef *gamedef)
{
TEST(testFreeID);
TEST(testRegisterObject)
TEST(testRemoveObject)
}
////////////////////////////////////////////////////////////////////////////////
void TestClientActiveObjectMgr::testFreeID()
{
client::ActiveObjectMgr caomgr;
std::vector<u16> aoids;
u16 aoid = caomgr.getFreeId();
// Ensure it's not the same id
UASSERT(caomgr.getFreeId() != aoid);
aoids.push_back(aoid);
// Register basic objects, ensure we never found
for (u8 i = 0; i < UINT8_MAX; i++) {
// Register an object
auto tcao = new TestClientActiveObject();
caomgr.registerObject(tcao);
aoids.push_back(tcao->getId());
// Ensure next id is not in registered list
UASSERT(std::find(aoids.begin(), aoids.end(), caomgr.getFreeId()) ==
aoids.end());
}
caomgr.clear();
}
void TestClientActiveObjectMgr::testRegisterObject()
{
client::ActiveObjectMgr caomgr;
auto tcao = new TestClientActiveObject();
UASSERT(caomgr.registerObject(tcao));
u16 id = tcao->getId();
auto tcaoToCompare = caomgr.getActiveObject(id);
UASSERT(tcaoToCompare->getId() == id);
UASSERT(tcaoToCompare == tcao);
tcao = new TestClientActiveObject();
UASSERT(caomgr.registerObject(tcao));
UASSERT(caomgr.getActiveObject(tcao->getId()) == tcao);
UASSERT(caomgr.getActiveObject(tcao->getId()) != tcaoToCompare);
caomgr.clear();
}
void TestClientActiveObjectMgr::testRemoveObject()
{
client::ActiveObjectMgr caomgr;
auto tcao = new TestClientActiveObject();
UASSERT(caomgr.registerObject(tcao));
u16 id = tcao->getId();
UASSERT(caomgr.getActiveObject(id) != nullptr)
caomgr.removeObject(tcao->getId());
UASSERT(caomgr.getActiveObject(id) == nullptr)
caomgr.clear();
}

View File

@ -0,0 +1,180 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "collision.h"
class TestCollision : public TestBase {
public:
TestCollision() { TestManager::registerTestModule(this); }
const char *getName() { return "TestCollision"; }
void runTests(IGameDef *gamedef);
void testAxisAlignedCollision();
};
static TestCollision g_test_instance;
void TestCollision::runTests(IGameDef *gamedef)
{
TEST(testAxisAlignedCollision);
}
////////////////////////////////////////////////////////////////////////////////
void TestCollision::testAxisAlignedCollision()
{
for (s16 bx = -3; bx <= 3; bx++)
for (s16 by = -3; by <= 3; by++)
for (s16 bz = -3; bz <= 3; bz++) {
// X-
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx-2, by, bz, bx-1, by+1, bz+1);
v3f v(1, 0, 0);
f32 dtime = 1.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 1.000) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx-2, by, bz, bx-1, by+1, bz+1);
v3f v(-1, 0, 0);
f32 dtime = 1.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == -1);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx-2, by+1.5, bz, bx-1, by+2.5, bz-1);
v3f v(1, 0, 0);
f32 dtime = 1.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == -1);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx-2, by-1.5, bz, bx-1.5, by+0.5, bz+1);
v3f v(0.5, 0.1, 0);
f32 dtime = 3.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 3.000) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx-2, by-1.5, bz, bx-1.5, by+0.5, bz+1);
v3f v(0.5, 0.1, 0);
f32 dtime = 3.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 3.000) < 0.001);
}
// X+
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx+2, by, bz, bx+3, by+1, bz+1);
v3f v(-1, 0, 0);
f32 dtime = 1.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 1.000) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx+2, by, bz, bx+3, by+1, bz+1);
v3f v(1, 0, 0);
f32 dtime = 1.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == -1);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx+2, by, bz+1.5, bx+3, by+1, bz+3.5);
v3f v(-1, 0, 0);
f32 dtime = 1.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == -1);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx+2, by-1.5, bz, bx+2.5, by-0.5, bz+1);
v3f v(-0.5, 0.2, 0);
f32 dtime = 2.5f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 1); // Y, not X!
UASSERT(fabs(dtime - 2.500) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+1, by+1, bz+1);
aabb3f m(bx+2, by-1.5, bz, bx+2.5, by-0.5, bz+1);
v3f v(-0.5, 0.3, 0);
f32 dtime = 2.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 2.000) < 0.001);
}
// TODO: Y-, Y+, Z-, Z+
// misc
{
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx+2.3, by+2.29, bz+2.29, bx+4.2, by+4.2, bz+4.2);
v3f v(-1./3, -1./3, -1./3);
f32 dtime = 1.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 0.9) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx+2.29, by+2.3, bz+2.29, bx+4.2, by+4.2, bz+4.2);
v3f v(-1./3, -1./3, -1./3);
f32 dtime = 1.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 1);
UASSERT(fabs(dtime - 0.9) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx+2.29, by+2.29, bz+2.3, bx+4.2, by+4.2, bz+4.2);
v3f v(-1./3, -1./3, -1./3);
f32 dtime = 1.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 2);
UASSERT(fabs(dtime - 0.9) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.3, by-2.29, bz-2.29);
v3f v(1./7, 1./7, 1./7);
f32 dtime = 17.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 0);
UASSERT(fabs(dtime - 16.1) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.29, by-2.3, bz-2.29);
v3f v(1./7, 1./7, 1./7);
f32 dtime = 17.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 1);
UASSERT(fabs(dtime - 16.1) < 0.001);
}
{
aabb3f s(bx, by, bz, bx+2, by+2, bz+2);
aabb3f m(bx-4.2, by-4.2, bz-4.2, bx-2.29, by-2.29, bz-2.3);
v3f v(1./7, 1./7, 1./7);
f32 dtime = 17.0f;
UASSERT(axisAlignedCollision(s, m, v, &dtime) == 2);
UASSERT(fabs(dtime - 16.1) < 0.001);
}
}
}

View File

@ -0,0 +1,235 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <sstream>
#include "irrlichttypes_extrabloated.h"
#include "log.h"
#include "serialization.h"
#include "nodedef.h"
#include "noise.h"
class TestCompression : public TestBase {
public:
TestCompression() { TestManager::registerTestModule(this); }
const char *getName() { return "TestCompression"; }
void runTests(IGameDef *gamedef);
void testRLECompression();
void testZlibCompression();
void testZlibLargeData();
void testZlibLimit();
void _testZlibLimit(u32 size, u32 limit);
};
static TestCompression g_test_instance;
void TestCompression::runTests(IGameDef *gamedef)
{
TEST(testRLECompression);
TEST(testZlibCompression);
TEST(testZlibLargeData);
TEST(testZlibLimit);
}
////////////////////////////////////////////////////////////////////////////////
void TestCompression::testRLECompression()
{
SharedBuffer<u8> fromdata(4);
fromdata[0]=1;
fromdata[1]=5;
fromdata[2]=5;
fromdata[3]=1;
std::ostringstream os(std::ios_base::binary);
compress(fromdata, os, 0);
std::string str_out = os.str();
infostream << "str_out.size()="<<str_out.size()<<std::endl;
infostream << "TestCompress: 1,5,5,1 -> ";
for (char i : str_out)
infostream << (u32) i << ",";
infostream << std::endl;
UASSERT(str_out.size() == 10);
UASSERT(str_out[0] == 0);
UASSERT(str_out[1] == 0);
UASSERT(str_out[2] == 0);
UASSERT(str_out[3] == 4);
UASSERT(str_out[4] == 0);
UASSERT(str_out[5] == 1);
UASSERT(str_out[6] == 1);
UASSERT(str_out[7] == 5);
UASSERT(str_out[8] == 0);
UASSERT(str_out[9] == 1);
std::istringstream is(str_out, std::ios_base::binary);
std::ostringstream os2(std::ios_base::binary);
decompress(is, os2, 0);
std::string str_out2 = os2.str();
infostream << "decompress: ";
for (char i : str_out2)
infostream << (u32) i << ",";
infostream << std::endl;
UASSERTEQ(size_t, str_out2.size(), fromdata.getSize());
for (u32 i = 0; i < str_out2.size(); i++)
UASSERT(str_out2[i] == fromdata[i]);
}
void TestCompression::testZlibCompression()
{
SharedBuffer<u8> fromdata(4);
fromdata[0]=1;
fromdata[1]=5;
fromdata[2]=5;
fromdata[3]=1;
std::ostringstream os(std::ios_base::binary);
compress(fromdata, os, SER_FMT_VER_HIGHEST_READ);
std::string str_out = os.str();
infostream << "str_out.size()=" << str_out.size() <<std::endl;
infostream << "TestCompress: 1,5,5,1 -> ";
for (char i : str_out)
infostream << (u32) i << ",";
infostream << std::endl;
std::istringstream is(str_out, std::ios_base::binary);
std::ostringstream os2(std::ios_base::binary);
decompress(is, os2, SER_FMT_VER_HIGHEST_READ);
std::string str_out2 = os2.str();
infostream << "decompress: ";
for (char i : str_out2)
infostream << (u32) i << ",";
infostream << std::endl;
UASSERTEQ(size_t, str_out2.size(), fromdata.getSize());
for (u32 i = 0; i < str_out2.size(); i++)
UASSERT(str_out2[i] == fromdata[i]);
}
void TestCompression::testZlibLargeData()
{
infostream << "Test: Testing zlib wrappers with a large amount "
"of pseudorandom data" << std::endl;
u32 size = 50000;
infostream << "Test: Input size of large compressZlib is "
<< size << std::endl;
std::string data_in;
data_in.resize(size);
PseudoRandom pseudorandom(9420);
for (u32 i = 0; i < size; i++)
data_in[i] = pseudorandom.range(0, 255);
std::ostringstream os_compressed(std::ios::binary);
compressZlib(data_in, os_compressed);
infostream << "Test: Output size of large compressZlib is "
<< os_compressed.str().size()<<std::endl;
std::istringstream is_compressed(os_compressed.str(), std::ios::binary);
std::ostringstream os_decompressed(std::ios::binary);
decompressZlib(is_compressed, os_decompressed);
infostream << "Test: Output size of large decompressZlib is "
<< os_decompressed.str().size() << std::endl;
std::string str_decompressed = os_decompressed.str();
UASSERTEQ(size_t, str_decompressed.size(), data_in.size());
for (u32 i = 0; i < size && i < str_decompressed.size(); i++) {
UTEST(str_decompressed[i] == data_in[i],
"index out[%i]=%i differs from in[%i]=%i",
i, str_decompressed[i], i, data_in[i]);
}
}
void TestCompression::testZlibLimit()
{
// edge cases
_testZlibLimit(1024, 1023);
_testZlibLimit(1024, 1024);
_testZlibLimit(1024, 1025);
// test around buffer borders
u32 bufsize = 16384; // as in implementation
for (int s = -1; s <= 1; s++)
{
for (int l = -1; l <= 1; l++)
{
_testZlibLimit(bufsize + s, bufsize + l);
}
}
// span multiple buffers
_testZlibLimit(35000, 22000);
_testZlibLimit(22000, 35000);
}
void TestCompression::_testZlibLimit(u32 size, u32 limit)
{
infostream << "Test: Testing zlib wrappers with a decompression "
"memory limit of " << limit << std::endl;
infostream << "Test: Input size of compressZlib for limit is "
<< size << std::endl;
// how much data we expect to get
u32 expected = size < limit ? size : limit;
// create recognizable data
std::string data_in;
data_in.resize(size);
for (u32 i = 0; i < size; i++)
data_in[i] = (u8)(i % 256);
std::ostringstream os_compressed(std::ios::binary);
compressZlib(data_in, os_compressed);
infostream << "Test: Output size of compressZlib for limit is "
<< os_compressed.str().size()<<std::endl;
std::istringstream is_compressed(os_compressed.str(), std::ios::binary);
std::ostringstream os_decompressed(std::ios::binary);
decompressZlib(is_compressed, os_decompressed, limit);
infostream << "Test: Output size of decompressZlib with limit is "
<< os_decompressed.str().size() << std::endl;
std::string str_decompressed = os_decompressed.str();
UASSERTEQ(size_t, str_decompressed.size(), expected);
for (u32 i = 0; i < size && i < str_decompressed.size(); i++) {
UTEST(str_decompressed[i] == data_in[i],
"index out[%i]=%i differs from in[%i]=%i",
i, str_decompressed[i], i, data_in[i]);
}
}

View File

@ -0,0 +1,6 @@
// Filled in by the build system
#pragma once
#define TEST_WORLDDIR "@TEST_WORLDDIR@"
#define TEST_SUBGAME_PATH "@TEST_SUBGAME_PATH@"

View File

@ -0,0 +1,343 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "log.h"
#include "porting.h"
#include "settings.h"
#include "util/serialize.h"
#include "network/connection.h"
#include "network/networkpacket.h"
#include "network/socket.h"
class TestConnection : public TestBase {
public:
TestConnection()
{
if (INTERNET_SIMULATOR == false)
TestManager::registerTestModule(this);
}
const char *getName() { return "TestConnection"; }
void runTests(IGameDef *gamedef);
void testHelpers();
void testConnectSendReceive();
};
static TestConnection g_test_instance;
void TestConnection::runTests(IGameDef *gamedef)
{
TEST(testHelpers);
TEST(testConnectSendReceive);
}
////////////////////////////////////////////////////////////////////////////////
struct Handler : public con::PeerHandler
{
Handler(const char *a_name) : name(a_name) {}
void peerAdded(con::Peer *peer)
{
infostream << "Handler(" << name << ")::peerAdded(): "
"id=" << peer->id << std::endl;
last_id = peer->id;
count++;
}
void deletingPeer(con::Peer *peer, bool timeout)
{
infostream << "Handler(" << name << ")::deletingPeer(): "
"id=" << peer->id << ", timeout=" << timeout << std::endl;
last_id = peer->id;
count--;
}
s32 count = 0;
u16 last_id = 0;
const char *name;
};
void TestConnection::testHelpers()
{
// Some constants for testing
u32 proto_id = 0x12345678;
session_t peer_id = 123;
u8 channel = 2;
SharedBuffer<u8> data1(1);
data1[0] = 100;
Address a(127,0,0,1, 10);
const u16 seqnum = 34352;
con::BufferedPacket p1 = con::makePacket(a, data1,
proto_id, peer_id, channel);
/*
We should now have a packet with this data:
Header:
[0] u32 protocol_id
[4] session_t sender_peer_id
[6] u8 channel
Data:
[7] u8 data1[0]
*/
UASSERT(readU32(&p1.data[0]) == proto_id);
UASSERT(readU16(&p1.data[4]) == peer_id);
UASSERT(readU8(&p1.data[6]) == channel);
UASSERT(readU8(&p1.data[7]) == data1[0]);
//infostream<<"initial data1[0]="<<((u32)data1[0]&0xff)<<std::endl;
SharedBuffer<u8> p2 = con::makeReliablePacket(data1, seqnum);
/*infostream<<"p2.getSize()="<<p2.getSize()<<", data1.getSize()="
<<data1.getSize()<<std::endl;
infostream<<"readU8(&p2[3])="<<readU8(&p2[3])
<<" p2[3]="<<((u32)p2[3]&0xff)<<std::endl;
infostream<<"data1[0]="<<((u32)data1[0]&0xff)<<std::endl;*/
UASSERT(p2.getSize() == 3 + data1.getSize());
UASSERT(readU8(&p2[0]) == con::PACKET_TYPE_RELIABLE);
UASSERT(readU16(&p2[1]) == seqnum);
UASSERT(readU8(&p2[3]) == data1[0]);
}
void TestConnection::testConnectSendReceive()
{
/*
Test some real connections
NOTE: This mostly tests the legacy interface.
*/
u32 proto_id = 0xad26846a;
Handler hand_server("server");
Handler hand_client("client");
Address address(0, 0, 0, 0, 30001);
Address bind_addr(0, 0, 0, 0, 30001);
/*
* Try to use the bind_address for servers with no localhost address
* For example: FreeBSD jails
*/
std::string bind_str = g_settings->get("bind_address");
try {
bind_addr.Resolve(bind_str.c_str());
if (!bind_addr.isIPv6()) {
address = bind_addr;
}
} catch (ResolveError &e) {
}
infostream << "** Creating server Connection" << std::endl;
con::Connection server(proto_id, 512, 5.0, false, &hand_server);
server.Serve(address);
infostream << "** Creating client Connection" << std::endl;
con::Connection client(proto_id, 512, 5.0, false, &hand_client);
UASSERT(hand_server.count == 0);
UASSERT(hand_client.count == 0);
sleep_ms(50);
Address server_address(127, 0, 0, 1, 30001);
if (address != Address(0, 0, 0, 0, 30001)) {
server_address = bind_addr;
}
infostream << "** running client.Connect()" << std::endl;
client.Connect(server_address);
sleep_ms(50);
// Client should not have added client yet
UASSERT(hand_client.count == 0);
try {
NetworkPacket pkt;
infostream << "** running client.Receive()" << std::endl;
client.Receive(&pkt);
infostream << "** Client received: peer_id=" << pkt.getPeerId()
<< ", size=" << pkt.getSize() << std::endl;
} catch (con::NoIncomingDataException &e) {
}
// Client should have added server now
UASSERT(hand_client.count == 1);
UASSERT(hand_client.last_id == 1);
// Server should not have added client yet
UASSERT(hand_server.count == 0);
sleep_ms(100);
try {
NetworkPacket pkt;
infostream << "** running server.Receive()" << std::endl;
server.Receive(&pkt);
infostream << "** Server received: peer_id=" << pkt.getPeerId()
<< ", size=" << pkt.getSize()
<< std::endl;
} catch (con::NoIncomingDataException &e) {
// No actual data received, but the client has
// probably been connected
}
// Client should be the same
UASSERT(hand_client.count == 1);
UASSERT(hand_client.last_id == 1);
// Server should have the client
UASSERT(hand_server.count == 1);
UASSERT(hand_server.last_id == 2);
//sleep_ms(50);
while (client.Connected() == false) {
try {
NetworkPacket pkt;
infostream << "** running client.Receive()" << std::endl;
client.Receive(&pkt);
infostream << "** Client received: peer_id=" << pkt.getPeerId()
<< ", size=" << pkt.getSize() << std::endl;
} catch (con::NoIncomingDataException &e) {
}
sleep_ms(50);
}
sleep_ms(50);
try {
NetworkPacket pkt;
infostream << "** running server.Receive()" << std::endl;
server.Receive(&pkt);
infostream << "** Server received: peer_id=" << pkt.getPeerId()
<< ", size=" << pkt.getSize()
<< std::endl;
} catch (con::NoIncomingDataException &e) {
}
/*
Simple send-receive test
*/
{
NetworkPacket pkt;
pkt.putRawPacket((u8*) "Hello World !", 14, 0);
SharedBuffer<u8> sentdata = pkt.oldForgePacket();
infostream<<"** running client.Send()"<<std::endl;
client.Send(PEER_ID_SERVER, 0, &pkt, true);
sleep_ms(50);
NetworkPacket recvpacket;
infostream << "** running server.Receive()" << std::endl;
server.Receive(&recvpacket);
infostream << "** Server received: peer_id=" << pkt.getPeerId()
<< ", size=" << pkt.getSize()
<< ", data=" << (const char*)pkt.getU8Ptr(0)
<< std::endl;
SharedBuffer<u8> recvdata = pkt.oldForgePacket();
UASSERT(memcmp(*sentdata, *recvdata, recvdata.getSize()) == 0);
}
session_t peer_id_client = 2;
/*
Send a large packet
*/
{
const int datasize = 30000;
NetworkPacket pkt(0, datasize);
for (u16 i=0; i<datasize; i++) {
pkt << (u8) i/4;
}
infostream << "Sending data (size=" << datasize << "):";
for (int i = 0; i < datasize && i < 20; i++) {
if (i % 2 == 0)
infostream << " ";
char buf[10];
porting::mt_snprintf(buf, sizeof(buf), "%.2X",
((int)((const char *)pkt.getU8Ptr(0))[i]) & 0xff);
infostream<<buf;
}
if (datasize > 20)
infostream << "...";
infostream << std::endl;
SharedBuffer<u8> sentdata = pkt.oldForgePacket();
server.Send(peer_id_client, 0, &pkt, true);
//sleep_ms(3000);
SharedBuffer<u8> recvdata;
infostream << "** running client.Receive()" << std::endl;
session_t peer_id = 132;
u16 size = 0;
bool received = false;
u64 timems0 = porting::getTimeMs();
for (;;) {
if (porting::getTimeMs() - timems0 > 5000 || received)
break;
try {
NetworkPacket pkt;
client.Receive(&pkt);
size = pkt.getSize();
peer_id = pkt.getPeerId();
recvdata = pkt.oldForgePacket();
received = true;
} catch (con::NoIncomingDataException &e) {
}
sleep_ms(10);
}
UASSERT(received);
infostream << "** Client received: peer_id=" << peer_id
<< ", size=" << size << std::endl;
infostream << "Received data (size=" << size << "): ";
for (int i = 0; i < size && i < 20; i++) {
if (i % 2 == 0)
infostream << " ";
char buf[10];
porting::mt_snprintf(buf, sizeof(buf), "%.2X", ((int)(recvdata[i])) & 0xff);
infostream << buf;
}
if (size > 20)
infostream << "...";
infostream << std::endl;
UASSERT(memcmp(*sentdata, *recvdata, recvdata.getSize()) == 0);
UASSERT(peer_id == PEER_ID_SERVER);
}
// Check peer handlers
UASSERT(hand_client.count == 1);
UASSERT(hand_client.last_id == 1);
UASSERT(hand_server.count == 1);
UASSERT(hand_server.last_id == 2);
}

View File

@ -0,0 +1,112 @@
/*
Minetest
Copyright (C) 2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <unordered_map>
#include "test.h"
#include "client/event_manager.h"
class TestEventManager : public TestBase
{
public:
TestEventManager() { TestManager::registerTestModule(this); }
const char *getName() override { return "TestEventManager"; }
void runTests(IGameDef *gamedef) override;
void testRegister();
void testDeregister();
void testRealEvent();
void testRealEventAfterDereg();
};
// EventManager test class
class EventManagerTest : public EventManager
{
public:
static void eventTest(MtEvent *e, void *data)
{
UASSERT(e->getType() >= 0);
UASSERT(e->getType() < MtEvent::TYPE_MAX);
EventManagerTest *emt = (EventManagerTest *)data;
emt->m_test_value = e->getType();
}
u64 getTestValue() const { return m_test_value; }
void resetValue() { m_test_value = 0; }
private:
u64 m_test_value = 0;
};
static TestEventManager g_test_instance;
void TestEventManager::runTests(IGameDef *gamedef)
{
TEST(testRegister);
TEST(testDeregister);
TEST(testRealEvent);
TEST(testRealEventAfterDereg);
}
void TestEventManager::testRegister()
{
EventManager ev;
ev.reg(MtEvent::PLAYER_DAMAGE, nullptr, nullptr);
ev.reg(MtEvent::PLAYER_DAMAGE, nullptr, nullptr);
}
void TestEventManager::testDeregister()
{
EventManager ev;
ev.dereg(MtEvent::NODE_DUG, nullptr, nullptr);
ev.reg(MtEvent::PLAYER_DAMAGE, nullptr, nullptr);
ev.dereg(MtEvent::PLAYER_DAMAGE, nullptr, nullptr);
}
void TestEventManager::testRealEvent()
{
EventManager ev;
std::unique_ptr<EventManagerTest> emt(new EventManagerTest());
ev.reg(MtEvent::PLAYER_REGAIN_GROUND, EventManagerTest::eventTest, emt.get());
// Put event & verify event value
ev.put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND));
UASSERT(emt->getTestValue() == MtEvent::PLAYER_REGAIN_GROUND);
}
void TestEventManager::testRealEventAfterDereg()
{
EventManager ev;
std::unique_ptr<EventManagerTest> emt(new EventManagerTest());
ev.reg(MtEvent::PLAYER_REGAIN_GROUND, EventManagerTest::eventTest, emt.get());
// Put event & verify event value
ev.put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND));
UASSERT(emt->getTestValue() == MtEvent::PLAYER_REGAIN_GROUND);
// Reset internal value
emt->resetValue();
// Remove the registered event
ev.dereg(MtEvent::PLAYER_REGAIN_GROUND, EventManagerTest::eventTest, emt.get());
// Push the new event & ensure we target the default value
ev.put(new SimpleTriggerEvent(MtEvent::PLAYER_REGAIN_GROUND));
UASSERT(emt->getTestValue() == 0);
}

View File

@ -0,0 +1,264 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <sstream>
#include "log.h"
#include "serialization.h"
#include "nodedef.h"
#include "noise.h"
class TestFilePath : public TestBase {
public:
TestFilePath() { TestManager::registerTestModule(this); }
const char *getName() { return "TestFilePath"; }
void runTests(IGameDef *gamedef);
void testIsDirDelimiter();
void testPathStartsWith();
void testRemoveLastPathComponent();
void testRemoveLastPathComponentWithTrailingDelimiter();
void testRemoveRelativePathComponent();
};
static TestFilePath g_test_instance;
void TestFilePath::runTests(IGameDef *gamedef)
{
TEST(testIsDirDelimiter);
TEST(testPathStartsWith);
TEST(testRemoveLastPathComponent);
TEST(testRemoveLastPathComponentWithTrailingDelimiter);
TEST(testRemoveRelativePathComponent);
}
////////////////////////////////////////////////////////////////////////////////
// adjusts a POSIX path to system-specific conventions
// -> changes '/' to DIR_DELIM
// -> absolute paths start with "C:\\" on windows
std::string p(std::string path)
{
for (size_t i = 0; i < path.size(); ++i) {
if (path[i] == '/') {
path.replace(i, 1, DIR_DELIM);
i += std::string(DIR_DELIM).size() - 1; // generally a no-op
}
}
#ifdef _WIN32
if (path[0] == '\\')
path = "C:" + path;
#endif
return path;
}
void TestFilePath::testIsDirDelimiter()
{
UASSERT(fs::IsDirDelimiter('/') == true);
UASSERT(fs::IsDirDelimiter('A') == false);
UASSERT(fs::IsDirDelimiter(0) == false);
#ifdef _WIN32
UASSERT(fs::IsDirDelimiter('\\') == true);
#else
UASSERT(fs::IsDirDelimiter('\\') == false);
#endif
}
void TestFilePath::testPathStartsWith()
{
const int numpaths = 12;
std::string paths[numpaths] = {
"",
p("/"),
p("/home/user/minetest"),
p("/home/user/minetest/bin"),
p("/home/user/.minetest"),
p("/tmp/dir/file"),
p("/tmp/file/"),
p("/tmP/file"),
p("/tmp"),
p("/tmp/dir"),
p("/home/user2/minetest/worlds"),
p("/home/user2/minetest/world"),
};
/*
expected fs::PathStartsWith results
0 = returns false
1 = returns true
2 = returns false on windows, true elsewhere
3 = returns true on windows, false elsewhere
4 = returns true if and only if
FILESYS_CASE_INSENSITIVE is true
*/
int expected_results[numpaths][numpaths] = {
{1,2,0,0,0,0,0,0,0,0,0,0},
{1,1,0,0,0,0,0,0,0,0,0,0},
{1,1,1,0,0,0,0,0,0,0,0,0},
{1,1,1,1,0,0,0,0,0,0,0,0},
{1,1,0,0,1,0,0,0,0,0,0,0},
{1,1,0,0,0,1,0,0,1,1,0,0},
{1,1,0,0,0,0,1,4,1,0,0,0},
{1,1,0,0,0,0,4,1,4,0,0,0},
{1,1,0,0,0,0,0,0,1,0,0,0},
{1,1,0,0,0,0,0,0,1,1,0,0},
{1,1,0,0,0,0,0,0,0,0,1,0},
{1,1,0,0,0,0,0,0,0,0,0,1},
};
for (int i = 0; i < numpaths; i++)
for (int j = 0; j < numpaths; j++){
/*verbosestream<<"testing fs::PathStartsWith(\""
<<paths[i]<<"\", \""
<<paths[j]<<"\")"<<std::endl;*/
bool starts = fs::PathStartsWith(paths[i], paths[j]);
int expected = expected_results[i][j];
if(expected == 0){
UASSERT(starts == false);
}
else if(expected == 1){
UASSERT(starts == true);
}
#ifdef _WIN32
else if(expected == 2){
UASSERT(starts == false);
}
else if(expected == 3){
UASSERT(starts == true);
}
#else
else if(expected == 2){
UASSERT(starts == true);
}
else if(expected == 3){
UASSERT(starts == false);
}
#endif
else if(expected == 4){
UASSERT(starts == (bool)FILESYS_CASE_INSENSITIVE);
}
}
}
void TestFilePath::testRemoveLastPathComponent()
{
std::string path, result, removed;
UASSERT(fs::RemoveLastPathComponent("") == "");
path = p("/home/user/minetest/bin/..//worlds/world1");
result = fs::RemoveLastPathComponent(path, &removed, 0);
UASSERT(result == path);
UASSERT(removed == "");
result = fs::RemoveLastPathComponent(path, &removed, 1);
UASSERT(result == p("/home/user/minetest/bin/..//worlds"));
UASSERT(removed == p("world1"));
result = fs::RemoveLastPathComponent(path, &removed, 2);
UASSERT(result == p("/home/user/minetest/bin/.."));
UASSERT(removed == p("worlds/world1"));
result = fs::RemoveLastPathComponent(path, &removed, 3);
UASSERT(result == p("/home/user/minetest/bin"));
UASSERT(removed == p("../worlds/world1"));
result = fs::RemoveLastPathComponent(path, &removed, 4);
UASSERT(result == p("/home/user/minetest"));
UASSERT(removed == p("bin/../worlds/world1"));
result = fs::RemoveLastPathComponent(path, &removed, 5);
UASSERT(result == p("/home/user"));
UASSERT(removed == p("minetest/bin/../worlds/world1"));
result = fs::RemoveLastPathComponent(path, &removed, 6);
UASSERT(result == p("/home"));
UASSERT(removed == p("user/minetest/bin/../worlds/world1"));
result = fs::RemoveLastPathComponent(path, &removed, 7);
#ifdef _WIN32
UASSERT(result == "C:");
#else
UASSERT(result == "");
#endif
UASSERT(removed == p("home/user/minetest/bin/../worlds/world1"));
}
void TestFilePath::testRemoveLastPathComponentWithTrailingDelimiter()
{
std::string path, result, removed;
path = p("/home/user/minetest/bin/..//worlds/world1/");
result = fs::RemoveLastPathComponent(path, &removed, 0);
UASSERT(result == path);
UASSERT(removed == "");
result = fs::RemoveLastPathComponent(path, &removed, 1);
UASSERT(result == p("/home/user/minetest/bin/..//worlds"));
UASSERT(removed == p("world1"));
result = fs::RemoveLastPathComponent(path, &removed, 2);
UASSERT(result == p("/home/user/minetest/bin/.."));
UASSERT(removed == p("worlds/world1"));
result = fs::RemoveLastPathComponent(path, &removed, 3);
UASSERT(result == p("/home/user/minetest/bin"));
UASSERT(removed == p("../worlds/world1"));
result = fs::RemoveLastPathComponent(path, &removed, 4);
UASSERT(result == p("/home/user/minetest"));
UASSERT(removed == p("bin/../worlds/world1"));
result = fs::RemoveLastPathComponent(path, &removed, 5);
UASSERT(result == p("/home/user"));
UASSERT(removed == p("minetest/bin/../worlds/world1"));
result = fs::RemoveLastPathComponent(path, &removed, 6);
UASSERT(result == p("/home"));
UASSERT(removed == p("user/minetest/bin/../worlds/world1"));
result = fs::RemoveLastPathComponent(path, &removed, 7);
#ifdef _WIN32
UASSERT(result == "C:");
#else
UASSERT(result == "");
#endif
UASSERT(removed == p("home/user/minetest/bin/../worlds/world1"));
}
void TestFilePath::testRemoveRelativePathComponent()
{
std::string path, result, removed;
path = p("/home/user/minetest/bin");
result = fs::RemoveRelativePathComponents(path);
UASSERT(result == path);
path = p("/home/user/minetest/bin/../worlds/world1");
result = fs::RemoveRelativePathComponents(path);
UASSERT(result == p("/home/user/minetest/worlds/world1"));
path = p("/home/user/minetest/bin/../worlds/world1/");
result = fs::RemoveRelativePathComponents(path);
UASSERT(result == p("/home/user/minetest/worlds/world1"));
path = p(".");
result = fs::RemoveRelativePathComponents(path);
UASSERT(result == "");
path = p("../a");
result = fs::RemoveRelativePathComponents(path);
UASSERT(result == "");
path = p("./subdir/../..");
result = fs::RemoveRelativePathComponents(path);
UASSERT(result == "");
path = p("/a/b/c/.././../d/../e/f/g/../h/i/j/../../../..");
result = fs::RemoveRelativePathComponents(path);
UASSERT(result == p("/a/e"));
}

View File

@ -0,0 +1,93 @@
/*
Minetest
Copyright (C) 2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "client/gameui.h"
class TestGameUI : public TestBase
{
public:
TestGameUI() { TestManager::registerTestModule(this); }
const char *getName() { return "TestGameUI"; }
void runTests(IGameDef *gamedef);
void testInit();
void testFlagSetters();
void testInfoText();
void testStatusText();
};
static TestGameUI g_test_instance;
void TestGameUI::runTests(IGameDef *gamedef)
{
TEST(testInit);
TEST(testFlagSetters);
TEST(testInfoText);
TEST(testStatusText);
}
void TestGameUI::testInit()
{
GameUI gui{};
// Ensure flags on GameUI init
UASSERT(gui.getFlags().show_chat)
UASSERT(gui.getFlags().show_hud)
UASSERT(!gui.getFlags().show_minimap)
UASSERT(!gui.getFlags().show_profiler_graph)
// And after the initFlags init stage
gui.initFlags();
UASSERT(gui.getFlags().show_chat)
UASSERT(gui.getFlags().show_hud)
UASSERT(!gui.getFlags().show_minimap)
UASSERT(!gui.getFlags().show_profiler_graph)
// @TODO verify if we can create non UI nulldevice to test this function
// gui.init();
}
void TestGameUI::testFlagSetters()
{
GameUI gui{};
gui.showMinimap(true);
UASSERT(gui.getFlags().show_minimap);
gui.showMinimap(false);
UASSERT(!gui.getFlags().show_minimap);
}
void TestGameUI::testStatusText()
{
GameUI gui{};
gui.showStatusText(L"test status");
UASSERT(gui.m_statustext_time == 0.0f);
UASSERT(gui.m_statustext == L"test status");
}
void TestGameUI::testInfoText()
{
GameUI gui{};
gui.setInfoText(L"test info");
UASSERT(gui.m_infotext == L"test info");
}

View File

@ -0,0 +1,126 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <sstream>
#include "gamedef.h"
#include "inventory.h"
class TestInventory : public TestBase {
public:
TestInventory() { TestManager::registerTestModule(this); }
const char *getName() { return "TestInventory"; }
void runTests(IGameDef *gamedef);
void testSerializeDeserialize(IItemDefManager *idef);
static const char *serialized_inventory_in;
static const char *serialized_inventory_out;
static const char *serialized_inventory_inc;
};
static TestInventory g_test_instance;
void TestInventory::runTests(IGameDef *gamedef)
{
TEST(testSerializeDeserialize, gamedef->getItemDefManager());
}
////////////////////////////////////////////////////////////////////////////////
void TestInventory::testSerializeDeserialize(IItemDefManager *idef)
{
Inventory inv(idef);
std::istringstream is(serialized_inventory_in, std::ios::binary);
inv.deSerialize(is);
UASSERT(inv.getList("0"));
UASSERT(!inv.getList("main"));
inv.getList("0")->setName("main");
UASSERT(!inv.getList("0"));
UASSERT(inv.getList("main"));
UASSERTEQ(u32, inv.getList("main")->getWidth(), 3);
inv.getList("main")->setWidth(5);
std::ostringstream inv_os(std::ios::binary);
inv.serialize(inv_os, false);
UASSERTEQ(std::string, inv_os.str(), serialized_inventory_out);
inv.setModified(false);
inv_os.str("");
inv_os.clear();
inv.serialize(inv_os, true);
UASSERTEQ(std::string, inv_os.str(), serialized_inventory_inc);
ItemStack leftover = inv.getList("main")->takeItem(7, 99 - 12);
ItemStack wanted = ItemStack("default:dirt", 99 - 12, 0, idef);
UASSERT(leftover == wanted);
leftover = inv.getList("main")->getItem(7);
wanted.count = 12;
UASSERT(leftover == wanted);
}
const char *TestInventory::serialized_inventory_in =
"List 0 10\n"
"Width 3\n"
"Empty\n"
"Empty\n"
"Item default:cobble 61\n"
"Empty\n"
"Empty\n"
"Item default:dirt 71\n"
"Empty\n"
"Item default:dirt 99\n"
"Item default:cobble 38\n"
"Empty\n"
"EndInventoryList\n"
"List abc 1\n"
"Item default:stick 3\n"
"Width 0\n"
"EndInventoryList\n"
"EndInventory\n";
const char *TestInventory::serialized_inventory_out =
"List main 10\n"
"Width 5\n"
"Empty\n"
"Empty\n"
"Item default:cobble 61\n"
"Empty\n"
"Empty\n"
"Item default:dirt 71\n"
"Empty\n"
"Item default:dirt 99\n"
"Item default:cobble 38\n"
"Empty\n"
"EndInventoryList\n"
"List abc 1\n"
"Width 0\n"
"Item default:stick 3\n"
"EndInventoryList\n"
"EndInventory\n";
const char *TestInventory::serialized_inventory_inc =
"KeepList main\n"
"KeepList abc\n"
"EndInventory\n";

View File

@ -0,0 +1,131 @@
/*
Minetest
Copyright (C) 2018 numzero, Lobachevskiy Vitaliy <numzer0@yandex.ru>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "exceptions.h"
#include "irr_ptr.h"
class TestIrrPtr : public TestBase
{
public:
TestIrrPtr() { TestManager::registerTestModule(this); }
const char *getName() { return "TestIrrPtr"; }
void runTests(IGameDef *gamedef);
void testRefCounting();
void testSelfAssignment();
void testNullHandling();
};
static TestIrrPtr g_test_instance;
void TestIrrPtr::runTests(IGameDef *gamedef)
{
TEST(testRefCounting);
TEST(testSelfAssignment);
TEST(testNullHandling);
}
////////////////////////////////////////////////////////////////////////////////
#define UASSERT_REFERENCE_COUNT(object, value, info) \
UTEST((object)->getReferenceCount() == value, \
info "Reference count is %d instead of " #value, \
(object)->getReferenceCount())
void TestIrrPtr::testRefCounting()
{
IReferenceCounted *obj = new IReferenceCounted(); // RC=1
obj->grab();
UASSERT_REFERENCE_COUNT(obj, 2, "Pre-condition failed: ");
{
irr_ptr<IReferenceCounted> p1{obj}; // move semantics
UASSERT(p1.get() == obj);
UASSERT_REFERENCE_COUNT(obj, 2, );
irr_ptr<IReferenceCounted> p2{p1}; // copy ctor
UASSERT(p1.get() == obj);
UASSERT(p2.get() == obj);
UASSERT_REFERENCE_COUNT(obj, 3, );
irr_ptr<IReferenceCounted> p3{std::move(p1)}; // move ctor
UASSERT(p1.get() == nullptr);
UASSERT(p3.get() == obj);
UASSERT_REFERENCE_COUNT(obj, 3, );
p1 = std::move(p2); // move assignment
UASSERT(p1.get() == obj);
UASSERT(p2.get() == nullptr);
UASSERT_REFERENCE_COUNT(obj, 3, );
p2 = p3; // copy assignment
UASSERT(p2.get() == obj);
UASSERT(p3.get() == obj);
UASSERT_REFERENCE_COUNT(obj, 4, );
p1.release();
UASSERT(p1.get() == nullptr);
UASSERT_REFERENCE_COUNT(obj, 4, );
}
UASSERT_REFERENCE_COUNT(obj, 2, );
obj->drop();
UTEST(obj->drop(), "Dropping failed: reference count is %d",
obj->getReferenceCount());
}
void TestIrrPtr::testSelfAssignment()
{
irr_ptr<IReferenceCounted> p1{new IReferenceCounted()};
UASSERT(p1);
UASSERT_REFERENCE_COUNT(p1, 1, );
p1 = p1;
UASSERT(p1);
UASSERT_REFERENCE_COUNT(p1, 1, );
p1 = std::move(p1);
UASSERT(p1);
UASSERT_REFERENCE_COUNT(p1, 1, );
}
void TestIrrPtr::testNullHandling()
{
// In the case of an error, it will probably crash with SEGV.
// Nevertheless, UASSERTs are used to catch possible corner cases.
irr_ptr<IReferenceCounted> p1{new IReferenceCounted()};
UASSERT(p1);
irr_ptr<IReferenceCounted> p2;
UASSERT(!p2);
irr_ptr<IReferenceCounted> p3{p2};
UASSERT(!p2);
UASSERT(!p3);
irr_ptr<IReferenceCounted> p4{std::move(p2)};
UASSERT(!p2);
UASSERT(!p4);
p2 = p2;
UASSERT(!p2);
p2 = std::move(p2);
UASSERT(!p2);
p3 = p2;
UASSERT(!p2);
UASSERT(!p3);
p3 = std::move(p2);
UASSERT(!p2);
UASSERT(!p3);
}

View File

@ -0,0 +1,129 @@
/*
Minetest
Copyright (C) 2016 sfan5 <sfan5@live.de>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <string>
#include "exceptions.h"
#include "client/keycode.h"
class TestKeycode : public TestBase {
public:
TestKeycode() { TestManager::registerTestModule(this); }
const char *getName() { return "TestKeycode"; }
void runTests(IGameDef *gamedef);
void testCreateFromString();
void testCreateFromSKeyInput();
void testCompare();
};
static TestKeycode g_test_instance;
void TestKeycode::runTests(IGameDef *gamedef)
{
TEST(testCreateFromString);
TEST(testCreateFromSKeyInput);
TEST(testCompare);
}
////////////////////////////////////////////////////////////////////////////////
#define UASSERTEQ_STR(one, two) UASSERT(strcmp(one, two) == 0)
void TestKeycode::testCreateFromString()
{
KeyPress k;
// Character key, from char
k = KeyPress("R");
UASSERTEQ_STR(k.sym(), "KEY_KEY_R");
UASSERTCMP(int, >, strlen(k.name()), 0); // should have human description
// Character key, from identifier
k = KeyPress("KEY_KEY_B");
UASSERTEQ_STR(k.sym(), "KEY_KEY_B");
UASSERTCMP(int, >, strlen(k.name()), 0);
// Non-Character key, from identifier
k = KeyPress("KEY_UP");
UASSERTEQ_STR(k.sym(), "KEY_UP");
UASSERTCMP(int, >, strlen(k.name()), 0);
k = KeyPress("KEY_F6");
UASSERTEQ_STR(k.sym(), "KEY_F6");
UASSERTCMP(int, >, strlen(k.name()), 0);
// Irrlicht-unknown key, from char
k = KeyPress("/");
UASSERTEQ_STR(k.sym(), "/");
UASSERTCMP(int, >, strlen(k.name()), 0);
}
void TestKeycode::testCreateFromSKeyInput()
{
KeyPress k;
irr::SEvent::SKeyInput in;
// Character key
in.Key = irr::KEY_KEY_3;
in.Char = L'3';
k = KeyPress(in);
UASSERTEQ_STR(k.sym(), "KEY_KEY_3");
// Non-Character key
in.Key = irr::KEY_RSHIFT;
in.Char = L'\0';
k = KeyPress(in);
UASSERTEQ_STR(k.sym(), "KEY_RSHIFT");
// Irrlicht-unknown key
in.Key = irr::KEY_KEY_CODES_COUNT;
in.Char = L'?';
k = KeyPress(in);
UASSERTEQ_STR(k.sym(), "?");
// prefer_character mode
in.Key = irr::KEY_COMMA;
in.Char = L'G';
k = KeyPress(in, true);
UASSERTEQ_STR(k.sym(), "KEY_KEY_G");
}
void TestKeycode::testCompare()
{
// Basic comparison
UASSERT(KeyPress("5") == KeyPress("KEY_KEY_5"));
UASSERT(!(KeyPress("5") == KeyPress("KEY_NUMPAD_5")));
// Matching char suffices
// note: This is a real-world example, Irrlicht maps XK_equal to irr::KEY_PLUS on Linux
irr::SEvent::SKeyInput in;
in.Key = irr::KEY_PLUS;
in.Char = L'=';
UASSERT(KeyPress("=") == KeyPress(in));
// Matching keycode suffices
irr::SEvent::SKeyInput in2;
in.Key = in2.Key = irr::KEY_OEM_CLEAR;
in.Char = L'\0';
in2.Char = L';';
UASSERT(KeyPress(in) == KeyPress(in2));
}

View File

@ -0,0 +1,237 @@
/*
Minetest
Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "noise.h"
#include "settings.h"
#include "mapgen/mapgen_v5.h"
#include "util/sha1.h"
#include "map_settings_manager.h"
class TestMapSettingsManager : public TestBase {
public:
TestMapSettingsManager() { TestManager::registerTestModule(this); }
const char *getName() { return "TestMapSettingsManager"; }
void makeUserConfig(Settings *conf);
std::string makeMetaFile(bool make_corrupt);
void runTests(IGameDef *gamedef);
void testMapSettingsManager();
void testMapMetaSaveLoad();
void testMapMetaFailures();
};
static TestMapSettingsManager g_test_instance;
void TestMapSettingsManager::runTests(IGameDef *gamedef)
{
TEST(testMapSettingsManager);
TEST(testMapMetaSaveLoad);
TEST(testMapMetaFailures);
}
////////////////////////////////////////////////////////////////////////////////
void check_noise_params(const NoiseParams *np1, const NoiseParams *np2)
{
UASSERTEQ(float, np1->offset, np2->offset);
UASSERTEQ(float, np1->scale, np2->scale);
UASSERT(np1->spread == np2->spread);
UASSERTEQ(s32, np1->seed, np2->seed);
UASSERTEQ(u16, np1->octaves, np2->octaves);
UASSERTEQ(float, np1->persist, np2->persist);
UASSERTEQ(float, np1->lacunarity, np2->lacunarity);
UASSERTEQ(u32, np1->flags, np2->flags);
}
void TestMapSettingsManager::makeUserConfig(Settings *conf)
{
conf->set("mg_name", "v7");
conf->set("seed", "5678");
conf->set("water_level", "20");
conf->set("mgv5_np_factor", "0, 12, (500, 250, 500), 920382, 5, 0.45, 3.0");
conf->set("mgv5_np_height", "0, 15, (500, 250, 500), 841746, 5, 0.5, 3.0");
conf->set("mgv5_np_filler_depth", "20, 1, (150, 150, 150), 261, 4, 0.7, 1.0");
conf->set("mgv5_np_ground", "-43, 40, (80, 80, 80), 983240, 4, 0.55, 2.0");
}
std::string TestMapSettingsManager::makeMetaFile(bool make_corrupt)
{
std::string metafile = getTestTempFile();
const char *metafile_contents =
"mg_name = v5\n"
"seed = 1234\n"
"mg_flags = light\n"
"mgv5_np_filler_depth = 20, 1, (150, 150, 150), 261, 4, 0.7, 1.0\n"
"mgv5_np_height = 20, 10, (250, 250, 250), 84174, 4, 0.5, 1.0\n";
FILE *f = fopen(metafile.c_str(), "wb");
UASSERT(f != NULL);
fputs(metafile_contents, f);
if (!make_corrupt)
fputs("[end_of_params]\n", f);
fclose(f);
return metafile;
}
void TestMapSettingsManager::testMapSettingsManager()
{
Settings user_settings;
makeUserConfig(&user_settings);
std::string test_mapmeta_path = makeMetaFile(false);
MapSettingsManager mgr(&user_settings, test_mapmeta_path);
std::string value;
UASSERT(mgr.getMapSetting("mg_name", &value));
UASSERT(value == "v7");
// Pretend we're initializing the ServerMap
UASSERT(mgr.loadMapMeta());
// Pretend some scripts are requesting mapgen params
UASSERT(mgr.getMapSetting("mg_name", &value));
UASSERT(value == "v5");
UASSERT(mgr.getMapSetting("seed", &value));
UASSERT(value == "1234");
UASSERT(mgr.getMapSetting("water_level", &value));
UASSERT(value == "20");
// Pretend we have some mapgen settings configured from the scripting
UASSERT(mgr.setMapSetting("water_level", "15"));
UASSERT(mgr.setMapSetting("seed", "02468"));
UASSERT(mgr.setMapSetting("mg_flags", "nolight", true));
NoiseParams script_np_filler_depth(0, 100, v3f(200, 100, 200), 261, 4, 0.7, 2.0);
NoiseParams script_np_factor(0, 100, v3f(50, 50, 50), 920381, 3, 0.45, 2.0);
NoiseParams script_np_height(0, 100, v3f(450, 450, 450), 84174, 4, 0.5, 2.0);
NoiseParams meta_np_height(20, 10, v3f(250, 250, 250), 84174, 4, 0.5, 1.0);
NoiseParams user_np_ground(-43, 40, v3f(80, 80, 80), 983240, 4, 0.55, 2.0, NOISE_FLAG_EASED);
mgr.setMapSettingNoiseParams("mgv5_np_filler_depth", &script_np_filler_depth, true);
mgr.setMapSettingNoiseParams("mgv5_np_height", &script_np_height);
mgr.setMapSettingNoiseParams("mgv5_np_factor", &script_np_factor);
// Now make our Params and see if the values are correctly sourced
MapgenParams *params = mgr.makeMapgenParams();
UASSERT(params->mgtype == MAPGEN_V5);
UASSERT(params->chunksize == 5);
UASSERT(params->water_level == 15);
UASSERT(params->seed == 1234);
UASSERT((params->flags & MG_LIGHT) == 0);
MapgenV5Params *v5params = (MapgenV5Params *)params;
check_noise_params(&v5params->np_filler_depth, &script_np_filler_depth);
check_noise_params(&v5params->np_factor, &script_np_factor);
check_noise_params(&v5params->np_height, &meta_np_height);
check_noise_params(&v5params->np_ground, &user_np_ground);
UASSERT(mgr.setMapSetting("foobar", "25") == false);
// Pretend the ServerMap is shutting down
UASSERT(mgr.saveMapMeta());
// Make sure our interface expectations are met
UASSERT(mgr.mapgen_params == params);
UASSERT(mgr.makeMapgenParams() == params);
#if 0
// TODO(paramat or hmmmm): change this to compare the result against a static file
// Load the resulting map_meta.txt and make sure it contains what we expect
unsigned char expected_contents_hash[20] = {
0x48, 0x3f, 0x88, 0x5a, 0xc0, 0x7a, 0x14, 0x48, 0xa4, 0x71,
0x78, 0x56, 0x95, 0x2d, 0xdc, 0x6a, 0xf7, 0x61, 0x36, 0x5f
};
SHA1 ctx;
std::string metafile_contents;
UASSERT(fs::ReadFile(test_mapmeta_path, metafile_contents));
ctx.addBytes(&metafile_contents[0], metafile_contents.size());
unsigned char *sha1_result = ctx.getDigest();
int resultdiff = memcmp(sha1_result, expected_contents_hash, 20);
free(sha1_result);
UASSERT(!resultdiff);
#endif
}
void TestMapSettingsManager::testMapMetaSaveLoad()
{
Settings conf;
std::string path = getTestTempDirectory()
+ DIR_DELIM + "foobar" + DIR_DELIM + "map_meta.txt";
// Create a set of mapgen params and save them to map meta
conf.set("seed", "12345");
conf.set("water_level", "5");
MapSettingsManager mgr1(&conf, path);
MapgenParams *params1 = mgr1.makeMapgenParams();
UASSERT(params1);
UASSERT(mgr1.saveMapMeta());
// Now try loading the map meta to mapgen params
conf.set("seed", "67890");
conf.set("water_level", "32");
MapSettingsManager mgr2(&conf, path);
UASSERT(mgr2.loadMapMeta());
MapgenParams *params2 = mgr2.makeMapgenParams();
UASSERT(params2);
// Check that both results are correct
UASSERTEQ(u64, params1->seed, 12345);
UASSERTEQ(s16, params1->water_level, 5);
UASSERTEQ(u64, params2->seed, 12345);
UASSERTEQ(s16, params2->water_level, 5);
}
void TestMapSettingsManager::testMapMetaFailures()
{
std::string test_mapmeta_path;
Settings conf;
// Check to see if it'll fail on a non-existent map meta file
test_mapmeta_path = "woobawooba/fgdfg/map_meta.txt";
UASSERT(!fs::PathExists(test_mapmeta_path));
MapSettingsManager mgr1(&conf, test_mapmeta_path);
UASSERT(!mgr1.loadMapMeta());
// Check to see if it'll fail on a corrupt map meta file
test_mapmeta_path = makeMetaFile(true);
UASSERT(fs::PathExists(test_mapmeta_path));
MapSettingsManager mgr2(&conf, test_mapmeta_path);
UASSERT(!mgr2.loadMapMeta());
}

View File

@ -0,0 +1,57 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "gamedef.h"
#include "nodedef.h"
#include "content_mapnode.h"
class TestMapNode : public TestBase
{
public:
TestMapNode() { TestManager::registerTestModule(this); }
const char *getName() { return "TestMapNode"; }
void runTests(IGameDef *gamedef);
void testNodeProperties(const NodeDefManager *nodedef);
};
static TestMapNode g_test_instance;
void TestMapNode::runTests(IGameDef *gamedef)
{
TEST(testNodeProperties, gamedef->getNodeDefManager());
}
////////////////////////////////////////////////////////////////////////////////
void TestMapNode::testNodeProperties(const NodeDefManager *nodedef)
{
MapNode n(CONTENT_AIR);
UASSERT(n.getContent() == CONTENT_AIR);
UASSERT(n.getLight(LIGHTBANK_DAY, nodedef) == 0);
UASSERT(n.getLight(LIGHTBANK_NIGHT, nodedef) == 0);
// Transparency
n.setContent(CONTENT_AIR);
UASSERT(nodedef->get(n).light_propagates == true);
}

View File

@ -0,0 +1,76 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "gamedef.h"
#include "modchannels.h"
class TestModChannels : public TestBase
{
public:
TestModChannels() { TestManager::registerTestModule(this); }
const char *getName() { return "TestModChannels"; }
void runTests(IGameDef *gamedef);
void testJoinChannel(IGameDef *gamedef);
void testLeaveChannel(IGameDef *gamedef);
void testSendMessageToChannel(IGameDef *gamedef);
};
static TestModChannels g_test_instance;
void TestModChannels::runTests(IGameDef *gamedef)
{
TEST(testJoinChannel, gamedef);
TEST(testLeaveChannel, gamedef);
TEST(testSendMessageToChannel, gamedef);
}
void TestModChannels::testJoinChannel(IGameDef *gamedef)
{
// Test join
UASSERT(gamedef->joinModChannel("test_join_channel"));
// Test join (fail, already join)
UASSERT(!gamedef->joinModChannel("test_join_channel"));
}
void TestModChannels::testLeaveChannel(IGameDef *gamedef)
{
// Test leave (not joined)
UASSERT(!gamedef->leaveModChannel("test_leave_channel"));
UASSERT(gamedef->joinModChannel("test_leave_channel"));
// Test leave (joined)
UASSERT(gamedef->leaveModChannel("test_leave_channel"));
}
void TestModChannels::testSendMessageToChannel(IGameDef *gamedef)
{
// Test sendmsg (not joined)
UASSERT(!gamedef->sendModChannelMessage(
"test_sendmsg_channel", "testmsgchannel"));
UASSERT(gamedef->joinModChannel("test_sendmsg_channel"));
// Test sendmsg (joined)
UASSERT(gamedef->sendModChannelMessage("test_sendmsg_channel", "testmsgchannel"));
}

View File

@ -0,0 +1,67 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <sstream>
#include "gamedef.h"
#include "nodedef.h"
#include "network/networkprotocol.h"
class TestNodeDef : public TestBase
{
public:
TestNodeDef() { TestManager::registerTestModule(this); }
const char *getName() { return "TestNodeDef"; }
void runTests(IGameDef *gamedef);
void testContentFeaturesSerialization();
};
static TestNodeDef g_test_instance;
void TestNodeDef::runTests(IGameDef *gamedef)
{
TEST(testContentFeaturesSerialization);
}
////////////////////////////////////////////////////////////////////////////////
void TestNodeDef::testContentFeaturesSerialization()
{
ContentFeatures f;
f.name = "default:stone";
for (TileDef &tiledef : f.tiledef)
tiledef.name = "default_stone.png";
f.is_ground_content = true;
std::ostringstream os(std::ios::binary);
f.serialize(os, LATEST_PROTOCOL_VERSION);
// verbosestream<<"Test ContentFeatures size: "<<os.str().size()<<std::endl;
std::istringstream is(os.str(), std::ios::binary);
ContentFeatures f2;
f2.deSerialize(is);
UASSERT(f.walkable == f2.walkable);
UASSERT(f.node_box.type == f2.node_box.type);
}

View File

@ -0,0 +1,210 @@
/*
Minetest
Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "util/numeric.h"
#include "exceptions.h"
#include "gamedef.h"
#include "nodedef.h"
#include <algorithm>
class TestNodeResolver : public TestBase {
public:
TestNodeResolver() { TestManager::registerTestModule(this); }
const char *getName() { return "TestNodeResolver"; }
void runTests(IGameDef *gamedef);
void testNodeResolving(NodeDefManager *ndef);
void testPendingResolveCancellation(NodeDefManager *ndef);
void testDirectResolveMethod(NodeDefManager *ndef);
void testNoneResolveMethod(NodeDefManager *ndef);
};
static TestNodeResolver g_test_instance;
void TestNodeResolver::runTests(IGameDef *gamedef)
{
NodeDefManager *ndef =
(NodeDefManager *)gamedef->getNodeDefManager();
ndef->resetNodeResolveState();
TEST(testNodeResolving, ndef);
ndef->resetNodeResolveState();
TEST(testPendingResolveCancellation, ndef);
}
class Foobar : public NodeResolver {
public:
void resolveNodeNames();
content_t test_nr_node1;
content_t test_nr_node2;
content_t test_nr_node3;
content_t test_nr_node4;
content_t test_nr_node5;
std::vector<content_t> test_nr_list;
std::vector<content_t> test_nr_list_group;
std::vector<content_t> test_nr_list_required;
std::vector<content_t> test_nr_list_empty;
};
class Foobaz : public NodeResolver {
public:
void resolveNodeNames();
content_t test_content1;
content_t test_content2;
};
////////////////////////////////////////////////////////////////////////////////
void Foobar::resolveNodeNames()
{
UASSERT(getIdFromNrBacklog(&test_nr_node1, "", CONTENT_IGNORE) == true);
UASSERT(getIdsFromNrBacklog(&test_nr_list) == true);
UASSERT(getIdsFromNrBacklog(&test_nr_list_group) == true);
UASSERT(getIdsFromNrBacklog(&test_nr_list_required,
true, CONTENT_AIR) == false);
UASSERT(getIdsFromNrBacklog(&test_nr_list_empty) == true);
UASSERT(getIdFromNrBacklog(&test_nr_node2, "", CONTENT_IGNORE) == true);
UASSERT(getIdFromNrBacklog(&test_nr_node3,
"default:brick", CONTENT_IGNORE) == true);
UASSERT(getIdFromNrBacklog(&test_nr_node4,
"default:gobbledygook", CONTENT_AIR) == false);
UASSERT(getIdFromNrBacklog(&test_nr_node5, "", CONTENT_IGNORE) == false);
}
void Foobaz::resolveNodeNames()
{
UASSERT(getIdFromNrBacklog(&test_content1, "", CONTENT_IGNORE) == true);
UASSERT(getIdFromNrBacklog(&test_content2, "", CONTENT_IGNORE) == false);
}
void TestNodeResolver::testNodeResolving(NodeDefManager *ndef)
{
Foobar foobar;
size_t i;
foobar.m_nodenames.emplace_back("default:torch");
foobar.m_nodenames.emplace_back("default:dirt_with_grass");
foobar.m_nodenames.emplace_back("default:water");
foobar.m_nodenames.emplace_back("default:abloobloobloo");
foobar.m_nodenames.emplace_back("default:stone");
foobar.m_nodenames.emplace_back("default:shmegoldorf");
foobar.m_nnlistsizes.push_back(5);
foobar.m_nodenames.emplace_back("group:liquids");
foobar.m_nnlistsizes.push_back(1);
foobar.m_nodenames.emplace_back("default:warf");
foobar.m_nodenames.emplace_back("default:stone");
foobar.m_nodenames.emplace_back("default:bloop");
foobar.m_nnlistsizes.push_back(3);
foobar.m_nnlistsizes.push_back(0);
foobar.m_nodenames.emplace_back("default:brick");
foobar.m_nodenames.emplace_back("default:desert_stone");
foobar.m_nodenames.emplace_back("default:shnitzle");
ndef->pendNodeResolve(&foobar);
UASSERT(foobar.m_ndef == ndef);
ndef->setNodeRegistrationStatus(true);
ndef->runNodeResolveCallbacks();
// Check that we read single nodes successfully
UASSERTEQ(content_t, foobar.test_nr_node1, t_CONTENT_TORCH);
UASSERTEQ(content_t, foobar.test_nr_node2, t_CONTENT_BRICK);
UASSERTEQ(content_t, foobar.test_nr_node3, t_CONTENT_BRICK);
UASSERTEQ(content_t, foobar.test_nr_node4, CONTENT_AIR);
UASSERTEQ(content_t, foobar.test_nr_node5, CONTENT_IGNORE);
// Check that we read all the regular list items
static const content_t expected_test_nr_list[] = {
t_CONTENT_GRASS,
t_CONTENT_WATER,
t_CONTENT_STONE,
};
UASSERTEQ(size_t, foobar.test_nr_list.size(), 3);
for (i = 0; i != foobar.test_nr_list.size(); i++)
UASSERTEQ(content_t, foobar.test_nr_list[i], expected_test_nr_list[i]);
// Check that we read all the list items that were from a group entry
static const content_t expected_test_nr_list_group[] = {
t_CONTENT_WATER,
t_CONTENT_LAVA,
};
UASSERTEQ(size_t, foobar.test_nr_list_group.size(), 2);
for (i = 0; i != foobar.test_nr_list_group.size(); i++) {
UASSERT(CONTAINS(foobar.test_nr_list_group,
expected_test_nr_list_group[i]));
}
// Check that we read all the items we're able to in a required list
static const content_t expected_test_nr_list_required[] = {
CONTENT_AIR,
t_CONTENT_STONE,
CONTENT_AIR,
};
UASSERTEQ(size_t, foobar.test_nr_list_required.size(), 3);
for (i = 0; i != foobar.test_nr_list_required.size(); i++)
UASSERTEQ(content_t, foobar.test_nr_list_required[i],
expected_test_nr_list_required[i]);
// Check that the edge case of 0 is successful
UASSERTEQ(size_t, foobar.test_nr_list_empty.size(), 0);
}
void TestNodeResolver::testPendingResolveCancellation(NodeDefManager *ndef)
{
Foobaz foobaz1;
foobaz1.test_content1 = 1234;
foobaz1.test_content2 = 5678;
foobaz1.m_nodenames.emplace_back("default:dirt_with_grass");
foobaz1.m_nodenames.emplace_back("default:abloobloobloo");
ndef->pendNodeResolve(&foobaz1);
Foobaz foobaz2;
foobaz2.test_content1 = 1234;
foobaz2.test_content2 = 5678;
foobaz2.m_nodenames.emplace_back("default:dirt_with_grass");
foobaz2.m_nodenames.emplace_back("default:abloobloobloo");
ndef->pendNodeResolve(&foobaz2);
ndef->cancelNodeResolveCallback(&foobaz1);
ndef->setNodeRegistrationStatus(true);
ndef->runNodeResolveCallbacks();
UASSERT(foobaz1.test_content1 == 1234);
UASSERT(foobaz1.test_content2 == 5678);
UASSERT(foobaz2.test_content1 == t_CONTENT_GRASS);
UASSERT(foobaz2.test_content2 == CONTENT_IGNORE);
}

286
src/unittest/test_noise.cpp Normal file
View File

@ -0,0 +1,286 @@
/*
Minetest
Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <cmath>
#include "exceptions.h"
#include "noise.h"
class TestNoise : public TestBase {
public:
TestNoise() { TestManager::registerTestModule(this); }
const char *getName() { return "TestNoise"; }
void runTests(IGameDef *gamedef);
void testNoise2dPoint();
void testNoise2dBulk();
void testNoise3dPoint();
void testNoise3dBulk();
void testNoiseInvalidParams();
static const float expected_2d_results[10 * 10];
static const float expected_3d_results[10 * 10 * 10];
};
static TestNoise g_test_instance;
void TestNoise::runTests(IGameDef *gamedef)
{
TEST(testNoise2dPoint);
TEST(testNoise2dBulk);
TEST(testNoise3dPoint);
TEST(testNoise3dBulk);
TEST(testNoiseInvalidParams);
}
////////////////////////////////////////////////////////////////////////////////
void TestNoise::testNoise2dPoint()
{
NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0);
u32 i = 0;
for (u32 y = 0; y != 10; y++)
for (u32 x = 0; x != 10; x++, i++) {
float actual = NoisePerlin2D(&np_normal, x, y, 1337);
float expected = expected_2d_results[i];
UASSERT(std::fabs(actual - expected) <= 0.00001);
}
}
void TestNoise::testNoise2dBulk()
{
NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0);
Noise noise_normal_2d(&np_normal, 1337, 10, 10);
float *noisevals = noise_normal_2d.perlinMap2D(0, 0, NULL);
for (u32 i = 0; i != 10 * 10; i++) {
float actual = noisevals[i];
float expected = expected_2d_results[i];
UASSERT(std::fabs(actual - expected) <= 0.00001);
}
}
void TestNoise::testNoise3dPoint()
{
NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0);
u32 i = 0;
for (u32 z = 0; z != 10; z++)
for (u32 y = 0; y != 10; y++)
for (u32 x = 0; x != 10; x++, i++) {
float actual = NoisePerlin3D(&np_normal, x, y, z, 1337);
float expected = expected_3d_results[i];
UASSERT(std::fabs(actual - expected) <= 0.00001);
}
}
void TestNoise::testNoise3dBulk()
{
NoiseParams np_normal(20, 40, v3f(50, 50, 50), 9, 5, 0.6, 2.0);
Noise noise_normal_3d(&np_normal, 1337, 10, 10, 10);
float *noisevals = noise_normal_3d.perlinMap3D(0, 0, 0, NULL);
for (u32 i = 0; i != 10 * 10 * 10; i++) {
float actual = noisevals[i];
float expected = expected_3d_results[i];
UASSERT(std::fabs(actual - expected) <= 0.00001);
}
}
void TestNoise::testNoiseInvalidParams()
{
bool exception_thrown = false;
try {
NoiseParams np_highmem(4, 70, v3f(1, 1, 1), 5, 60, 0.7, 10.0);
Noise noise_highmem_3d(&np_highmem, 1337, 200, 200, 200);
noise_highmem_3d.perlinMap3D(0, 0, 0, NULL);
} catch (InvalidNoiseParamsException &) {
exception_thrown = true;
}
UASSERT(exception_thrown);
}
const float TestNoise::expected_2d_results[10 * 10] = {
19.11726, 18.49626, 16.48476, 15.02135, 14.75713, 16.26008, 17.54822,
18.06860, 18.57016, 18.48407, 18.49649, 17.89160, 15.94162, 14.54901,
14.31298, 15.72643, 16.94669, 17.55494, 18.58796, 18.87925, 16.08101,
15.53764, 13.83844, 12.77139, 12.73648, 13.95632, 14.97904, 15.81829,
18.37694, 19.73759, 13.19182, 12.71924, 11.34560, 10.78025, 11.18980,
12.52303, 13.45012, 14.30001, 17.43298, 19.15244, 10.93217, 10.48625,
9.30923, 9.18632, 10.16251, 12.11264, 13.19697, 13.80801, 16.39567,
17.66203, 10.40222, 9.86070, 8.47223, 8.45471, 10.04780, 13.54730,
15.33709, 15.48503, 16.46177, 16.52508, 10.80333, 10.19045, 8.59420,
8.47646, 10.22676, 14.43173, 16.48353, 16.24859, 16.20863, 15.52847,
11.01179, 10.45209, 8.98678, 8.83986, 10.43004, 14.46054, 16.29387,
15.73521, 15.01744, 13.85542, 10.55201, 10.33375, 9.85102, 10.07821,
11.58235, 15.62046, 17.35505, 16.13181, 12.66011, 9.51853, 11.50994,
11.54074, 11.77989, 12.29790, 13.76139, 17.81982, 19.49008, 17.79470,
12.34344, 7.78363,
};
const float TestNoise::expected_3d_results[10 * 10 * 10] = {
19.11726, 18.01059, 16.90392, 15.79725, 16.37154, 17.18597, 18.00040,
18.33467, 18.50889, 18.68311, 17.85386, 16.90585, 15.95785, 15.00985,
15.61132, 16.43415, 17.25697, 17.95415, 18.60942, 19.26471, 16.59046,
15.80112, 15.01178, 14.22244, 14.85110, 15.68232, 16.51355, 17.57361,
18.70996, 19.84631, 15.32705, 14.69638, 14.06571, 13.43504, 14.09087,
14.93050, 15.77012, 17.19309, 18.81050, 20.42790, 15.06729, 14.45855,
13.84981, 13.24107, 14.39364, 15.79782, 17.20201, 18.42640, 19.59085,
20.75530, 14.95090, 14.34456, 13.73821, 13.13187, 14.84825, 16.89645,
18.94465, 19.89025, 20.46832, 21.04639, 14.83452, 14.23057, 13.62662,
13.02267, 15.30287, 17.99508, 20.68730, 21.35411, 21.34580, 21.33748,
15.39817, 15.03590, 14.67364, 14.31137, 16.78242, 19.65824, 22.53405,
22.54626, 21.60395, 20.66164, 16.18850, 16.14768, 16.10686, 16.06603,
18.60362, 21.50956, 24.41549, 23.64784, 21.65566, 19.66349, 16.97884,
17.25946, 17.54008, 17.82069, 20.42482, 23.36088, 26.29694, 24.74942,
21.70738, 18.66534, 18.78506, 17.51834, 16.25162, 14.98489, 15.14217,
15.50287, 15.86357, 16.40597, 17.00895, 17.61193, 18.20160, 16.98795,
15.77430, 14.56065, 14.85059, 15.35533, 15.86007, 16.63399, 17.49763,
18.36128, 17.61814, 16.45757, 15.29699, 14.13641, 14.55902, 15.20779,
15.85657, 16.86200, 17.98632, 19.11064, 17.03468, 15.92718, 14.81968,
13.71218, 14.26744, 15.06026, 15.85306, 17.09001, 18.47501, 19.86000,
16.67870, 15.86256, 15.04641, 14.23026, 15.31397, 16.66909, 18.02420,
18.89042, 19.59369, 20.29695, 16.35522, 15.86447, 15.37372, 14.88297,
16.55165, 18.52883, 20.50600, 20.91547, 20.80237, 20.68927, 16.03174,
15.86639, 15.70103, 15.53568, 17.78933, 20.38857, 22.98780, 22.94051,
22.01105, 21.08159, 16.42434, 16.61407, 16.80381, 16.99355, 19.16133,
21.61169, 24.06204, 23.65252, 22.28970, 20.92689, 17.05562, 17.61035,
18.16508, 18.71981, 20.57809, 22.62260, 24.66711, 23.92686, 22.25835,
20.58984, 17.68691, 18.60663, 19.52635, 20.44607, 21.99486, 23.63352,
25.27217, 24.20119, 22.22699, 20.25279, 18.45285, 17.02608, 15.59931,
14.17254, 13.91279, 13.81976, 13.72674, 14.47727, 15.50900, 16.54073,
18.54934, 17.07005, 15.59076, 14.11146, 14.08987, 14.27651, 14.46316,
15.31383, 16.38584, 17.45785, 18.64582, 17.11401, 15.58220, 14.05039,
14.26694, 14.73326, 15.19958, 16.15038, 17.26268, 18.37498, 18.74231,
17.15798, 15.57364, 13.98932, 14.44402, 15.19001, 15.93600, 16.98694,
18.13952, 19.29210, 18.29012, 17.26656, 16.24301, 15.21946, 16.23430,
17.54035, 18.84639, 19.35445, 19.59653, 19.83860, 17.75954, 17.38438,
17.00923, 16.63407, 18.25505, 20.16120, 22.06734, 21.94068, 21.13642,
20.33215, 17.22896, 17.50220, 17.77544, 18.04868, 20.27580, 22.78205,
25.28829, 24.52691, 22.67631, 20.82571, 17.45050, 18.19224, 18.93398,
19.67573, 21.54024, 23.56514, 25.59004, 24.75878, 22.97546, 21.19213,
17.92274, 19.07302, 20.22330, 21.37358, 22.55256, 23.73565, 24.91873,
24.20587, 22.86103, 21.51619, 18.39499, 19.95381, 21.51263, 23.07145,
23.56490, 23.90615, 24.24741, 23.65296, 22.74660, 21.84024, 18.12065,
16.53382, 14.94700, 13.36018, 12.68341, 12.13666, 11.58990, 12.54858,
14.00906, 15.46955, 18.89708, 17.15214, 15.40721, 13.66227, 13.32914,
13.19769, 13.06625, 13.99367, 15.27405, 16.55443, 19.67351, 17.77046,
15.86741, 13.96436, 13.97486, 14.25873, 14.54260, 15.43877, 16.53904,
17.63931, 20.44994, 18.38877, 16.32761, 14.26645, 14.62059, 15.31977,
16.01895, 16.88387, 17.80403, 18.72419, 19.90153, 18.67057, 17.43962,
16.20866, 17.15464, 18.41161, 19.66858, 19.81848, 19.59936, 19.38024,
19.16386, 18.90429, 18.64473, 18.38517, 19.95845, 21.79357, 23.62868,
22.96589, 21.47046, 19.97503, 18.42618, 19.13802, 19.84985, 20.56168,
22.76226, 25.17553, 27.58879, 26.11330, 23.34156, 20.56982, 18.47667,
19.77041, 21.06416, 22.35790, 23.91914, 25.51859, 27.11804, 25.86504,
23.66121, 21.45738, 18.78986, 20.53570, 22.28153, 24.02736, 24.52704,
24.84869, 25.17035, 24.48488, 23.46371, 22.44254, 19.10306, 21.30098,
23.49890, 25.69682, 25.13494, 24.17879, 23.22265, 23.10473, 23.26621,
23.42769, 17.93453, 16.72707, 15.51962, 14.31216, 12.96039, 11.58800,
10.21561, 11.29675, 13.19573, 15.09471, 18.05853, 16.85308, 15.64762,
14.44216, 13.72634, 13.08047, 12.43459, 13.48912, 15.11045, 16.73179,
18.18253, 16.97908, 15.77562, 14.57217, 14.49229, 14.57293, 14.65357,
15.68150, 17.02518, 18.36887, 18.30654, 17.10508, 15.90363, 14.70217,
15.25825, 16.06540, 16.87255, 17.87387, 18.93991, 20.00595, 17.54117,
17.32369, 17.10622, 16.88875, 18.07494, 19.46166, 20.84837, 21.12988,
21.04298, 20.95609, 16.64874, 17.55554, 18.46234, 19.36913, 21.18461,
23.12989, 25.07517, 24.53784, 23.17297, 21.80810, 15.75632, 17.78738,
19.81845, 21.84951, 24.29427, 26.79812, 29.30198, 27.94580, 25.30295,
22.66010, 15.98046, 18.43027, 20.88008, 23.32989, 25.21976, 27.02964,
28.83951, 27.75863, 25.71416, 23.66970, 16.57679, 19.21017, 21.84355,
24.47693, 25.41719, 26.11557, 26.81396, 26.37308, 25.55245, 24.73182,
17.17313, 19.99008, 22.80702, 25.62397, 25.61462, 25.20151, 24.78840,
24.98753, 25.39074, 25.79395, 17.76927, 17.01824, 16.26722, 15.51620,
13.45256, 11.20141, 8.95025, 10.14162, 12.48049, 14.81936, 17.05051,
16.49955, 15.94860, 15.39764, 14.28896, 13.10061, 11.91225, 13.10109,
15.08232, 17.06355, 16.33175, 15.98086, 15.62998, 15.27909, 15.12537,
14.99981, 14.87425, 16.06056, 17.68415, 19.30775, 15.61299, 15.46217,
15.31136, 15.16054, 15.96177, 16.89901, 17.83625, 19.02003, 20.28599,
21.55194, 14.61341, 15.58383, 16.55426, 17.52469, 18.99524, 20.53725,
22.07925, 22.56233, 22.69243, 22.82254, 13.57371, 15.79697, 18.02024,
20.24351, 22.34258, 24.42392, 26.50526, 26.18790, 25.07097, 23.95404,
12.53401, 16.01011, 19.48622, 22.96232, 25.68993, 28.31060, 30.93126,
29.81347, 27.44951, 25.08555, 12.98106, 16.67323, 20.36540, 24.05756,
26.36633, 28.47748, 30.58862, 29.76471, 27.96244, 26.16016, 13.92370,
17.48634, 21.04897, 24.61161, 26.15244, 27.40443, 28.65643, 28.49117,
27.85349, 27.21581, 14.86633, 18.29944, 21.73255, 25.16566, 25.93854,
26.33138, 26.72423, 27.21763, 27.74455, 28.27147, 17.60401, 17.30942,
17.01482, 16.72023, 13.94473, 10.81481, 7.68490, 8.98648, 11.76524,
14.54400, 16.04249, 16.14603, 16.24958, 16.35312, 14.85158, 13.12075,
11.38991, 12.71305, 15.05418, 17.39531, 14.48097, 14.98265, 15.48433,
15.98602, 15.75844, 15.42668, 15.09493, 16.43962, 18.34312, 20.24663,
12.91945, 13.81927, 14.71909, 15.61891, 16.66530, 17.73262, 18.79995,
20.16619, 21.63206, 23.09794, 11.68565, 13.84398, 16.00230, 18.16062,
19.91554, 21.61284, 23.31013, 23.99478, 24.34188, 24.68898, 10.49868,
14.03841, 17.57814, 21.11788, 23.50056, 25.71795, 27.93534, 27.83796,
26.96897, 26.09999, 9.31170, 14.23284, 19.15399, 24.07513, 27.08558,
29.82307, 32.56055, 31.68113, 29.59606, 27.51099, 9.98166, 14.91619,
19.85071, 24.78524, 27.51291, 29.92532, 32.33773, 31.77077, 30.21070,
28.65063, 11.27060, 15.76250, 20.25440, 24.74629, 26.88768, 28.69329,
30.49889, 30.60925, 30.15453, 29.69981, 12.55955, 16.60881, 20.65808,
24.70735, 26.26245, 27.46126, 28.66005, 29.44773, 30.09835, 30.74898,
15.20134, 15.53016, 15.85898, 16.18780, 13.53087, 10.44740, 7.36393,
8.95806, 12.11139, 15.26472, 13.87432, 14.52378, 15.17325, 15.82272,
14.49093, 12.87611, 11.26130, 12.73342, 15.23453, 17.73563, 12.54730,
13.51741, 14.48752, 15.45763, 15.45100, 15.30483, 15.15867, 16.50878,
18.35766, 20.20654, 11.22027, 12.51103, 13.80179, 15.09254, 16.41106,
17.73355, 19.05603, 20.28415, 21.48080, 22.67745, 10.27070, 12.53633,
14.80195, 17.06758, 19.04654, 20.98454, 22.92254, 23.63840, 23.94687,
24.25534, 9.37505, 12.70901, 16.04297, 19.37693, 21.92136, 24.35300,
26.78465, 26.93249, 26.31907, 25.70565, 8.47939, 12.88168, 17.28398,
21.68627, 24.79618, 27.72146, 30.64674, 30.22658, 28.69127, 27.15597,
9.77979, 13.97583, 18.17186, 22.36790, 25.18828, 27.81215, 30.43601,
30.34293, 29.34420, 28.34548, 11.81220, 15.37712, 18.94204, 22.50695,
24.75282, 26.81024, 28.86766, 29.40003, 29.42404, 29.44806, 13.84461,
16.77841, 19.71221, 22.64601, 24.31735, 25.80833, 27.29932, 28.45713,
29.50388, 30.55064, 12.05287, 13.06077, 14.06866, 15.07656, 12.81500,
10.08638, 7.35776, 9.30520, 12.81134, 16.31747, 11.31943, 12.47863,
13.63782, 14.79702, 13.82253, 12.54323, 11.26392, 12.88993, 15.48436,
18.07880, 10.58600, 11.89649, 13.20698, 14.51747, 14.83005, 15.00007,
15.17009, 16.47465, 18.15739, 19.84013, 9.85256, 11.31435, 12.77614,
14.23793, 15.83757, 17.45691, 19.07625, 20.05937, 20.83042, 21.60147,
9.36002, 11.37275, 13.38548, 15.39822, 17.58109, 19.78828, 21.99546,
22.68573, 22.87036, 23.05500, 8.90189, 11.52266, 14.14343, 16.76420,
19.42976, 22.10172, 24.77368, 25.17519, 24.81987, 24.46455, 8.44375,
11.67256, 14.90137, 18.13018, 21.27843, 24.41516, 27.55190, 27.66464,
26.76937, 25.87411, 10.51042, 13.30769, 16.10496, 18.90222, 21.70659,
24.51197, 27.31734, 27.77045, 27.43945, 27.10846, 13.41869, 15.43789,
17.45709, 19.47628, 21.66124, 23.86989, 26.07853, 27.08170, 27.68305,
28.28440, 16.32697, 17.56809, 18.80922, 20.05033, 21.61590, 23.22781,
24.83972, 26.39296, 27.92665, 29.46033, 8.90439, 10.59137, 12.27835,
13.96532, 12.09914, 9.72536, 7.35159, 9.65235, 13.51128, 17.37022,
8.76455, 10.43347, 12.10239, 13.77132, 13.15412, 12.21033, 11.26655,
13.04643, 15.73420, 18.42198, 8.62470, 10.27557, 11.92644, 13.57731,
14.20910, 14.69531, 15.18151, 16.44051, 17.95712, 19.47373, 8.48485,
10.11767, 11.75049, 13.38331, 15.26408, 17.18027, 19.09647, 19.83460,
20.18004, 20.52548, 8.44933, 10.20917, 11.96901, 13.72885, 16.11565,
18.59202, 21.06838, 21.73307, 21.79386, 21.85465, 8.42872, 10.33631,
12.24389, 14.15147, 16.93816, 19.85044, 22.76272, 23.41788, 23.32067,
23.22346, 8.40812, 10.46344, 12.51877, 14.57409, 17.76068, 21.10886,
24.45705, 25.10269, 24.84748, 24.59226, 11.24106, 12.63955, 14.03805,
15.43654, 18.22489, 21.21178, 24.19868, 25.19796, 25.53469, 25.87143,
15.02519, 15.49866, 15.97213, 16.44560, 18.56967, 20.92953, 23.28940,
24.76337, 25.94205, 27.12073, 18.80933, 18.35777, 17.90622, 17.45466,
18.91445, 20.64729, 22.38013, 24.32880, 26.34941, 28.37003,
};

View File

@ -0,0 +1,174 @@
/*
Minetest
Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "exceptions.h"
#include "objdef.h"
class TestObjDef : public TestBase
{
public:
TestObjDef() { TestManager::registerTestModule(this); }
const char *getName() { return "TestObjDef"; }
void runTests(IGameDef *gamedef);
void testHandles();
void testAddGetSetClear();
void testClone();
};
static TestObjDef g_test_instance;
void TestObjDef::runTests(IGameDef *gamedef)
{
TEST(testHandles);
TEST(testAddGetSetClear);
TEST(testClone);
}
////////////////////////////////////////////////////////////////////////////////
/* Minimal implementation of ObjDef and ObjDefManager subclass */
class MyObjDef : public ObjDef
{
public:
ObjDef *clone() const
{
auto def = new MyObjDef();
ObjDef::cloneTo(def);
def->testvalue = testvalue;
return def;
};
u32 testvalue;
};
class MyObjDefManager : public ObjDefManager
{
public:
MyObjDefManager(ObjDefType type) : ObjDefManager(NULL, type){};
MyObjDefManager *clone() const
{
auto mgr = new MyObjDefManager();
ObjDefManager::cloneTo(mgr);
return mgr;
};
protected:
MyObjDefManager(){};
};
void TestObjDef::testHandles()
{
u32 uid = 0;
u32 index = 0;
ObjDefType type = OBJDEF_GENERIC;
ObjDefHandle handle = ObjDefManager::createHandle(9530, OBJDEF_ORE, 47);
UASSERTEQ(ObjDefHandle, 0xAF507B55, handle);
UASSERT(ObjDefManager::decodeHandle(handle, &index, &type, &uid));
UASSERTEQ(u32, 9530, index);
UASSERTEQ(u32, 47, uid);
UASSERTEQ(ObjDefHandle, OBJDEF_ORE, type);
}
void TestObjDef::testAddGetSetClear()
{
ObjDefManager testmgr(NULL, OBJDEF_GENERIC);
ObjDefHandle hObj0, hObj1, hObj2, hObj3;
ObjDef *obj0, *obj1, *obj2, *obj3;
UASSERTEQ(ObjDefType, testmgr.getType(), OBJDEF_GENERIC);
obj0 = new MyObjDef;
obj0->name = "foobar";
hObj0 = testmgr.add(obj0);
UASSERT(hObj0 != OBJDEF_INVALID_HANDLE);
UASSERTEQ(u32, obj0->index, 0);
obj1 = new MyObjDef;
obj1->name = "FooBaz";
hObj1 = testmgr.add(obj1);
UASSERT(hObj1 != OBJDEF_INVALID_HANDLE);
UASSERTEQ(u32, obj1->index, 1);
obj2 = new MyObjDef;
obj2->name = "asdf";
hObj2 = testmgr.add(obj2);
UASSERT(hObj2 != OBJDEF_INVALID_HANDLE);
UASSERTEQ(u32, obj2->index, 2);
obj3 = new MyObjDef;
obj3->name = "foobaz";
hObj3 = testmgr.add(obj3);
UASSERT(hObj3 == OBJDEF_INVALID_HANDLE);
UASSERTEQ(size_t, testmgr.getNumObjects(), 3);
UASSERT(testmgr.get(hObj0) == obj0);
UASSERT(testmgr.getByName("FOOBAZ") == obj1);
UASSERT(testmgr.set(hObj0, obj3) == obj0);
UASSERT(testmgr.get(hObj0) == obj3);
delete obj0;
testmgr.clear();
UASSERTEQ(size_t, testmgr.getNumObjects(), 0);
}
void TestObjDef::testClone()
{
MyObjDefManager testmgr(OBJDEF_GENERIC);
ObjDefManager *mgrcopy;
MyObjDef *obj, *temp2;
ObjDef *temp1;
ObjDefHandle hObj;
obj = new MyObjDef;
obj->testvalue = 0xee00ff11;
hObj = testmgr.add(obj);
UASSERT(hObj != OBJDEF_INVALID_HANDLE);
mgrcopy = testmgr.clone();
UASSERT(mgrcopy);
UASSERTEQ(ObjDefType, mgrcopy->getType(), testmgr.getType());
UASSERTEQ(size_t, mgrcopy->getNumObjects(), testmgr.getNumObjects());
// 1) check that the same handle is still valid on the copy
temp1 = mgrcopy->get(hObj);
UASSERT(temp1);
UASSERT(temp1 == mgrcopy->getRaw(0));
// 2) check that the copy has the correct C++ class
temp2 = dynamic_cast<MyObjDef *>(temp1);
UASSERT(temp2);
// 3) check that it was correctly copied
UASSERTEQ(u32, obj->testvalue, temp2->testvalue);
// 4) check that it was copied AT ALL (not the same)
UASSERT(obj != temp2);
testmgr.clear();
mgrcopy->clear();
delete mgrcopy;
}

View File

@ -0,0 +1,39 @@
/*
Minetest
Copyright (C) 2010-2016 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "exceptions.h"
#include "remoteplayer.h"
#include "server.h"
class TestPlayer : public TestBase
{
public:
TestPlayer() { TestManager::registerTestModule(this); }
const char *getName() { return "TestPlayer"; }
void runTests(IGameDef *gamedef);
};
static TestPlayer g_test_instance;
void TestPlayer::runTests(IGameDef *gamedef)
{
}

View File

@ -0,0 +1,73 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "profiler.h"
class TestProfiler : public TestBase
{
public:
TestProfiler() { TestManager::registerTestModule(this); }
const char *getName() { return "TestProfiler"; }
void runTests(IGameDef *gamedef);
void testProfilerAverage();
};
static TestProfiler g_test_instance;
void TestProfiler::runTests(IGameDef *gamedef)
{
TEST(testProfilerAverage);
}
////////////////////////////////////////////////////////////////////////////////
void TestProfiler::testProfilerAverage()
{
Profiler p;
p.avg("Test1", 1.f);
UASSERT(p.getValue("Test1") == 1.f);
p.avg("Test1", 2.f);
UASSERT(p.getValue("Test1") == 1.5f);
p.avg("Test1", 3.f);
UASSERT(p.getValue("Test1") == 2.f);
p.avg("Test1", 486.f);
UASSERT(p.getValue("Test1") == 123.f);
p.avg("Test1", 8);
UASSERT(p.getValue("Test1") == 100.f);
p.avg("Test1", 700);
UASSERT(p.getValue("Test1") == 200.f);
p.avg("Test1", 10000);
UASSERT(p.getValue("Test1") == 1600.f);
p.avg("Test2", 123.56);
p.avg("Test2", 123.58);
UASSERT(p.getValue("Test2") == 123.57f);
}

View File

@ -0,0 +1,275 @@
/*
Minetest
Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <cmath>
#include "util/numeric.h"
#include "exceptions.h"
#include "noise.h"
class TestRandom : public TestBase {
public:
TestRandom() { TestManager::registerTestModule(this); }
const char *getName() { return "TestRandom"; }
void runTests(IGameDef *gamedef);
void testPseudoRandom();
void testPseudoRandomRange();
void testPcgRandom();
void testPcgRandomRange();
void testPcgRandomBytes();
void testPcgRandomNormalDist();
static const int expected_pseudorandom_results[256];
static const u32 expected_pcgrandom_results[256];
static const u8 expected_pcgrandom_bytes_result[24];
static const u8 expected_pcgrandom_bytes_result2[24];
};
static TestRandom g_test_instance;
void TestRandom::runTests(IGameDef *gamedef)
{
TEST(testPseudoRandom);
TEST(testPseudoRandomRange);
TEST(testPcgRandom);
TEST(testPcgRandomRange);
TEST(testPcgRandomBytes);
TEST(testPcgRandomNormalDist);
}
////////////////////////////////////////////////////////////////////////////////
void TestRandom::testPseudoRandom()
{
PseudoRandom pr(814538);
for (u32 i = 0; i != 256; i++)
UASSERTEQ(int, pr.next(), expected_pseudorandom_results[i]);
}
void TestRandom::testPseudoRandomRange()
{
PseudoRandom pr((int)time(NULL));
EXCEPTION_CHECK(PrngException, pr.range(2000, 6000));
EXCEPTION_CHECK(PrngException, pr.range(5, 1));
for (u32 i = 0; i != 32768; i++) {
int min = (pr.next() % 3000) - 500;
int max = (pr.next() % 3000) - 500;
if (min > max)
SWAP(int, min, max);
int randval = pr.range(min, max);
UASSERT(randval >= min);
UASSERT(randval <= max);
}
}
void TestRandom::testPcgRandom()
{
PcgRandom pr(814538, 998877);
for (u32 i = 0; i != 256; i++)
UASSERTEQ(u32, pr.next(), expected_pcgrandom_results[i]);
}
void TestRandom::testPcgRandomRange()
{
PcgRandom pr((int)time(NULL));
EXCEPTION_CHECK(PrngException, pr.range(5, 1));
// Regression test for bug 3027
pr.range(pr.RANDOM_MIN, pr.RANDOM_MAX);
for (u32 i = 0; i != 32768; i++) {
int min = (pr.next() % 3000) - 500;
int max = (pr.next() % 3000) - 500;
if (min > max)
SWAP(int, min, max);
int randval = pr.range(min, max);
UASSERT(randval >= min);
UASSERT(randval <= max);
}
}
void TestRandom::testPcgRandomBytes()
{
char buf[32];
PcgRandom r(1538, 877);
memset(buf, 0, sizeof(buf));
r.bytes(buf + 5, 23);
UASSERT(memcmp(buf + 5, expected_pcgrandom_bytes_result,
sizeof(expected_pcgrandom_bytes_result)) == 0);
memset(buf, 0, sizeof(buf));
r.bytes(buf, 17);
UASSERT(memcmp(buf, expected_pcgrandom_bytes_result2,
sizeof(expected_pcgrandom_bytes_result2)) == 0);
}
void TestRandom::testPcgRandomNormalDist()
{
static const int max = 120;
static const int min = -120;
static const int num_trials = 20;
static const u32 num_samples = 61000;
s32 bins[max - min + 1];
memset(bins, 0, sizeof(bins));
PcgRandom r(486179 + (int)time(NULL));
for (u32 i = 0; i != num_samples; i++) {
s32 randval = r.randNormalDist(min, max, num_trials);
UASSERT(randval <= max);
UASSERT(randval >= min);
bins[randval - min]++;
}
// Note that here we divide variance by the number of trials;
// this is because variance is a biased estimator.
int range = (max - min + 1);
float mean = (max + min) / 2;
float variance = ((range * range - 1) / 12) / num_trials;
float stddev = std::sqrt(variance);
static const float prediction_intervals[] = {
0.68269f, // 1.0
0.86639f, // 1.5
0.95450f, // 2.0
0.98758f, // 2.5
0.99730f, // 3.0
};
//// Simple normality test using the 68-95-99.7% rule
for (u32 i = 0; i != ARRLEN(prediction_intervals); i++) {
float deviations = i / 2.f + 1.f;
int lbound = myround(mean - deviations * stddev);
int ubound = myround(mean + deviations * stddev);
UASSERT(lbound >= min);
UASSERT(ubound <= max);
int accum = 0;
for (int j = lbound; j != ubound; j++)
accum += bins[j - min];
float actual = (float)accum / num_samples;
UASSERT(std::fabs(actual - prediction_intervals[i]) < 0.02f);
}
}
const int TestRandom::expected_pseudorandom_results[256] = {
0x02fa, 0x60d5, 0x6c10, 0x606b, 0x098b, 0x5f1e, 0x4f56, 0x3fbd, 0x77af,
0x4fe9, 0x419a, 0x6fe1, 0x177b, 0x6858, 0x36f8, 0x6d83, 0x14fc, 0x2d62,
0x1077, 0x23e2, 0x041b, 0x7a7e, 0x5b52, 0x215d, 0x682b, 0x4716, 0x47e3,
0x08c0, 0x1952, 0x56ae, 0x146d, 0x4b4f, 0x239f, 0x3fd0, 0x6794, 0x7796,
0x7be2, 0x75b7, 0x5691, 0x28ee, 0x2656, 0x40c0, 0x133c, 0x63cd, 0x2aeb,
0x518f, 0x7dbc, 0x6ad8, 0x736e, 0x5b05, 0x160b, 0x589f, 0x6f64, 0x5edc,
0x092c, 0x0a39, 0x199e, 0x1927, 0x562b, 0x2689, 0x3ba3, 0x366f, 0x46da,
0x4e49, 0x0abb, 0x40a1, 0x3846, 0x40db, 0x7adb, 0x6ec1, 0x6efa, 0x01cc,
0x6335, 0x4352, 0x72fb, 0x4b2d, 0x509a, 0x257e, 0x2f7d, 0x5891, 0x2195,
0x6107, 0x5269, 0x56e3, 0x4849, 0x38f7, 0x2791, 0x04f2, 0x4e05, 0x78ff,
0x6bae, 0x50b3, 0x74ad, 0x31af, 0x531e, 0x7d56, 0x11c9, 0x0b5e, 0x405e,
0x1e15, 0x7f6a, 0x5bd3, 0x6649, 0x71b4, 0x3ec2, 0x6ab4, 0x520e, 0x6ad6,
0x287e, 0x10b8, 0x18f2, 0x7107, 0x46ea, 0x1d85, 0x25cc, 0x2689, 0x35c1,
0x3065, 0x6237, 0x3edd, 0x23d9, 0x6fb5, 0x37a1, 0x3211, 0x526a, 0x4b09,
0x23f1, 0x58cc, 0x2e42, 0x341f, 0x5e16, 0x3d1a, 0x5e8c, 0x7a82, 0x4635,
0x2bf8, 0x6577, 0x3603, 0x1daf, 0x539f, 0x2e91, 0x6bd8, 0x42d3, 0x7a93,
0x26e3, 0x5a91, 0x6c67, 0x1b66, 0x3ac7, 0x18bf, 0x20d8, 0x7153, 0x558d,
0x7262, 0x653d, 0x417d, 0x3ed3, 0x3117, 0x600d, 0x6d04, 0x719c, 0x3afd,
0x6ba5, 0x17c5, 0x4935, 0x346c, 0x5479, 0x6ff6, 0x1fcc, 0x1054, 0x3f14,
0x6266, 0x3acc, 0x3b77, 0x71d8, 0x478b, 0x20fa, 0x4e46, 0x7e77, 0x5554,
0x3652, 0x719c, 0x072b, 0x61ad, 0x399f, 0x621d, 0x1bba, 0x41d0, 0x7fdc,
0x3e6c, 0x6a2a, 0x5253, 0x094e, 0x0c10, 0x3f43, 0x73eb, 0x4c5f, 0x1f23,
0x12c9, 0x0902, 0x5238, 0x50c0, 0x1b77, 0x3ffd, 0x0124, 0x302a, 0x26b9,
0x3648, 0x30a6, 0x1abc, 0x3031, 0x4029, 0x6358, 0x6696, 0x74e8, 0x6142,
0x4284, 0x0c00, 0x7e50, 0x41e3, 0x3782, 0x79a5, 0x60fe, 0x2d15, 0x3ed2,
0x7f70, 0x2b27, 0x6366, 0x5100, 0x7c44, 0x3ee0, 0x4e76, 0x7d34, 0x3a60,
0x140e, 0x613d, 0x1193, 0x268d, 0x1e2f, 0x3123, 0x6d61, 0x4e0b, 0x51ce,
0x13bf, 0x58d4, 0x4f43, 0x05c6, 0x4d6a, 0x7eb5, 0x2921, 0x2c36, 0x1c89,
0x63b9, 0x1555, 0x1f41, 0x2d9f,
};
const u32 TestRandom::expected_pcgrandom_results[256] = {
0x48c593f8, 0x054f59f5, 0x0d062dc1, 0x23852a23, 0x7fbbc97b, 0x1f9f141e,
0x364e6ed8, 0x995bba58, 0xc9307dc0, 0x73fb34c4, 0xcd8de88d, 0x52e8ce08,
0x1c4a78e4, 0x25c0882e, 0x8a82e2e0, 0xe3bc3311, 0xb8068d42, 0x73186110,
0x19988df4, 0x69bd970b, 0x7214728c, 0x0aee320c, 0x2a5a536c, 0xaf48d715,
0x00bce504, 0xd2b8f548, 0x520df366, 0x96d8fff5, 0xa1bb510b, 0x63477049,
0xb85990b7, 0x7e090689, 0x275fb468, 0x50206257, 0x8bab4f8a, 0x0d6823db,
0x63faeaac, 0x2d92deeb, 0x2ba78024, 0x0d30f631, 0x338923a0, 0xd07248d8,
0xa5db62d3, 0xddba8af6, 0x0ad454e9, 0x6f0fd13a, 0xbbfde2bf, 0x91188009,
0x966b394d, 0xbb9d2012, 0x7e6926cb, 0x95183860, 0x5ff4c59b, 0x035f628a,
0xb67085ef, 0x33867e23, 0x68d1b887, 0x2e3298d7, 0x84fd0650, 0x8bc91141,
0x6fcb0452, 0x2836fee9, 0x2e83c0a3, 0xf1bafdc5, 0x9ff77777, 0xfdfbba87,
0x527aebeb, 0x423e5248, 0xd1756490, 0xe41148fa, 0x3361f7b4, 0xa2824f23,
0xf4e08072, 0xc50442be, 0x35adcc21, 0x36be153c, 0xc7709012, 0xf0eeb9f2,
0x3d73114e, 0x1c1574ee, 0x92095b9c, 0x1503d01c, 0xd6ce0677, 0x026a8ec1,
0x76d0084d, 0x86c23633, 0x36f75ce6, 0x08fa7bbe, 0x35f6ff2a, 0x31cc9525,
0x2c1a35e6, 0x8effcd62, 0xc782fa07, 0x8a86e248, 0x8fdb7a9b, 0x77246626,
0x5767723f, 0x3a78b699, 0xe548ce1c, 0x5820f37d, 0x148ed9b8, 0xf6796254,
0x32232c20, 0x392bf3a2, 0xe9af6625, 0xd40b0d88, 0x636cfa23, 0x6a5de514,
0xc4a69183, 0xc785c853, 0xab0de901, 0x16ae7e44, 0x376f13b5, 0x070f7f31,
0x34cbc93b, 0xe6184345, 0x1b7f911f, 0x631fbe4b, 0x86d6e023, 0xc689b518,
0x88ef4f7c, 0xddf06b45, 0xc97f18d4, 0x2aaee94b, 0x45694723, 0x6db111d2,
0x91974fce, 0xe33e29e2, 0xc5e99494, 0x8017e02b, 0x3ebd8143, 0x471ffb80,
0xc0d7ca1b, 0x4954c860, 0x48935d6a, 0xf2d27999, 0xb93d608d, 0x40696e90,
0x60b18162, 0x1a156998, 0x09b8bbab, 0xc80a79b6, 0x8adbcfbc, 0xc375248c,
0xa584e2ea, 0x5b46fe11, 0x58e84680, 0x8a8bc456, 0xd668b94f, 0x8b9035be,
0x278509d4, 0x6663a140, 0x81a9817a, 0xd4f9d3cf, 0x6dc5f607, 0x6ae04450,
0x694f22a4, 0x1d061788, 0x2e39ad8b, 0x748f4db2, 0xee569b52, 0xd157166d,
0xdabc161e, 0xc8d50176, 0x7e3110e5, 0x9f7d033b, 0x128df67f, 0xb0078583,
0xa3a75d26, 0xc1ad8011, 0x07dd89ec, 0xef04f456, 0x91bf866c, 0x6aac5306,
0xdd5a1573, 0xf73ff97a, 0x4e1186ad, 0xb9680680, 0xc8894515, 0xdc95a08e,
0xc894fd8e, 0xf84ade15, 0xd787f8c1, 0x40dcecca, 0x1b24743e, 0x1ce6ab23,
0x72321653, 0xb80fbaf7, 0x1bcf099b, 0x1ff26805, 0x78f66c8e, 0xf93bf51a,
0xfb0c06fe, 0xe50d48cf, 0x310947e0, 0x1b78804a, 0xe73e2c14, 0x8deb8381,
0xe576122a, 0xe5a8df39, 0x42397c5e, 0xf5503f3c, 0xbe3dbf8d, 0x1b360e5c,
0x9254caaf, 0x7a9f6744, 0x6d4144fa, 0xd77c65fe, 0x44ca7b12, 0xf58a4c00,
0x159500d0, 0x92769857, 0x7134fdd4, 0xa3fea693, 0xbd044831, 0xeded39a1,
0xe4570204, 0xaea37f2f, 0x9a302971, 0x620f8402, 0x1d2f3e5e, 0xf9c2f49c,
0x738e813a, 0xb3c92251, 0x7ecba63b, 0xbe7eebc7, 0xf800267c, 0x3fdeb760,
0xf12d5e7d, 0x5a18dce1, 0xb35a539c, 0xe565f057, 0x2babf38c, 0xae5800ad,
0x421004dd, 0x6715acb6, 0xff529b64, 0xd520d207, 0x7cb193e7, 0xe9b18e4c,
0xfd2a8a59, 0x47826ae3, 0x56ba43f8, 0x453b3d99, 0x8ae1675f, 0xf66f5c34,
0x057a6ac1, 0x010769e4, 0xa8324158, 0x410379a5, 0x5dfc8c97, 0x72848afe,
0x59f169e5, 0xe32acb78, 0x5dfaa9c4, 0x51bb956a,
};
const u8 TestRandom::expected_pcgrandom_bytes_result[24] = {
0xf3, 0x79, 0x8f, 0x31, 0xac, 0xd9, 0x34, 0xf8, 0x3c, 0x6e, 0x82, 0x37,
0x6b, 0x4b, 0x77, 0xe3, 0xbd, 0x0a, 0xee, 0x22, 0x79, 0x6e, 0x40, 0x00,
};
const u8 TestRandom::expected_pcgrandom_bytes_result2[24] = {
0x47, 0x9e, 0x08, 0x3e, 0xd4, 0x21, 0x2d, 0xf6, 0xb4, 0xb1, 0x9d, 0x7a,
0x60, 0x02, 0x5a, 0xb2, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

View File

@ -0,0 +1,280 @@
/*
Minetest
Copyright (C) 2010-2014 kwolekr, Ryan Kwolek <kwolekr@minetest.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "mapgen/mg_schematic.h"
#include "gamedef.h"
#include "nodedef.h"
class TestSchematic : public TestBase {
public:
TestSchematic() { TestManager::registerTestModule(this); }
const char *getName() { return "TestSchematic"; }
void runTests(IGameDef *gamedef);
void testMtsSerializeDeserialize(const NodeDefManager *ndef);
void testLuaTableSerialize(const NodeDefManager *ndef);
void testFileSerializeDeserialize(const NodeDefManager *ndef);
static const content_t test_schem1_data[7 * 6 * 4];
static const content_t test_schem2_data[3 * 3 * 3];
static const u8 test_schem2_prob[3 * 3 * 3];
static const char *expected_lua_output;
};
static TestSchematic g_test_instance;
void TestSchematic::runTests(IGameDef *gamedef)
{
NodeDefManager *ndef =
(NodeDefManager *)gamedef->getNodeDefManager();
ndef->setNodeRegistrationStatus(true);
TEST(testMtsSerializeDeserialize, ndef);
TEST(testLuaTableSerialize, ndef);
TEST(testFileSerializeDeserialize, ndef);
ndef->resetNodeResolveState();
}
////////////////////////////////////////////////////////////////////////////////
void TestSchematic::testMtsSerializeDeserialize(const NodeDefManager *ndef)
{
static const v3s16 size(7, 6, 4);
static const u32 volume = size.X * size.Y * size.Z;
std::stringstream ss(std::ios_base::binary |
std::ios_base::in | std::ios_base::out);
std::vector<std::string> names;
names.emplace_back("foo");
names.emplace_back("bar");
names.emplace_back("baz");
names.emplace_back("qux");
Schematic schem, schem2;
schem.flags = 0;
schem.size = size;
schem.schemdata = new MapNode[volume];
schem.slice_probs = new u8[size.Y];
for (size_t i = 0; i != volume; i++)
schem.schemdata[i] = MapNode(test_schem1_data[i], MTSCHEM_PROB_ALWAYS, 0);
for (s16 y = 0; y != size.Y; y++)
schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS;
UASSERT(schem.serializeToMts(&ss, names));
ss.seekg(0);
names.clear();
UASSERT(schem2.deserializeFromMts(&ss, &names));
UASSERTEQ(size_t, names.size(), 4);
UASSERTEQ(std::string, names[0], "foo");
UASSERTEQ(std::string, names[1], "bar");
UASSERTEQ(std::string, names[2], "baz");
UASSERTEQ(std::string, names[3], "qux");
UASSERT(schem2.size == size);
for (size_t i = 0; i != volume; i++)
UASSERT(schem2.schemdata[i] == schem.schemdata[i]);
for (s16 y = 0; y != size.Y; y++)
UASSERTEQ(u8, schem2.slice_probs[y], schem.slice_probs[y]);
}
void TestSchematic::testLuaTableSerialize(const NodeDefManager *ndef)
{
static const v3s16 size(3, 3, 3);
static const u32 volume = size.X * size.Y * size.Z;
Schematic schem;
schem.flags = 0;
schem.size = size;
schem.schemdata = new MapNode[volume];
schem.slice_probs = new u8[size.Y];
for (size_t i = 0; i != volume; i++)
schem.schemdata[i] = MapNode(test_schem2_data[i], test_schem2_prob[i], 0);
for (s16 y = 0; y != size.Y; y++)
schem.slice_probs[y] = MTSCHEM_PROB_ALWAYS;
std::vector<std::string> names;
names.emplace_back("air");
names.emplace_back("default:lava_source");
names.emplace_back("default:glass");
std::ostringstream ss(std::ios_base::binary);
UASSERT(schem.serializeToLua(&ss, names, false, 0));
UASSERTEQ(std::string, ss.str(), expected_lua_output);
}
void TestSchematic::testFileSerializeDeserialize(const NodeDefManager *ndef)
{
static const v3s16 size(3, 3, 3);
static const u32 volume = size.X * size.Y * size.Z;
static const content_t content_map[] = {
CONTENT_AIR,
t_CONTENT_STONE,
t_CONTENT_LAVA,
};
static const content_t content_map2[] = {
CONTENT_AIR,
t_CONTENT_STONE,
t_CONTENT_WATER,
};
StringMap replace_names;
replace_names["default:lava"] = "default:water";
Schematic schem1, schem2;
//// Construct the schematic to save
schem1.flags = 0;
schem1.size = size;
schem1.schemdata = new MapNode[volume];
schem1.slice_probs = new u8[size.Y];
schem1.slice_probs[0] = 80;
schem1.slice_probs[1] = 160;
schem1.slice_probs[2] = 240;
for (size_t i = 0; i != volume; i++) {
content_t c = content_map[test_schem2_data[i]];
schem1.schemdata[i] = MapNode(c, test_schem2_prob[i], 0);
}
std::string temp_file = getTestTempFile();
UASSERT(schem1.saveSchematicToFile(temp_file, ndef));
UASSERT(schem2.loadSchematicFromFile(temp_file, ndef, &replace_names));
UASSERT(schem2.size == size);
UASSERT(schem2.slice_probs[0] == 80);
UASSERT(schem2.slice_probs[1] == 160);
UASSERT(schem2.slice_probs[2] == 240);
for (size_t i = 0; i != volume; i++) {
content_t c = content_map2[test_schem2_data[i]];
UASSERT(schem2.schemdata[i] == MapNode(c, test_schem2_prob[i], 0));
}
}
// Should form a cross-shaped-thing...?
const content_t TestSchematic::test_schem1_data[7 * 6 * 4] = {
3, 3, 1, 1, 1, 3, 3, // Y=0, Z=0
3, 0, 1, 2, 1, 0, 3, // Y=1, Z=0
3, 0, 1, 2, 1, 0, 3, // Y=2, Z=0
3, 1, 1, 2, 1, 1, 3, // Y=3, Z=0
3, 2, 2, 2, 2, 2, 3, // Y=4, Z=0
3, 1, 1, 2, 1, 1, 3, // Y=5, Z=0
0, 0, 1, 1, 1, 0, 0, // Y=0, Z=1
0, 0, 1, 2, 1, 0, 0, // Y=1, Z=1
0, 0, 1, 2, 1, 0, 0, // Y=2, Z=1
1, 1, 1, 2, 1, 1, 1, // Y=3, Z=1
1, 2, 2, 2, 2, 2, 1, // Y=4, Z=1
1, 1, 1, 2, 1, 1, 1, // Y=5, Z=1
0, 0, 1, 1, 1, 0, 0, // Y=0, Z=2
0, 0, 1, 2, 1, 0, 0, // Y=1, Z=2
0, 0, 1, 2, 1, 0, 0, // Y=2, Z=2
1, 1, 1, 2, 1, 1, 1, // Y=3, Z=2
1, 2, 2, 2, 2, 2, 1, // Y=4, Z=2
1, 1, 1, 2, 1, 1, 1, // Y=5, Z=2
3, 3, 1, 1, 1, 3, 3, // Y=0, Z=3
3, 0, 1, 2, 1, 0, 3, // Y=1, Z=3
3, 0, 1, 2, 1, 0, 3, // Y=2, Z=3
3, 1, 1, 2, 1, 1, 3, // Y=3, Z=3
3, 2, 2, 2, 2, 2, 3, // Y=4, Z=3
3, 1, 1, 2, 1, 1, 3, // Y=5, Z=3
};
const content_t TestSchematic::test_schem2_data[3 * 3 * 3] = {
0, 0, 0,
0, 2, 0,
0, 0, 0,
0, 2, 0,
2, 1, 2,
0, 2, 0,
0, 0, 0,
0, 2, 0,
0, 0, 0,
};
const u8 TestSchematic::test_schem2_prob[3 * 3 * 3] = {
0x00, 0x00, 0x00,
0x00, 0xFF, 0x00,
0x00, 0x00, 0x00,
0x00, 0xFF, 0x00,
0xFF, 0xFF, 0xFF,
0x00, 0xFF, 0x00,
0x00, 0x00, 0x00,
0x00, 0xFF, 0x00,
0x00, 0x00, 0x00,
};
const char *TestSchematic::expected_lua_output =
"schematic = {\n"
"\tsize = {x=3, y=3, z=3},\n"
"\tyslice_prob = {\n"
"\t\t{ypos=0, prob=254},\n"
"\t\t{ypos=1, prob=254},\n"
"\t\t{ypos=2, prob=254},\n"
"\t},\n"
"\tdata = {\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
"\t\t{name=\"default:lava_source\", prob=254, param2=0, force_place=true},\n"
"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"default:glass\", prob=254, param2=0, force_place=true},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t\t{name=\"air\", prob=0, param2=0},\n"
"\t},\n"
"}\n";

View File

@ -0,0 +1,395 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "util/string.h"
#include "util/serialize.h"
#include <cmath>
class TestSerialization : public TestBase {
public:
TestSerialization() { TestManager::registerTestModule(this); }
const char *getName() { return "TestSerialization"; }
void runTests(IGameDef *gamedef);
void buildTestStrings();
void testSerializeString();
void testSerializeLongString();
void testSerializeJsonString();
void testDeSerializeString();
void testDeSerializeLongString();
void testStreamRead();
void testStreamWrite();
void testFloatFormat();
std::string teststring2;
std::wstring teststring2_w;
std::string teststring2_w_encoded;
static const u8 test_serialized_data[12 * 11 - 2];
};
static TestSerialization g_test_instance;
void TestSerialization::runTests(IGameDef *gamedef)
{
buildTestStrings();
TEST(testSerializeString);
TEST(testDeSerializeString);
TEST(testSerializeLongString);
TEST(testDeSerializeLongString);
TEST(testSerializeJsonString);
TEST(testStreamRead);
TEST(testStreamWrite);
TEST(testFloatFormat);
}
////////////////////////////////////////////////////////////////////////////////
// To be used like this:
// mkstr("Some\0string\0with\0embedded\0nuls")
// since std::string("...") doesn't work as expected in that case.
template<size_t N> std::string mkstr(const char (&s)[N])
{
return std::string(s, N - 1);
}
void TestSerialization::buildTestStrings()
{
std::ostringstream tmp_os;
std::wostringstream tmp_os_w;
std::ostringstream tmp_os_w_encoded;
for (int i = 0; i < 256; i++) {
tmp_os << (char)i;
tmp_os_w << (wchar_t)i;
tmp_os_w_encoded << (char)0 << (char)i;
}
teststring2 = tmp_os.str();
teststring2_w = tmp_os_w.str();
teststring2_w_encoded = tmp_os_w_encoded.str();
}
void TestSerialization::testSerializeString()
{
// Test blank string
UASSERT(serializeString16("") == mkstr("\0\0"));
// Test basic string
UASSERT(serializeString16("Hello world!") == mkstr("\0\14Hello world!"));
// Test character range
UASSERT(serializeString16(teststring2) == mkstr("\1\0") + teststring2);
}
void TestSerialization::testDeSerializeString()
{
// Test deserialize
{
std::istringstream is(serializeString16(teststring2), std::ios::binary);
UASSERT(deSerializeString16(is) == teststring2);
UASSERT(!is.eof());
is.get();
UASSERT(is.eof());
}
// Test deserialize an incomplete length specifier
{
std::istringstream is(mkstr("\x53"), std::ios::binary);
EXCEPTION_CHECK(SerializationError, deSerializeString16(is));
}
// Test deserialize a string with incomplete data
{
std::istringstream is(mkstr("\x00\x55 abcdefg"), std::ios::binary);
EXCEPTION_CHECK(SerializationError, deSerializeString16(is));
}
}
void TestSerialization::testSerializeLongString()
{
// Test blank string
UASSERT(serializeString32("") == mkstr("\0\0\0\0"));
// Test basic string
UASSERT(serializeString32("Hello world!") == mkstr("\0\0\0\14Hello world!"));
// Test character range
UASSERT(serializeString32(teststring2) == mkstr("\0\0\1\0") + teststring2);
}
void TestSerialization::testDeSerializeLongString()
{
// Test deserialize
{
std::istringstream is(serializeString32(teststring2), std::ios::binary);
UASSERT(deSerializeString32(is) == teststring2);
UASSERT(!is.eof());
is.get();
UASSERT(is.eof());
}
// Test deserialize an incomplete length specifier
{
std::istringstream is(mkstr("\x53"), std::ios::binary);
EXCEPTION_CHECK(SerializationError, deSerializeString32(is));
}
// Test deserialize a string with incomplete data
{
std::istringstream is(mkstr("\x00\x00\x00\x05 abc"), std::ios::binary);
EXCEPTION_CHECK(SerializationError, deSerializeString32(is));
}
// Test deserialize a string with a length too large
{
std::istringstream is(mkstr("\xFF\xFF\xFF\xFF blah"), std::ios::binary);
EXCEPTION_CHECK(SerializationError, deSerializeString32(is));
}
}
void TestSerialization::testSerializeJsonString()
{
// Test blank string
UASSERT(serializeJsonString("") == "\"\"");
// Test basic string
UASSERT(serializeJsonString("Hello world!") == "\"Hello world!\"");
// MSVC fails when directly using "\\\\"
std::string backslash = "\\";
UASSERT(serializeJsonString(teststring2) ==
mkstr("\"") +
"\\u0000\\u0001\\u0002\\u0003\\u0004\\u0005\\u0006\\u0007" +
"\\b\\t\\n\\u000b\\f\\r\\u000e\\u000f" +
"\\u0010\\u0011\\u0012\\u0013\\u0014\\u0015\\u0016\\u0017" +
"\\u0018\\u0019\\u001a\\u001b\\u001c\\u001d\\u001e\\u001f" +
" !\\\"" + teststring2.substr(0x23, 0x2f-0x23) +
"\\/" + teststring2.substr(0x30, 0x5c-0x30) +
backslash + backslash + teststring2.substr(0x5d, 0x7f-0x5d) + "\\u007f" +
"\\u0080\\u0081\\u0082\\u0083\\u0084\\u0085\\u0086\\u0087" +
"\\u0088\\u0089\\u008a\\u008b\\u008c\\u008d\\u008e\\u008f" +
"\\u0090\\u0091\\u0092\\u0093\\u0094\\u0095\\u0096\\u0097" +
"\\u0098\\u0099\\u009a\\u009b\\u009c\\u009d\\u009e\\u009f" +
"\\u00a0\\u00a1\\u00a2\\u00a3\\u00a4\\u00a5\\u00a6\\u00a7" +
"\\u00a8\\u00a9\\u00aa\\u00ab\\u00ac\\u00ad\\u00ae\\u00af" +
"\\u00b0\\u00b1\\u00b2\\u00b3\\u00b4\\u00b5\\u00b6\\u00b7" +
"\\u00b8\\u00b9\\u00ba\\u00bb\\u00bc\\u00bd\\u00be\\u00bf" +
"\\u00c0\\u00c1\\u00c2\\u00c3\\u00c4\\u00c5\\u00c6\\u00c7" +
"\\u00c8\\u00c9\\u00ca\\u00cb\\u00cc\\u00cd\\u00ce\\u00cf" +
"\\u00d0\\u00d1\\u00d2\\u00d3\\u00d4\\u00d5\\u00d6\\u00d7" +
"\\u00d8\\u00d9\\u00da\\u00db\\u00dc\\u00dd\\u00de\\u00df" +
"\\u00e0\\u00e1\\u00e2\\u00e3\\u00e4\\u00e5\\u00e6\\u00e7" +
"\\u00e8\\u00e9\\u00ea\\u00eb\\u00ec\\u00ed\\u00ee\\u00ef" +
"\\u00f0\\u00f1\\u00f2\\u00f3\\u00f4\\u00f5\\u00f6\\u00f7" +
"\\u00f8\\u00f9\\u00fa\\u00fb\\u00fc\\u00fd\\u00fe\\u00ff" +
"\"");
// Test deserialize
std::istringstream is(serializeJsonString(teststring2), std::ios::binary);
UASSERT(deSerializeJsonString(is) == teststring2);
UASSERT(!is.eof());
is.get();
UASSERT(is.eof());
}
void TestSerialization::testStreamRead()
{
std::string datastr(
(const char *)test_serialized_data,
sizeof(test_serialized_data));
std::istringstream is(datastr, std::ios_base::binary);
UASSERT(readU8(is) == 0x11);
UASSERT(readU16(is) == 0x2233);
UASSERT(readU32(is) == 0x44556677);
UASSERT(readU64(is) == 0x8899AABBCCDDEEFFLL);
UASSERT(readS8(is) == -128);
UASSERT(readS16(is) == 30000);
UASSERT(readS32(is) == -6);
UASSERT(readS64(is) == -43);
UASSERT(readF1000(is) == 53.534f);
UASSERT(readF1000(is) == -300000.32f);
UASSERT(readF1000(is) == F1000_MIN);
UASSERT(readF1000(is) == F1000_MAX);
UASSERT(deSerializeString16(is) == "foobar!");
UASSERT(readV2S16(is) == v2s16(500, 500));
UASSERT(readV3S16(is) == v3s16(4207, 604, -30));
UASSERT(readV2S32(is) == v2s32(1920, 1080));
UASSERT(readV3S32(is) == v3s32(-400, 6400054, 290549855));
UASSERT(readV3F1000(is) == v3f(500, 10024.2f, -192.54f));
UASSERT(readARGB8(is) == video::SColor(255, 128, 50, 128));
UASSERT(deSerializeString32(is) == "some longer string here");
UASSERT(is.rdbuf()->in_avail() == 2);
UASSERT(readU16(is) == 0xF00D);
UASSERT(is.rdbuf()->in_avail() == 0);
}
void TestSerialization::testStreamWrite()
{
std::ostringstream os(std::ios_base::binary);
std::string data;
writeU8(os, 0x11);
writeU16(os, 0x2233);
writeU32(os, 0x44556677);
writeU64(os, 0x8899AABBCCDDEEFFLL);
writeS8(os, -128);
writeS16(os, 30000);
writeS32(os, -6);
writeS64(os, -43);
writeF1000(os, 53.53467f);
writeF1000(os, -300000.32f);
writeF1000(os, F1000_MIN);
writeF1000(os, F1000_MAX);
os << serializeString16("foobar!");
data = os.str();
UASSERT(data.size() < sizeof(test_serialized_data));
UASSERT(!memcmp(&data[0], test_serialized_data, data.size()));
writeV2S16(os, v2s16(500, 500));
writeV3S16(os, v3s16(4207, 604, -30));
writeV2S32(os, v2s32(1920, 1080));
writeV3S32(os, v3s32(-400, 6400054, 290549855));
writeV3F1000(os, v3f(500, 10024.2f, -192.54f));
writeARGB8(os, video::SColor(255, 128, 50, 128));
os << serializeString32("some longer string here");
writeU16(os, 0xF00D);
data = os.str();
UASSERT(data.size() == sizeof(test_serialized_data));
UASSERT(!memcmp(&data[0], test_serialized_data, sizeof(test_serialized_data)));
}
void TestSerialization::testFloatFormat()
{
FloatType type = getFloatSerializationType();
u32 i;
f32 fs, fm;
// Check precision of float calculations on this platform
const std::unordered_map<f32, u32> float_results = {
{ 0.0f, 0x00000000UL },
{ 1.0f, 0x3F800000UL },
{ -1.0f, 0xBF800000UL },
{ 0.1f, 0x3DCCCCCDUL },
{ -0.1f, 0xBDCCCCCDUL },
{ 1945329.25f, 0x49ED778AUL },
{ -23298764.f, 0xCBB1C166UL },
{ 0.5f, 0x3F000000UL },
{ -0.5f, 0xBF000000UL }
};
for (const auto &v : float_results) {
i = f32Tou32Slow(v.first);
if (std::abs((s64)v.second - i) > 32) {
printf("Inaccurate float values on %.9g, expected 0x%X, actual 0x%X\n",
v.first, v.second, i);
UASSERT(false);
}
fs = u32Tof32Slow(v.second);
if (std::fabs(v.first - fs) > std::fabs(v.first * 0.000005f)) {
printf("Inaccurate float values on 0x%X, expected %.9g, actual 0x%.9g\n",
v.second, v.first, fs);
UASSERT(false);
}
}
if (type == FLOATTYPE_SLOW) {
// conversion using memcpy is not possible
// Skip exact float comparison checks below
return;
}
// The code below compares the IEEE conversion functions with a
// known good IEC559/IEEE754 implementation. This test neeeds
// IEC559 compliance in the compiler.
#if defined(__GNUC__) && (!defined(__STDC_IEC_559__) || defined(__FAST_MATH__))
// GNU C++ lies about its IEC559 support when -ffast-math is active.
// https://gcc.gnu.org/bugzilla//show_bug.cgi?id=84949
bool is_iec559 = false;
#else
bool is_iec559 = std::numeric_limits<f32>::is_iec559;
#endif
if (!is_iec559)
return;
auto test_single = [&fs, &fm](const u32 &i) -> bool {
memcpy(&fm, &i, 4);
fs = u32Tof32Slow(i);
if (fm != fs) {
printf("u32Tof32Slow failed on 0x%X, expected %.9g, actual %.9g\n",
i, fm, fs);
return false;
}
if (f32Tou32Slow(fs) != i) {
printf("f32Tou32Slow failed on %.9g, expected 0x%X, actual 0x%X\n",
fs, i, f32Tou32Slow(fs));
return false;
}
return true;
};
// Use step of prime 277 to speed things up from 3 minutes to a few seconds
// Test from 0 to 0xFF800000UL (positive)
for (i = 0x00000000UL; i <= 0x7F800000UL; i += 277)
UASSERT(test_single(i));
// Ensure +inf and -inf are tested
UASSERT(test_single(0x7F800000UL));
UASSERT(test_single(0xFF800000UL));
// Test from 0x80000000UL to 0xFF800000UL (negative)
for (i = 0x80000000UL; i <= 0xFF800000UL; i += 277)
UASSERT(test_single(i));
}
const u8 TestSerialization::test_serialized_data[12 * 11 - 2] = {
0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc,
0xdd, 0xee, 0xff, 0x80, 0x75, 0x30, 0xff, 0xff, 0xff, 0xfa, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xd5, 0x00, 0x00, 0xd1, 0x1e, 0xee, 0x1e,
0x5b, 0xc0, 0x80, 0x00, 0x02, 0x80, 0x7F, 0xFF, 0xFD, 0x80, 0x00, 0x07,
0x66, 0x6f, 0x6f, 0x62, 0x61, 0x72, 0x21, 0x01, 0xf4, 0x01, 0xf4, 0x10,
0x6f, 0x02, 0x5c, 0xff, 0xe2, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x04,
0x38, 0xff, 0xff, 0xfe, 0x70, 0x00, 0x61, 0xa8, 0x36, 0x11, 0x51, 0x70,
0x5f, 0x00, 0x07, 0xa1, 0x20, 0x00, 0x98, 0xf5, 0x08, 0xff,
0xfd, 0x0f, 0xe4, 0xff, 0x80, 0x32, 0x80, 0x00, 0x00, 0x00, 0x17, 0x73,
0x6f, 0x6d, 0x65, 0x20, 0x6c, 0x6f, 0x6e, 0x67, 0x65, 0x72, 0x20, 0x73,
0x74, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x68, 0x65, 0x72, 0x65, 0xF0, 0x0D,
};

View File

@ -0,0 +1,122 @@
/*
Minetest
Copyright (C) 2018 nerzhul, Loic BLOT <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include <server.h>
#include "test.h"
#include "util/string.h"
#include "util/serialize.h"
class FakeServer : public Server
{
public:
// clang-format off
FakeServer() : Server("fakeworld", SubgameSpec("fakespec", "fakespec"), true,
Address(), true, nullptr)
{
}
// clang-format on
private:
void SendChatMessage(session_t peer_id, const ChatMessage &message)
{
// NOOP
}
};
class TestServerShutdownState : public TestBase
{
public:
TestServerShutdownState() { TestManager::registerTestModule(this); }
const char *getName() { return "TestServerShutdownState"; }
void runTests(IGameDef *gamedef);
void testInit();
void testReset();
void testTrigger();
void testTick();
};
static TestServerShutdownState g_test_instance;
void TestServerShutdownState::runTests(IGameDef *gamedef)
{
TEST(testInit);
TEST(testReset);
TEST(testTrigger);
TEST(testTick);
}
void TestServerShutdownState::testInit()
{
Server::ShutdownState ss;
UASSERT(!ss.is_requested);
UASSERT(!ss.should_reconnect);
UASSERT(ss.message.empty());
UASSERT(ss.m_timer == 0.0f);
}
void TestServerShutdownState::testReset()
{
Server::ShutdownState ss;
ss.reset();
UASSERT(!ss.is_requested);
UASSERT(!ss.should_reconnect);
UASSERT(ss.message.empty());
UASSERT(ss.m_timer == 0.0f);
}
void TestServerShutdownState::testTrigger()
{
Server::ShutdownState ss;
ss.trigger(3.0f, "testtrigger", true);
UASSERT(!ss.is_requested);
UASSERT(ss.should_reconnect);
UASSERT(ss.message == "testtrigger");
UASSERT(ss.m_timer == 3.0f);
}
void TestServerShutdownState::testTick()
{
std::unique_ptr<FakeServer> fakeServer(new FakeServer());
Server::ShutdownState ss;
ss.trigger(28.0f, "testtrigger", true);
ss.tick(0.0f, fakeServer.get());
// Tick with no time should not change anything
UASSERT(!ss.is_requested);
UASSERT(ss.should_reconnect);
UASSERT(ss.message == "testtrigger");
UASSERT(ss.m_timer == 28.0f);
// Tick 2 seconds
ss.tick(2.0f, fakeServer.get());
UASSERT(!ss.is_requested);
UASSERT(ss.should_reconnect);
UASSERT(ss.message == "testtrigger");
UASSERT(ss.m_timer == 26.0f);
// Tick remaining seconds + additional expire
ss.tick(26.1f, fakeServer.get());
UASSERT(ss.is_requested);
UASSERT(ss.should_reconnect);
UASSERT(ss.message == "testtrigger");
UASSERT(ss.m_timer == 0.0f);
}

View File

@ -0,0 +1,200 @@
/*
Minetest
Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "server/activeobjectmgr.h"
#include <algorithm>
#include <queue>
#include "test.h"
#include "profiler.h"
class TestServerActiveObject : public ServerActiveObject
{
public:
TestServerActiveObject(const v3f &p = v3f()) : ServerActiveObject(nullptr, p) {}
~TestServerActiveObject() = default;
ActiveObjectType getType() const override { return ACTIVEOBJECT_TYPE_TEST; }
bool getCollisionBox(aabb3f *toset) const override { return false; }
bool getSelectionBox(aabb3f *toset) const override { return false; }
bool collideWithObjects() const override { return false; }
};
class TestServerActiveObjectMgr : public TestBase
{
public:
TestServerActiveObjectMgr() { TestManager::registerTestModule(this); }
const char *getName() { return "TestServerActiveObjectMgr"; }
void runTests(IGameDef *gamedef);
void testFreeID();
void testRegisterObject();
void testRemoveObject();
void testGetObjectsInsideRadius();
void testGetAddedActiveObjectsAroundPos();
};
static TestServerActiveObjectMgr g_test_instance;
void TestServerActiveObjectMgr::runTests(IGameDef *gamedef)
{
TEST(testFreeID);
TEST(testRegisterObject)
TEST(testRemoveObject)
TEST(testGetObjectsInsideRadius);
TEST(testGetAddedActiveObjectsAroundPos);
}
void clearSAOMgr(server::ActiveObjectMgr *saomgr)
{
auto clear_cb = [](ServerActiveObject *obj, u16 id) {
delete obj;
return true;
};
saomgr->clear(clear_cb);
}
////////////////////////////////////////////////////////////////////////////////
void TestServerActiveObjectMgr::testFreeID()
{
server::ActiveObjectMgr saomgr;
std::vector<u16> aoids;
u16 aoid = saomgr.getFreeId();
// Ensure it's not the same id
UASSERT(saomgr.getFreeId() != aoid);
aoids.push_back(aoid);
// Register basic objects, ensure we never found
for (u8 i = 0; i < UINT8_MAX; i++) {
// Register an object
auto tsao = new TestServerActiveObject();
saomgr.registerObject(tsao);
aoids.push_back(tsao->getId());
// Ensure next id is not in registered list
UASSERT(std::find(aoids.begin(), aoids.end(), saomgr.getFreeId()) ==
aoids.end());
}
clearSAOMgr(&saomgr);
}
void TestServerActiveObjectMgr::testRegisterObject()
{
server::ActiveObjectMgr saomgr;
auto tsao = new TestServerActiveObject();
UASSERT(saomgr.registerObject(tsao));
u16 id = tsao->getId();
auto tsaoToCompare = saomgr.getActiveObject(id);
UASSERT(tsaoToCompare->getId() == id);
UASSERT(tsaoToCompare == tsao);
tsao = new TestServerActiveObject();
UASSERT(saomgr.registerObject(tsao));
UASSERT(saomgr.getActiveObject(tsao->getId()) == tsao);
UASSERT(saomgr.getActiveObject(tsao->getId()) != tsaoToCompare);
clearSAOMgr(&saomgr);
}
void TestServerActiveObjectMgr::testRemoveObject()
{
server::ActiveObjectMgr saomgr;
auto tsao = new TestServerActiveObject();
UASSERT(saomgr.registerObject(tsao));
u16 id = tsao->getId();
UASSERT(saomgr.getActiveObject(id) != nullptr)
saomgr.removeObject(tsao->getId());
UASSERT(saomgr.getActiveObject(id) == nullptr);
clearSAOMgr(&saomgr);
}
void TestServerActiveObjectMgr::testGetObjectsInsideRadius()
{
server::ActiveObjectMgr saomgr;
static const v3f sao_pos[] = {
v3f(10, 40, 10),
v3f(740, 100, -304),
v3f(-200, 100, -304),
v3f(740, -740, -304),
v3f(1500, -740, -304),
};
for (const auto &p : sao_pos) {
saomgr.registerObject(new TestServerActiveObject(p));
}
std::vector<ServerActiveObject *> result;
saomgr.getObjectsInsideRadius(v3f(), 50, result, nullptr);
UASSERTCMP(int, ==, result.size(), 1);
result.clear();
saomgr.getObjectsInsideRadius(v3f(), 750, result, nullptr);
UASSERTCMP(int, ==, result.size(), 2);
result.clear();
saomgr.getObjectsInsideRadius(v3f(), 750000, result, nullptr);
UASSERTCMP(int, ==, result.size(), 5);
result.clear();
auto include_obj_cb = [](ServerActiveObject *obj) {
return (obj->getBasePosition().X != 10);
};
saomgr.getObjectsInsideRadius(v3f(), 750000, result, include_obj_cb);
UASSERTCMP(int, ==, result.size(), 4);
clearSAOMgr(&saomgr);
}
void TestServerActiveObjectMgr::testGetAddedActiveObjectsAroundPos()
{
server::ActiveObjectMgr saomgr;
static const v3f sao_pos[] = {
v3f(10, 40, 10),
v3f(740, 100, -304),
v3f(-200, 100, -304),
v3f(740, -740, -304),
v3f(1500, -740, -304),
};
for (const auto &p : sao_pos) {
saomgr.registerObject(new TestServerActiveObject(p));
}
std::queue<u16> result;
std::set<u16> cur_objects;
saomgr.getAddedActiveObjectsAroundPos(v3f(), 100, 50, cur_objects, result);
UASSERTCMP(int, ==, result.size(), 1);
result = std::queue<u16>();
cur_objects.clear();
saomgr.getAddedActiveObjectsAroundPos(v3f(), 740, 50, cur_objects, result);
UASSERTCMP(int, ==, result.size(), 2);
clearSAOMgr(&saomgr);
}

View File

@ -0,0 +1,172 @@
/*
Minetest
Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <algorithm>
#include "server/mods.h"
#include "settings.h"
#include "test_config.h"
class TestServerModManager : public TestBase
{
public:
TestServerModManager() { TestManager::registerTestModule(this); }
const char *getName() { return "TestServerModManager"; }
void runTests(IGameDef *gamedef);
void testCreation();
void testIsConsistent();
void testUnsatisfiedMods();
void testGetMods();
void testGetModsWrongDir();
void testGetModspec();
void testGetModNamesWrongDir();
void testGetModNames();
void testGetModMediaPathsWrongDir();
void testGetModMediaPaths();
};
static TestServerModManager g_test_instance;
void TestServerModManager::runTests(IGameDef *gamedef)
{
const char *saved_env_mt_subgame_path = getenv("MINETEST_SUBGAME_PATH");
#ifdef WIN32
{
std::string subgame_path("MINETEST_SUBGAME_PATH=");
subgame_path.append(TEST_SUBGAME_PATH);
_putenv(subgame_path.c_str());
}
#else
setenv("MINETEST_SUBGAME_PATH", TEST_SUBGAME_PATH, 1);
#endif
TEST(testCreation);
TEST(testIsConsistent);
TEST(testGetModsWrongDir);
TEST(testUnsatisfiedMods);
TEST(testGetMods);
TEST(testGetModspec);
TEST(testGetModNamesWrongDir);
TEST(testGetModNames);
TEST(testGetModMediaPathsWrongDir);
TEST(testGetModMediaPaths);
#ifdef WIN32
{
std::string subgame_path("MINETEST_SUBGAME_PATH=");
if (saved_env_mt_subgame_path)
subgame_path.append(saved_env_mt_subgame_path);
_putenv(subgame_path.c_str());
}
#else
if (saved_env_mt_subgame_path)
setenv("MINETEST_SUBGAME_PATH", saved_env_mt_subgame_path, 1);
else
unsetenv("MINETEST_SUBGAME_PATH");
#endif
}
void TestServerModManager::testCreation()
{
std::string path = std::string(TEST_WORLDDIR) + DIR_DELIM + "world.mt";
Settings world_config;
world_config.set("gameid", "devtest");
UASSERTEQ(bool, world_config.updateConfigFile(path.c_str()), true);
ServerModManager sm(TEST_WORLDDIR);
}
void TestServerModManager::testGetModsWrongDir()
{
// Test in non worlddir to ensure no mods are found
ServerModManager sm(std::string(TEST_WORLDDIR) + DIR_DELIM + "..");
UASSERTEQ(bool, sm.getMods().empty(), true);
}
void TestServerModManager::testUnsatisfiedMods()
{
ServerModManager sm(std::string(TEST_WORLDDIR));
UASSERTEQ(bool, sm.getUnsatisfiedMods().empty(), true);
}
void TestServerModManager::testIsConsistent()
{
ServerModManager sm(std::string(TEST_WORLDDIR));
UASSERTEQ(bool, sm.isConsistent(), true);
}
void TestServerModManager::testGetMods()
{
ServerModManager sm(std::string(TEST_WORLDDIR));
const auto &mods = sm.getMods();
UASSERTEQ(bool, mods.empty(), false);
// Ensure we found basenodes mod (part of devtest)
bool default_found = false;
for (const auto &m : mods) {
if (m.name == "basenodes")
default_found = true;
// Verify if paths are not empty
UASSERTEQ(bool, m.path.empty(), false);
}
UASSERTEQ(bool, default_found, true);
}
void TestServerModManager::testGetModspec()
{
ServerModManager sm(std::string(TEST_WORLDDIR));
UASSERTEQ(const ModSpec *, sm.getModSpec("wrongmod"), NULL);
UASSERT(sm.getModSpec("basenodes") != NULL);
}
void TestServerModManager::testGetModNamesWrongDir()
{
ServerModManager sm(std::string(TEST_WORLDDIR) + DIR_DELIM + "..");
std::vector<std::string> result;
sm.getModNames(result);
UASSERTEQ(bool, result.empty(), true);
}
void TestServerModManager::testGetModNames()
{
ServerModManager sm(std::string(TEST_WORLDDIR));
std::vector<std::string> result;
sm.getModNames(result);
UASSERTEQ(bool, result.empty(), false);
UASSERT(std::find(result.begin(), result.end(), "basenodes") != result.end());
}
void TestServerModManager::testGetModMediaPathsWrongDir()
{
ServerModManager sm(std::string(TEST_WORLDDIR) + DIR_DELIM + "..");
std::vector<std::string> result;
sm.getModsMediaPaths(result);
UASSERTEQ(bool, result.empty(), true);
}
void TestServerModManager::testGetModMediaPaths()
{
ServerModManager sm(std::string(TEST_WORLDDIR));
std::vector<std::string> result;
sm.getModsMediaPaths(result);
UASSERTEQ(bool, result.empty(), false);
}

View File

@ -0,0 +1,245 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <cmath>
#include "settings.h"
#include "noise.h"
class TestSettings : public TestBase {
public:
TestSettings() { TestManager::registerTestModule(this); }
const char *getName() { return "TestSettings"; }
void runTests(IGameDef *gamedef);
void testAllSettings();
void testFlagDesc();
static const char *config_text_before;
static const std::string config_text_after;
};
static TestSettings g_test_instance;
void TestSettings::runTests(IGameDef *gamedef)
{
TEST(testAllSettings);
TEST(testFlagDesc);
}
////////////////////////////////////////////////////////////////////////////////
const char *TestSettings::config_text_before =
"leet = 1337\n"
"leetleet = 13371337\n"
"leetleet_neg = -13371337\n"
"floaty_thing = 1.1\n"
"stringy_thing = asd /( ¤%&(/\" BLÖÄRP\n"
"coord = (1, 2, 4.5)\n"
" # this is just a comment\n"
"this is an invalid line\n"
"asdf = {\n"
" a = 5\n"
" bb = 2.5\n"
" ccc = \"\"\"\n"
"testy\n"
" testa \n"
"\"\"\"\n"
"\n"
"}\n"
"blarg = \"\"\" \n"
"some multiline text\n"
" with leading whitespace!\n"
"\"\"\"\n"
"np_terrain = 5, 40, (250, 250, 250), 12341, 5, 0.7, 2.4\n"
"zoop = true";
const std::string TestSettings::config_text_after =
"leet = 1337\n"
"leetleet = 13371337\n"
"leetleet_neg = -13371337\n"
"floaty_thing = 1.1\n"
"stringy_thing = asd /( ¤%&(/\" BLÖÄRP\n"
"coord = (1, 2, 4.5)\n"
" # this is just a comment\n"
"this is an invalid line\n"
"asdf = {\n"
" a = 5\n"
" bb = 2.5\n"
" ccc = \"\"\"\n"
"testy\n"
" testa \n"
"\"\"\"\n"
"\n"
"}\n"
"blarg = \"\"\" \n"
"some multiline text\n"
" with leading whitespace!\n"
"\"\"\"\n"
"np_terrain = {\n"
" flags = defaults\n"
" lacunarity = 2.4\n"
" octaves = 6\n"
" offset = 3.5\n"
" persistence = 0.7\n"
" scale = 40\n"
" seed = 12341\n"
" spread = (250,250,250)\n"
"}\n"
"zoop = true\n"
"coord2 = (1,2,3.3)\n"
"floaty_thing_2 = 1.2\n"
"groupy_thing = {\n"
" animals = cute\n"
" num_apples = 4\n"
" num_oranges = 53\n"
"}\n";
void TestSettings::testAllSettings()
{
try {
Settings s;
// Test reading of settings
std::istringstream is(config_text_before);
s.parseConfigLines(is);
UASSERT(s.getS32("leet") == 1337);
UASSERT(s.getS16("leetleet") == 32767);
UASSERT(s.getS16("leetleet_neg") == -32768);
// Not sure if 1.1 is an exact value as a float, but doesn't matter
UASSERT(fabs(s.getFloat("floaty_thing") - 1.1) < 0.001);
UASSERT(s.get("stringy_thing") == "asd /( ¤%&(/\" BLÖÄRP");
UASSERT(fabs(s.getV3F("coord").X - 1.0) < 0.001);
UASSERT(fabs(s.getV3F("coord").Y - 2.0) < 0.001);
UASSERT(fabs(s.getV3F("coord").Z - 4.5) < 0.001);
// Test the setting of settings too
s.setFloat("floaty_thing_2", 1.2);
s.setV3F("coord2", v3f(1, 2, 3.3));
UASSERT(s.get("floaty_thing_2").substr(0,3) == "1.2");
UASSERT(fabs(s.getFloat("floaty_thing_2") - 1.2) < 0.001);
UASSERT(fabs(s.getV3F("coord2").X - 1.0) < 0.001);
UASSERT(fabs(s.getV3F("coord2").Y - 2.0) < 0.001);
UASSERT(fabs(s.getV3F("coord2").Z - 3.3) < 0.001);
// Test settings groups
Settings *group = s.getGroup("asdf");
UASSERT(group != NULL);
UASSERT(s.getGroupNoEx("zoop", group) == false);
UASSERT(group->getS16("a") == 5);
UASSERT(fabs(group->getFloat("bb") - 2.5) < 0.001);
Settings group3;
group3.set("cat", "meow");
group3.set("dog", "woof");
Settings group2;
group2.setS16("num_apples", 4);
group2.setS16("num_oranges", 53);
group2.setGroup("animals", group3);
group2.set("animals", "cute"); //destroys group 3
s.setGroup("groupy_thing", group2);
// Test set failure conditions
UASSERT(s.set("Zoop = Poop\nsome_other_setting", "false") == false);
UASSERT(s.set("sneaky", "\"\"\"\njabberwocky = false") == false);
UASSERT(s.set("hehe", "asdfasdf\n\"\"\"\nsomething = false") == false);
// Test multiline settings
UASSERT(group->get("ccc") == "testy\n testa ");
UASSERT(s.get("blarg") ==
"some multiline text\n"
" with leading whitespace!");
// Test NoiseParams
UASSERT(s.getEntry("np_terrain").is_group == false);
NoiseParams np;
UASSERT(s.getNoiseParams("np_terrain", np) == true);
UASSERT(std::fabs(np.offset - 5) < 0.001f);
UASSERT(std::fabs(np.scale - 40) < 0.001f);
UASSERT(std::fabs(np.spread.X - 250) < 0.001f);
UASSERT(std::fabs(np.spread.Y - 250) < 0.001f);
UASSERT(std::fabs(np.spread.Z - 250) < 0.001f);
UASSERT(np.seed == 12341);
UASSERT(np.octaves == 5);
UASSERT(std::fabs(np.persist - 0.7) < 0.001f);
np.offset = 3.5;
np.octaves = 6;
s.setNoiseParams("np_terrain", np);
UASSERT(s.getEntry("np_terrain").is_group == true);
// Test writing
std::ostringstream os(std::ios_base::binary);
is.clear();
is.seekg(0);
UASSERT(s.updateConfigObject(is, os, "", 0) == true);
//printf(">>>> expected config:\n%s\n", TEST_CONFIG_TEXT_AFTER);
//printf(">>>> actual config:\n%s\n", os.str().c_str());
#if __cplusplus < 201103L
// This test only works in older C++ versions than C++11 because we use unordered_map
UASSERT(os.str() == config_text_after);
#endif
} catch (SettingNotFoundException &e) {
UASSERT(!"Setting not found!");
}
}
void TestSettings::testFlagDesc()
{
Settings s;
FlagDesc flagdesc[] = {
{ "biomes", 0x01 },
{ "trees", 0x02 },
{ "jungles", 0x04 },
{ "oranges", 0x08 },
{ "tables", 0x10 },
{ nullptr, 0 }
};
// Enabled: biomes, jungles, oranges (default)
s.setDefault("test_desc", flagdesc, readFlagString(
"biomes,notrees,jungles,oranges", flagdesc, nullptr));
UASSERT(s.getFlagStr("test_desc", flagdesc, nullptr) == (0x01 | 0x04 | 0x08));
// Enabled: jungles, oranges, tables
s.set("test_desc", "nobiomes,tables");
UASSERT(s.getFlagStr("test_desc", flagdesc, nullptr) == (0x04 | 0x08 | 0x10));
// Enabled: (nothing)
s.set("test_desc", "nobiomes,nojungles,nooranges,notables");
UASSERT(s.getFlagStr("test_desc", flagdesc, nullptr) == 0x00);
// Numeric flag tests (override)
// Enabled: trees, tables
s.setDefault("test_flags", flagdesc, 0x02 | 0x10);
UASSERT(s.getFlagStr("test_flags", flagdesc, nullptr) == (0x02 | 0x10));
// Enabled: tables
s.set("test_flags", "16");
UASSERT(s.getFlagStr("test_flags", flagdesc, nullptr) == 0x10);
}

View File

@ -0,0 +1,151 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "log.h"
#include "settings.h"
#include "network/socket.h"
class TestSocket : public TestBase {
public:
TestSocket()
{
if (INTERNET_SIMULATOR == false)
TestManager::registerTestModule(this);
}
const char *getName() { return "TestSocket"; }
void runTests(IGameDef *gamedef);
void testIPv4Socket();
void testIPv6Socket();
static const int port = 30003;
};
static TestSocket g_test_instance;
void TestSocket::runTests(IGameDef *gamedef)
{
TEST(testIPv4Socket);
if (g_settings->getBool("enable_ipv6"))
TEST(testIPv6Socket);
}
////////////////////////////////////////////////////////////////////////////////
void TestSocket::testIPv4Socket()
{
Address address(0, 0, 0, 0, port);
Address bind_addr(0, 0, 0, 0, port);
/*
* Try to use the bind_address for servers with no localhost address
* For example: FreeBSD jails
*/
std::string bind_str = g_settings->get("bind_address");
try {
bind_addr.Resolve(bind_str.c_str());
if (!bind_addr.isIPv6()) {
address = bind_addr;
}
} catch (ResolveError &e) {
}
UDPSocket socket(false);
socket.Bind(address);
const char sendbuffer[] = "hello world!";
/*
* If there is a bind address, use it.
* It's useful in container environments
*/
if (address != Address(0, 0, 0, 0, port))
socket.Send(address, sendbuffer, sizeof(sendbuffer));
else
socket.Send(Address(127, 0, 0, 1, port), sendbuffer, sizeof(sendbuffer));
sleep_ms(50);
char rcvbuffer[256] = { 0 };
Address sender;
for (;;) {
if (socket.Receive(sender, rcvbuffer, sizeof(rcvbuffer)) < 0)
break;
}
//FIXME: This fails on some systems
UASSERT(strncmp(sendbuffer, rcvbuffer, sizeof(sendbuffer)) == 0);
if (address != Address(0, 0, 0, 0, port)) {
UASSERT(sender.getAddress().sin_addr.s_addr ==
address.getAddress().sin_addr.s_addr);
} else {
UASSERT(sender.getAddress().sin_addr.s_addr ==
Address(127, 0, 0, 1, 0).getAddress().sin_addr.s_addr);
}
}
void TestSocket::testIPv6Socket()
{
Address address6((IPv6AddressBytes *)NULL, port);
UDPSocket socket6;
if (!socket6.init(true, true)) {
/* Note: Failing to create an IPv6 socket is not technically an
error because the OS may not support IPv6 or it may
have been disabled. IPv6 is not /required/ by
minetest and therefore this should not cause the unit
test to fail
*/
dstream << "WARNING: IPv6 socket creation failed (unit test)"
<< std::endl;
return;
}
const char sendbuffer[] = "hello world!";
IPv6AddressBytes bytes;
bytes.bytes[15] = 1;
socket6.Bind(address6);
try {
socket6.Send(Address(&bytes, port), sendbuffer, sizeof(sendbuffer));
sleep_ms(50);
char rcvbuffer[256] = { 0 };
Address sender;
for(;;) {
if (socket6.Receive(sender, rcvbuffer, sizeof(rcvbuffer)) < 0)
break;
}
//FIXME: This fails on some systems
UASSERT(strncmp(sendbuffer, rcvbuffer, sizeof(sendbuffer)) == 0);
UASSERT(memcmp(sender.getAddress6().sin6_addr.s6_addr,
Address(&bytes, 0).getAddress6().sin6_addr.s6_addr, 16) == 0);
} catch (SendFailedException &e) {
errorstream << "IPv6 support enabled but not available!"
<< std::endl;
}
}

View File

@ -0,0 +1,158 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <atomic>
#include "threading/semaphore.h"
#include "threading/thread.h"
class TestThreading : public TestBase {
public:
TestThreading() { TestManager::registerTestModule(this); }
const char *getName() { return "TestThreading"; }
void runTests(IGameDef *gamedef);
void testStartStopWait();
void testAtomicSemaphoreThread();
};
static TestThreading g_test_instance;
void TestThreading::runTests(IGameDef *gamedef)
{
TEST(testStartStopWait);
TEST(testAtomicSemaphoreThread);
}
class SimpleTestThread : public Thread {
public:
SimpleTestThread(unsigned int interval) :
Thread("SimpleTest"),
m_interval(interval)
{
}
private:
void *run()
{
void *retval = this;
if (isCurrentThread() == false)
retval = (void *)0xBAD;
while (!stopRequested())
sleep_ms(m_interval);
return retval;
}
unsigned int m_interval;
};
void TestThreading::testStartStopWait()
{
void *thread_retval;
SimpleTestThread *thread = new SimpleTestThread(25);
// Try this a couple times, since a Thread should be reusable after waiting
for (size_t i = 0; i != 5; i++) {
// Can't wait() on a joined, stopped thread
UASSERT(thread->wait() == false);
// start() should work the first time, but not the second.
UASSERT(thread->start() == true);
UASSERT(thread->start() == false);
UASSERT(thread->isRunning() == true);
UASSERT(thread->isCurrentThread() == false);
// Let it loop a few times...
sleep_ms(70);
// It's still running, so the return value shouldn't be available to us.
UASSERT(thread->getReturnValue(&thread_retval) == false);
// stop() should always succeed
UASSERT(thread->stop() == true);
// wait() only needs to wait the first time - the other two are no-ops.
UASSERT(thread->wait() == true);
UASSERT(thread->wait() == false);
UASSERT(thread->wait() == false);
// Now that the thread is stopped, we should be able to get the
// return value, and it should be the object itself.
thread_retval = NULL;
UASSERT(thread->getReturnValue(&thread_retval) == true);
UASSERT(thread_retval == thread);
}
delete thread;
}
class AtomicTestThread : public Thread {
public:
AtomicTestThread(std::atomic<u32> &v, Semaphore &trigger) :
Thread("AtomicTest"),
val(v),
trigger(trigger)
{
}
private:
void *run()
{
trigger.wait();
for (u32 i = 0; i < 0x10000; ++i)
++val;
return NULL;
}
std::atomic<u32> &val;
Semaphore &trigger;
};
void TestThreading::testAtomicSemaphoreThread()
{
std::atomic<u32> val;
val = 0;
Semaphore trigger;
static const u8 num_threads = 4;
AtomicTestThread *threads[num_threads];
for (auto &thread : threads) {
thread = new AtomicTestThread(val, trigger);
UASSERT(thread->start());
}
trigger.post(num_threads);
for (AtomicTestThread *thread : threads) {
thread->wait();
delete thread;
}
UASSERT(val == num_threads * 0x10000);
}

View File

@ -0,0 +1,530 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <cmath>
#include "util/enriched_string.h"
#include "util/numeric.h"
#include "util/string.h"
class TestUtilities : public TestBase {
public:
TestUtilities() { TestManager::registerTestModule(this); }
const char *getName() { return "TestUtilities"; }
void runTests(IGameDef *gamedef);
void testAngleWrapAround();
void testWrapDegrees_0_360_v3f();
void testLowercase();
void testTrim();
void testIsYes();
void testRemoveStringEnd();
void testUrlEncode();
void testUrlDecode();
void testPadString();
void testStartsWith();
void testStrEqual();
void testStringTrim();
void testStrToIntConversion();
void testStringReplace();
void testStringAllowed();
void testAsciiPrintableHelper();
void testUTF8();
void testRemoveEscapes();
void testWrapRows();
void testEnrichedString();
void testIsNumber();
void testIsPowerOfTwo();
void testMyround();
void testStringJoin();
void testEulerConversion();
};
static TestUtilities g_test_instance;
void TestUtilities::runTests(IGameDef *gamedef)
{
TEST(testAngleWrapAround);
TEST(testWrapDegrees_0_360_v3f);
TEST(testLowercase);
TEST(testTrim);
TEST(testIsYes);
TEST(testRemoveStringEnd);
TEST(testUrlEncode);
TEST(testUrlDecode);
TEST(testPadString);
TEST(testStartsWith);
TEST(testStrEqual);
TEST(testStringTrim);
TEST(testStrToIntConversion);
TEST(testStringReplace);
TEST(testStringAllowed);
TEST(testAsciiPrintableHelper);
TEST(testUTF8);
TEST(testRemoveEscapes);
TEST(testWrapRows);
TEST(testEnrichedString);
TEST(testIsNumber);
TEST(testIsPowerOfTwo);
TEST(testMyround);
TEST(testStringJoin);
TEST(testEulerConversion);
}
////////////////////////////////////////////////////////////////////////////////
inline float ref_WrapDegrees180(float f)
{
// This is a slower alternative to the wrapDegrees_180() function;
// used as a reference for testing
float value = fmodf(f + 180, 360);
if (value < 0)
value += 360;
return value - 180;
}
inline float ref_WrapDegrees_0_360(float f)
{
// This is a slower alternative to the wrapDegrees_0_360() function;
// used as a reference for testing
float value = fmodf(f, 360);
if (value < 0)
value += 360;
return value < 0 ? value + 360 : value;
}
void TestUtilities::testAngleWrapAround() {
UASSERT(fabs(modulo360f(100.0) - 100.0) < 0.001);
UASSERT(fabs(modulo360f(720.5) - 0.5) < 0.001);
UASSERT(fabs(modulo360f(-0.5) - (-0.5)) < 0.001);
UASSERT(fabs(modulo360f(-365.5) - (-5.5)) < 0.001);
for (float f = -720; f <= -360; f += 0.25) {
UASSERT(std::fabs(modulo360f(f) - modulo360f(f + 360)) < 0.001);
}
for (float f = -1440; f <= 1440; f += 0.25) {
UASSERT(std::fabs(modulo360f(f) - fmodf(f, 360)) < 0.001);
UASSERT(std::fabs(wrapDegrees_180(f) - ref_WrapDegrees180(f)) < 0.001);
UASSERT(std::fabs(wrapDegrees_0_360(f) - ref_WrapDegrees_0_360(f)) < 0.001);
UASSERT(wrapDegrees_0_360(
std::fabs(wrapDegrees_180(f) - wrapDegrees_0_360(f))) < 0.001);
}
}
void TestUtilities::testWrapDegrees_0_360_v3f()
{
// only x test with little step
for (float x = -720.f; x <= 720; x += 0.05) {
v3f r = wrapDegrees_0_360_v3f(v3f(x, 0, 0));
UASSERT(r.X >= 0.0f && r.X < 360.0f)
UASSERT(r.Y == 0.0f)
UASSERT(r.Z == 0.0f)
}
// only y test with little step
for (float y = -720.f; y <= 720; y += 0.05) {
v3f r = wrapDegrees_0_360_v3f(v3f(0, y, 0));
UASSERT(r.X == 0.0f)
UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
UASSERT(r.Z == 0.0f)
}
// only z test with little step
for (float z = -720.f; z <= 720; z += 0.05) {
v3f r = wrapDegrees_0_360_v3f(v3f(0, 0, z));
UASSERT(r.X == 0.0f)
UASSERT(r.Y == 0.0f)
UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
}
// test the whole coordinate translation
for (float x = -720.f; x <= 720; x += 2.5) {
for (float y = -720.f; y <= 720; y += 2.5) {
for (float z = -720.f; z <= 720; z += 2.5) {
v3f r = wrapDegrees_0_360_v3f(v3f(x, y, z));
UASSERT(r.X >= 0.0f && r.X < 360.0f)
UASSERT(r.Y >= 0.0f && r.Y < 360.0f)
UASSERT(r.Z >= 0.0f && r.Z < 360.0f)
}
}
}
}
void TestUtilities::testLowercase()
{
UASSERT(lowercase("Foo bAR") == "foo bar");
UASSERT(lowercase("eeeeeeaaaaaaaaaaaààààà") == "eeeeeeaaaaaaaaaaaààààà");
UASSERT(lowercase("MINETEST-powa") == "minetest-powa");
}
void TestUtilities::testTrim()
{
UASSERT(trim("") == "");
UASSERT(trim("dirt_with_grass") == "dirt_with_grass");
UASSERT(trim("\n \t\r Foo bAR \r\n\t\t ") == "Foo bAR");
UASSERT(trim("\n \t\r \r\n\t\t ") == "");
}
void TestUtilities::testIsYes()
{
UASSERT(is_yes("YeS") == true);
UASSERT(is_yes("") == false);
UASSERT(is_yes("FAlse") == false);
UASSERT(is_yes("-1") == true);
UASSERT(is_yes("0") == false);
UASSERT(is_yes("1") == true);
UASSERT(is_yes("2") == true);
}
void TestUtilities::testRemoveStringEnd()
{
const char *ends[] = {"abc", "c", "bc", "", NULL};
UASSERT(removeStringEnd("abc", ends) == "");
UASSERT(removeStringEnd("bc", ends) == "b");
UASSERT(removeStringEnd("12c", ends) == "12");
UASSERT(removeStringEnd("foo", ends) == "");
}
void TestUtilities::testUrlEncode()
{
UASSERT(urlencode("\"Aardvarks lurk, OK?\"")
== "%22Aardvarks%20lurk%2C%20OK%3F%22");
}
void TestUtilities::testUrlDecode()
{
UASSERT(urldecode("%22Aardvarks%20lurk%2C%20OK%3F%22")
== "\"Aardvarks lurk, OK?\"");
}
void TestUtilities::testPadString()
{
UASSERT(padStringRight("hello", 8) == "hello ");
}
void TestUtilities::testStartsWith()
{
UASSERT(str_starts_with(std::string(), std::string()) == true);
UASSERT(str_starts_with(std::string("the sharp pickaxe"),
std::string()) == true);
UASSERT(str_starts_with(std::string("the sharp pickaxe"),
std::string("the")) == true);
UASSERT(str_starts_with(std::string("the sharp pickaxe"),
std::string("The")) == false);
UASSERT(str_starts_with(std::string("the sharp pickaxe"),
std::string("The"), true) == true);
UASSERT(str_starts_with(std::string("T"), std::string("The")) == false);
}
void TestUtilities::testStrEqual()
{
UASSERT(str_equal(narrow_to_wide("abc"), narrow_to_wide("abc")));
UASSERT(str_equal(narrow_to_wide("ABC"), narrow_to_wide("abc"), true));
}
void TestUtilities::testStringTrim()
{
UASSERT(trim(" a") == "a");
UASSERT(trim(" a ") == "a");
UASSERT(trim("a ") == "a");
UASSERT(trim("") == "");
}
void TestUtilities::testStrToIntConversion()
{
UASSERT(mystoi("123", 0, 1000) == 123);
UASSERT(mystoi("123", 0, 10) == 10);
}
void TestUtilities::testStringReplace()
{
std::string test_str;
test_str = "Hello there";
str_replace(test_str, "there", "world");
UASSERT(test_str == "Hello world");
test_str = "ThisAisAaAtest";
str_replace(test_str, 'A', ' ');
UASSERT(test_str == "This is a test");
}
void TestUtilities::testStringAllowed()
{
UASSERT(string_allowed("hello", "abcdefghijklmno") == true);
UASSERT(string_allowed("123", "abcdefghijklmno") == false);
UASSERT(string_allowed_blacklist("hello", "123") == true);
UASSERT(string_allowed_blacklist("hello123", "123") == false);
}
void TestUtilities::testAsciiPrintableHelper()
{
UASSERT(IS_ASCII_PRINTABLE_CHAR('e') == true);
UASSERT(IS_ASCII_PRINTABLE_CHAR('\0') == false);
// Ensures that there is no cutting off going on...
// If there were, 331 would be cut to 75 in this example
// and 73 is a valid ASCII char.
int ch = 331;
UASSERT(IS_ASCII_PRINTABLE_CHAR(ch) == false);
}
void TestUtilities::testUTF8()
{
UASSERT(wide_to_utf8(utf8_to_wide("")) == "");
UASSERT(wide_to_utf8(utf8_to_wide("the shovel dug a crumbly node!"))
== "the shovel dug a crumbly node!");
}
void TestUtilities::testRemoveEscapes()
{
UASSERT(unescape_enriched<wchar_t>(
L"abc\x1bXdef") == L"abcdef");
UASSERT(unescape_enriched<wchar_t>(
L"abc\x1b(escaped)def") == L"abcdef");
UASSERT(unescape_enriched<wchar_t>(
L"abc\x1b((escaped with parenthesis\\))def") == L"abcdef");
UASSERT(unescape_enriched<wchar_t>(
L"abc\x1b(incomplete") == L"abc");
UASSERT(unescape_enriched<wchar_t>(
L"escape at the end\x1b") == L"escape at the end");
// Nested escapes not supported
UASSERT(unescape_enriched<wchar_t>(
L"abc\x1b(outer \x1b(inner escape)escape)def") == L"abcescape)def");
}
void TestUtilities::testWrapRows()
{
UASSERT(wrap_rows("12345678",4) == "1234\n5678");
// test that wrap_rows doesn't wrap inside multibyte sequences
{
const unsigned char s[] = {
0x2f, 0x68, 0x6f, 0x6d, 0x65, 0x2f, 0x72, 0x61, 0x70, 0x74, 0x6f,
0x72, 0x2f, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0x2f,
0x6d, 0x69, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x74, 0x2f, 0x62, 0x69,
0x6e, 0x2f, 0x2e, 0x2e, 0};
std::string str((char *)s);
UASSERT(utf8_to_wide(wrap_rows(str, 20)) != L"<invalid UTF-8 string>");
};
{
const unsigned char s[] = {
0x74, 0x65, 0x73, 0x74, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81,
0xd1, 0x82, 0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82,
0x20, 0xd1, 0x82, 0xd0, 0xb5, 0xd1, 0x81, 0xd1, 0x82, 0};
std::string str((char *)s);
UASSERT(utf8_to_wide(wrap_rows(str, 8)) != L"<invalid UTF-8 string>");
}
}
void TestUtilities::testEnrichedString()
{
EnrichedString str(L"Test bar");
irr::video::SColor color(0xFF, 0, 0, 0xFF);
UASSERT(str.substr(1, 3).getString() == L"est");
str += L" BUZZ";
UASSERT(str.substr(9, std::string::npos).getString() == L"BUZZ");
str.setDefaultColor(color); // Blue foreground
UASSERT(str.getColors()[5] == color);
// Green background, then white and yellow text
str = L"\x1b(b@#0F0)Regular \x1b(c@#FF0)yellow";
UASSERT(str.getColors()[2] == 0xFFFFFFFF);
str.setDefaultColor(color); // Blue foreground
UASSERT(str.getColors()[13] == 0xFFFFFF00); // Still yellow text
UASSERT(str.getBackground() == 0xFF00FF00); // Green background
}
void TestUtilities::testIsNumber()
{
UASSERT(is_number("123") == true);
UASSERT(is_number("") == false);
UASSERT(is_number("123a") == false);
}
void TestUtilities::testIsPowerOfTwo()
{
UASSERT(is_power_of_two(0) == false);
UASSERT(is_power_of_two(1) == true);
UASSERT(is_power_of_two(2) == true);
UASSERT(is_power_of_two(3) == false);
for (int exponent = 2; exponent <= 31; ++exponent) {
UASSERT(is_power_of_two((1 << exponent) - 1) == false);
UASSERT(is_power_of_two((1 << exponent)) == true);
UASSERT(is_power_of_two((1 << exponent) + 1) == false);
}
UASSERT(is_power_of_two(U32_MAX) == false);
}
void TestUtilities::testMyround()
{
UASSERT(myround(4.6f) == 5);
UASSERT(myround(1.2f) == 1);
UASSERT(myround(-3.1f) == -3);
UASSERT(myround(-6.5f) == -7);
}
void TestUtilities::testStringJoin()
{
std::vector<std::string> input;
UASSERT(str_join(input, ",") == "");
input.emplace_back("one");
UASSERT(str_join(input, ",") == "one");
input.emplace_back("two");
UASSERT(str_join(input, ",") == "one,two");
input.emplace_back("three");
UASSERT(str_join(input, ",") == "one,two,three");
input[1] = "";
UASSERT(str_join(input, ",") == "one,,three");
input[1] = "two";
UASSERT(str_join(input, " and ") == "one and two and three");
}
static bool within(const f32 value1, const f32 value2, const f32 precision)
{
return std::fabs(value1 - value2) <= precision;
}
static bool within(const v3f &v1, const v3f &v2, const f32 precision)
{
return within(v1.X, v2.X, precision) && within(v1.Y, v2.Y, precision)
&& within(v1.Z, v2.Z, precision);
}
static bool within(const core::matrix4 &m1, const core::matrix4 &m2,
const f32 precision)
{
const f32 *M1 = m1.pointer();
const f32 *M2 = m2.pointer();
for (int i = 0; i < 16; i++)
if (! within(M1[i], M2[i], precision))
return false;
return true;
}
static bool roundTripsDeg(const v3f &v, const f32 precision)
{
core::matrix4 m;
setPitchYawRoll(m, v);
return within(v, getPitchYawRoll(m), precision);
}
void TestUtilities::testEulerConversion()
{
// This test may fail on non-IEEE systems.
// Low tolerance is 4 ulp(1.0) for binary floats with 24 bit mantissa.
// (ulp = unit in the last place; ulp(1.0) = 2^-23).
const f32 tolL = 4.76837158203125e-7f;
// High tolerance is 2 ulp(180.0), needed for numbers in degrees.
// ulp(180.0) = 2^-16
const f32 tolH = 3.0517578125e-5f;
v3f v1, v2;
core::matrix4 m1, m2;
const f32 *M1 = m1.pointer();
const f32 *M2 = m2.pointer();
// Check that the radians version and the degrees version
// produce the same results. Check also that the conversion
// works both ways for these values.
v1 = v3f(M_PI/3.0, M_PI/5.0, M_PI/4.0);
v2 = v3f(60.0f, 36.0f, 45.0f);
setPitchYawRollRad(m1, v1);
setPitchYawRoll(m2, v2);
UASSERT(within(m1, m2, tolL));
UASSERT(within(getPitchYawRollRad(m1), v1, tolL));
UASSERT(within(getPitchYawRoll(m2), v2, tolH));
// Check the rotation matrix produced.
UASSERT(within(M1[0], 0.932004869f, tolL));
UASSERT(within(M1[1], 0.353553385f, tolL));
UASSERT(within(M1[2], 0.0797927827f, tolL));
UASSERT(within(M1[4], -0.21211791f, tolL));
UASSERT(within(M1[5], 0.353553355f, tolL));
UASSERT(within(M1[6], 0.911046684f, tolL));
UASSERT(within(M1[8], 0.293892622f, tolL));
UASSERT(within(M1[9], -0.866025448f, tolL));
UASSERT(within(M1[10], 0.404508471f, tolL));
// Check that the matrix is still homogeneous with no translation
UASSERT(M1[3] == 0.0f);
UASSERT(M1[7] == 0.0f);
UASSERT(M1[11] == 0.0f);
UASSERT(M1[12] == 0.0f);
UASSERT(M1[13] == 0.0f);
UASSERT(M1[14] == 0.0f);
UASSERT(M1[15] == 1.0f);
UASSERT(M2[3] == 0.0f);
UASSERT(M2[7] == 0.0f);
UASSERT(M2[11] == 0.0f);
UASSERT(M2[12] == 0.0f);
UASSERT(M2[13] == 0.0f);
UASSERT(M2[14] == 0.0f);
UASSERT(M2[15] == 1.0f);
// Compare to Irrlicht's results. To be comparable, the
// angles must come in a different order and the matrix
// elements to compare are different too.
m2.setRotationRadians(v3f(v1.Z, v1.X, v1.Y));
UASSERT(within(M1[0], M2[5], tolL));
UASSERT(within(M1[1], M2[6], tolL));
UASSERT(within(M1[2], M2[4], tolL));
UASSERT(within(M1[4], M2[9], tolL));
UASSERT(within(M1[5], M2[10], tolL));
UASSERT(within(M1[6], M2[8], tolL));
UASSERT(within(M1[8], M2[1], tolL));
UASSERT(within(M1[9], M2[2], tolL));
UASSERT(within(M1[10], M2[0], tolL));
// Check that Eulers that produce near gimbal-lock still round-trip
UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 0.f), tolH));
UASSERT(roundTripsDeg(v3f(89.9999f, 0.f, 19.f), tolH));
UASSERT(roundTripsDeg(v3f(89.9999f, 17.f, 19.f), tolH));
// Check that Eulers at an angle > 90 degrees may not round-trip...
v1 = v3f(90.00001f, 1.f, 1.f);
setPitchYawRoll(m1, v1);
v2 = getPitchYawRoll(m1);
//UASSERT(within(v1, v2, tolL)); // this is typically false
// ... however the rotation matrix is the same for both
setPitchYawRoll(m2, v2);
UASSERT(within(m1, m2, tolL));
}

View File

@ -0,0 +1,101 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "gamedef.h"
#include "voxelalgorithms.h"
#include "util/numeric.h"
class TestVoxelAlgorithms : public TestBase {
public:
TestVoxelAlgorithms() { TestManager::registerTestModule(this); }
const char *getName() { return "TestVoxelAlgorithms"; }
void runTests(IGameDef *gamedef);
void testVoxelLineIterator(const NodeDefManager *ndef);
};
static TestVoxelAlgorithms g_test_instance;
void TestVoxelAlgorithms::runTests(IGameDef *gamedef)
{
const NodeDefManager *ndef = gamedef->getNodeDefManager();
TEST(testVoxelLineIterator, ndef);
}
////////////////////////////////////////////////////////////////////////////////
void TestVoxelAlgorithms::testVoxelLineIterator(const NodeDefManager *ndef)
{
// Test some lines
// Do not test lines that start or end on the border of
// two voxels as rounding errors can make the test fail!
std::vector<core::line3d<f32> > lines;
for (f32 x = -9.1; x < 9; x += 3.124) {
for (f32 y = -9.2; y < 9; y += 3.123) {
for (f32 z = -9.3; z < 9; z += 3.122) {
lines.emplace_back(-x, -y, -z, x, y, z);
}
}
}
lines.emplace_back(0, 0, 0, 0, 0, 0);
// Test every line
std::vector<core::line3d<f32> >::iterator it = lines.begin();
for (; it < lines.end(); it++) {
core::line3d<f32> l = *it;
// Initialize test
voxalgo::VoxelLineIterator iterator(l.start, l.getVector());
//Test the first voxel
v3s16 start_voxel = floatToInt(l.start, 1);
UASSERT(iterator.m_current_node_pos == start_voxel);
// Values for testing
v3s16 end_voxel = floatToInt(l.end, 1);
v3s16 voxel_vector = end_voxel - start_voxel;
int nodecount = abs(voxel_vector.X) + abs(voxel_vector.Y)
+ abs(voxel_vector.Z);
int actual_nodecount = 0;
v3s16 old_voxel = iterator.m_current_node_pos;
while (iterator.hasNext()) {
iterator.next();
actual_nodecount++;
v3s16 new_voxel = iterator.m_current_node_pos;
// This must be a neighbor of the old voxel
UASSERTEQ(f32, (new_voxel - old_voxel).getLengthSQ(), 1);
// The line must intersect with the voxel
v3f voxel_center = intToFloat(iterator.m_current_node_pos, 1);
aabb3f box(voxel_center - v3f(0.5, 0.5, 0.5),
voxel_center + v3f(0.5, 0.5, 0.5));
UASSERT(box.intersectsWithLine(l));
// Update old voxel
old_voxel = new_voxel;
}
// Test last node
UASSERT(iterator.m_current_node_pos == end_voxel);
// Test node count
UASSERTEQ(int, actual_nodecount, nodecount);
}
}

View File

@ -0,0 +1,374 @@
/*
Minetest
Copyright (C) 2018 nerzhul, Loic Blot <loic.blot@unix-experience.fr>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include "voxel.h"
class TestVoxelArea : public TestBase
{
public:
TestVoxelArea() { TestManager::registerTestModule(this); }
const char *getName() { return "TestVoxelArea"; }
void runTests(IGameDef *gamedef);
void test_addarea();
void test_pad();
void test_volume();
void test_contains_voxelarea();
void test_contains_point();
void test_contains_i();
void test_equal();
void test_plus();
void test_minor();
void test_index_xyz_all_pos();
void test_index_xyz_x_neg();
void test_index_xyz_y_neg();
void test_index_xyz_z_neg();
void test_index_xyz_xy_neg();
void test_index_xyz_xz_neg();
void test_index_xyz_yz_neg();
void test_index_xyz_all_neg();
void test_index_v3s16_all_pos();
void test_index_v3s16_x_neg();
void test_index_v3s16_y_neg();
void test_index_v3s16_z_neg();
void test_index_v3s16_xy_neg();
void test_index_v3s16_xz_neg();
void test_index_v3s16_yz_neg();
void test_index_v3s16_all_neg();
void test_add_x();
void test_add_y();
void test_add_z();
void test_add_p();
};
static TestVoxelArea g_test_instance;
void TestVoxelArea::runTests(IGameDef *gamedef)
{
TEST(test_addarea);
TEST(test_pad);
TEST(test_volume);
TEST(test_contains_voxelarea);
TEST(test_contains_point);
TEST(test_contains_i);
TEST(test_equal);
TEST(test_plus);
TEST(test_minor);
TEST(test_index_xyz_all_pos);
TEST(test_index_xyz_x_neg);
TEST(test_index_xyz_y_neg);
TEST(test_index_xyz_z_neg);
TEST(test_index_xyz_xy_neg);
TEST(test_index_xyz_xz_neg);
TEST(test_index_xyz_yz_neg);
TEST(test_index_xyz_all_neg);
TEST(test_index_v3s16_all_pos);
TEST(test_index_v3s16_x_neg);
TEST(test_index_v3s16_y_neg);
TEST(test_index_v3s16_z_neg);
TEST(test_index_v3s16_xy_neg);
TEST(test_index_v3s16_xz_neg);
TEST(test_index_v3s16_yz_neg);
TEST(test_index_v3s16_all_neg);
TEST(test_add_x);
TEST(test_add_y);
TEST(test_add_z);
TEST(test_add_p);
}
void TestVoxelArea::test_addarea()
{
VoxelArea v1(v3s16(-1447, 8854, -875), v3s16(-147, -9547, 669));
VoxelArea v2(v3s16(-887, 4445, -5478), v3s16(447, -8779, 4778));
v1.addArea(v2);
UASSERT(v1.MinEdge == v3s16(-1447, 4445, -5478));
UASSERT(v1.MaxEdge == v3s16(447, -8779, 4778));
}
void TestVoxelArea::test_pad()
{
VoxelArea v1(v3s16(-1447, 8854, -875), v3s16(-147, -9547, 669));
v1.pad(v3s16(100, 200, 300));
UASSERT(v1.MinEdge == v3s16(-1547, 8654, -1175));
UASSERT(v1.MaxEdge == v3s16(-47, -9347, 969));
}
void TestVoxelArea::test_volume()
{
VoxelArea v1(v3s16(-1337, 447, -789), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v1.getVolume(), -184657133);
}
void TestVoxelArea::test_contains_voxelarea()
{
VoxelArea v1(v3s16(-1337, -9547, -789), v3s16(-147, 750, 669));
UASSERTEQ(bool, v1.contains(VoxelArea(v3s16(-200, 10, 10), v3s16(-150, 10, 10))),
true);
UASSERTEQ(bool, v1.contains(VoxelArea(v3s16(-2550, 10, 10), v3s16(10, 10, 10))),
false);
UASSERTEQ(bool, v1.contains(VoxelArea(v3s16(-10, 10, 10), v3s16(3500, 10, 10))),
false);
UASSERTEQ(bool,
v1.contains(VoxelArea(
v3s16(-800, -400, 669), v3s16(-500, 200, 669))),
true);
UASSERTEQ(bool,
v1.contains(VoxelArea(
v3s16(-800, -400, 670), v3s16(-500, 200, 670))),
false);
}
void TestVoxelArea::test_contains_point()
{
VoxelArea v1(v3s16(-1337, -9547, -789), v3s16(-147, 750, 669));
UASSERTEQ(bool, v1.contains(v3s16(-200, 10, 10)), true);
UASSERTEQ(bool, v1.contains(v3s16(-10000, 10, 10)), false);
UASSERTEQ(bool, v1.contains(v3s16(-100, 10000, 10)), false);
UASSERTEQ(bool, v1.contains(v3s16(-100, 100, 10000)), false);
UASSERTEQ(bool, v1.contains(v3s16(-100, 100, -10000)), false);
UASSERTEQ(bool, v1.contains(v3s16(10000, 100, 10)), false);
}
void TestVoxelArea::test_contains_i()
{
VoxelArea v1(v3s16(-1337, -9547, -789), v3s16(-147, 750, 669));
UASSERTEQ(bool, v1.contains(10), true);
UASSERTEQ(bool, v1.contains(v1.getVolume()), false);
UASSERTEQ(bool, v1.contains(v1.getVolume() - 1), true);
UASSERTEQ(bool, v1.contains(v1.getVolume() + 1), false);
UASSERTEQ(bool, v1.contains(-1), false)
VoxelArea v2(v3s16(10, 10, 10), v3s16(30, 30, 30));
UASSERTEQ(bool, v2.contains(10), true);
UASSERTEQ(bool, v2.contains(0), true);
UASSERTEQ(bool, v2.contains(-1), false);
}
void TestVoxelArea::test_equal()
{
VoxelArea v1(v3s16(-1337, -9547, -789), v3s16(-147, 750, 669));
UASSERTEQ(bool, v1 == VoxelArea(v3s16(-1337, -9547, -789), v3s16(-147, 750, 669)),
true);
UASSERTEQ(bool, v1 == VoxelArea(v3s16(0, 0, 0), v3s16(-147, 750, 669)), false);
UASSERTEQ(bool, v1 == VoxelArea(v3s16(0, 0, 0), v3s16(-147, 750, 669)), false);
UASSERTEQ(bool, v1 == VoxelArea(v3s16(0, 0, 0), v3s16(0, 0, 0)), false);
}
void TestVoxelArea::test_plus()
{
VoxelArea v1(v3s16(-10, -10, -10), v3s16(100, 100, 100));
UASSERT(v1 + v3s16(10, 0, 0) ==
VoxelArea(v3s16(0, -10, -10), v3s16(110, 100, 100)));
UASSERT(v1 + v3s16(10, -10, 0) ==
VoxelArea(v3s16(0, -20, -10), v3s16(110, 90, 100)));
UASSERT(v1 + v3s16(0, 0, 35) ==
VoxelArea(v3s16(-10, -10, 25), v3s16(100, 100, 135)));
}
void TestVoxelArea::test_minor()
{
VoxelArea v1(v3s16(-10, -10, -10), v3s16(100, 100, 100));
UASSERT(v1 - v3s16(10, 0, 0) ==
VoxelArea(v3s16(-20, -10, -10), v3s16(90, 100, 100)));
UASSERT(v1 - v3s16(10, -10, 0) ==
VoxelArea(v3s16(-20, 0, -10), v3s16(90, 110, 100)));
UASSERT(v1 - v3s16(0, 0, 35) ==
VoxelArea(v3s16(-10, -10, -45), v3s16(100, 100, 65)));
}
void TestVoxelArea::test_index_xyz_all_pos()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(156, 25, 236), 155);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(156, 25, 236), 1267138774);
}
void TestVoxelArea::test_index_xyz_x_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(-147, 25, 366), -148);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(-147, 25, 366), -870244825);
}
void TestVoxelArea::test_index_xyz_y_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(247, -269, 100), 246);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(247, -269, 100), -989760747);
}
void TestVoxelArea::test_index_xyz_z_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(244, 336, -887), 243);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(244, 336, -887), -191478876);
}
void TestVoxelArea::test_index_xyz_xy_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(-365, -47, 6978), -366);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(-365, -47, 6978), 1493679101);
}
void TestVoxelArea::test_index_xyz_yz_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(66, -58, -789), 65);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(66, -58, -789), 1435362734);
}
void TestVoxelArea::test_index_xyz_xz_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(-36, 589, -992), -37);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(-36, 589, -992), -1934371362);
}
void TestVoxelArea::test_index_xyz_all_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(-88, -99, -1474), -89);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(-88, -99, -1474), -1343473846);
}
void TestVoxelArea::test_index_v3s16_all_pos()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(v3s16(156, 25, 236)), 155);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(v3s16(156, 25, 236)), 1267138774);
}
void TestVoxelArea::test_index_v3s16_x_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(v3s16(-147, 25, 366)), -148);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(v3s16(-147, 25, 366)), -870244825);
}
void TestVoxelArea::test_index_v3s16_y_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(v3s16(247, -269, 100)), 246);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(v3s16(247, -269, 100)), -989760747);
}
void TestVoxelArea::test_index_v3s16_z_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(v3s16(244, 336, -887)), 243);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(v3s16(244, 336, -887)), -191478876);
}
void TestVoxelArea::test_index_v3s16_xy_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(v3s16(-365, -47, 6978)), -366);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(v3s16(-365, -47, 6978)), 1493679101);
}
void TestVoxelArea::test_index_v3s16_yz_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(v3s16(66, -58, -789)), 65);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(v3s16(66, -58, -789)), 1435362734);
}
void TestVoxelArea::test_index_v3s16_xz_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(v3s16(-36, 589, -992)), -37);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(v3s16(-36, 589, -992)), -1934371362);
}
void TestVoxelArea::test_index_v3s16_all_neg()
{
VoxelArea v1;
UASSERTEQ(s32, v1.index(v3s16(-88, -99, -1474)), -89);
VoxelArea v2(v3s16(756, 8854, -875), v3s16(-147, -9547, 669));
UASSERTEQ(s32, v2.index(v3s16(-88, -99, -1474)), -1343473846);
}
void TestVoxelArea::test_add_x()
{
v3s16 extent;
u32 i = 4;
VoxelArea::add_x(extent, i, 8);
UASSERTEQ(u32, i, 12)
}
void TestVoxelArea::test_add_y()
{
v3s16 extent(740, 16, 87);
u32 i = 8;
VoxelArea::add_y(extent, i, 88);
UASSERTEQ(u32, i, 65128)
}
void TestVoxelArea::test_add_z()
{
v3s16 extent(114, 80, 256);
u32 i = 4;
VoxelArea::add_z(extent, i, 8);
UASSERTEQ(u32, i, 72964)
}
void TestVoxelArea::test_add_p()
{
v3s16 extent(33, 14, 742);
v3s16 a(15, 12, 369);
u32 i = 4;
VoxelArea::add_p(extent, i, a);
UASSERTEQ(u32, i, 170893)
}

View File

@ -0,0 +1,108 @@
/*
Minetest
Copyright (C) 2013 celeron55, Perttu Ahola <celeron55@gmail.com>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "test.h"
#include <algorithm>
#include "gamedef.h"
#include "log.h"
#include "voxel.h"
class TestVoxelManipulator : public TestBase {
public:
TestVoxelManipulator() { TestManager::registerTestModule(this); }
const char *getName() { return "TestVoxelManipulator"; }
void runTests(IGameDef *gamedef);
void testVoxelArea();
void testVoxelManipulator(const NodeDefManager *nodedef);
};
static TestVoxelManipulator g_test_instance;
void TestVoxelManipulator::runTests(IGameDef *gamedef)
{
TEST(testVoxelArea);
TEST(testVoxelManipulator, gamedef->getNodeDefManager());
}
////////////////////////////////////////////////////////////////////////////////
void TestVoxelManipulator::testVoxelArea()
{
VoxelArea a(v3s16(-1,-1,-1), v3s16(1,1,1));
UASSERT(a.index(0,0,0) == 1*3*3 + 1*3 + 1);
UASSERT(a.index(-1,-1,-1) == 0);
VoxelArea c(v3s16(-2,-2,-2), v3s16(2,2,2));
// An area that is 1 bigger in x+ and z-
VoxelArea d(v3s16(-2,-2,-3), v3s16(3,2,2));
std::list<VoxelArea> aa;
d.diff(c, aa);
// Correct results
std::vector<VoxelArea> results;
results.emplace_back(v3s16(-2,-2,-3), v3s16(3,2,-3));
results.emplace_back(v3s16(3,-2,-2), v3s16(3,2,2));
UASSERT(aa.size() == results.size());
infostream<<"Result of diff:"<<std::endl;
for (std::list<VoxelArea>::const_iterator
it = aa.begin(); it != aa.end(); ++it) {
it->print(infostream);
infostream << std::endl;
std::vector<VoxelArea>::iterator j;
j = std::find(results.begin(), results.end(), *it);
UASSERT(j != results.end());
results.erase(j);
}
}
void TestVoxelManipulator::testVoxelManipulator(const NodeDefManager *nodedef)
{
VoxelManipulator v;
v.print(infostream, nodedef);
infostream << "*** Setting (-1,0,-1)=2 ***" << std::endl;
v.setNodeNoRef(v3s16(-1,0,-1), MapNode(t_CONTENT_GRASS));
v.print(infostream, nodedef);
UASSERT(v.getNode(v3s16(-1,0,-1)).getContent() == t_CONTENT_GRASS);
infostream << "*** Reading from inexistent (0,0,-1) ***" << std::endl;
EXCEPTION_CHECK(InvalidPositionException, v.getNode(v3s16(0,0,-1)));
v.print(infostream, nodedef);
infostream << "*** Adding area ***" << std::endl;
VoxelArea a(v3s16(-1,-1,-1), v3s16(1,1,1));
v.addArea(a);
v.print(infostream, nodedef);
UASSERT(v.getNode(v3s16(-1,0,-1)).getContent() == t_CONTENT_GRASS);
EXCEPTION_CHECK(InvalidPositionException, v.getNode(v3s16(0,1,1)));
}