mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-10 00:04:48 -07:00
Centralize counter API with server-side bounds and no-op filtering (#6879)
* Refactor server counter API to own overflow protection and filter no-op events Counter modifications now clamp to int bounds server-side and return change status, allowing command handlers to skip network broadcasts when values don't actually change. * Centralize MAX_COUNTERS_ON_CARD and enforce [0, 999] bounds on server - Move MAX_COUNTERS_ON_CARD to trice_limits.h - Server clamps values in setCounter() and incrementCounter() - Client uses clamped comparison to allow recovery from invalid states - Add tests for clamping behavior * move incrementCount() implementation from header to cpp
This commit is contained in:
parent
74102aa1ec
commit
8dca14933c
12 changed files with 432 additions and 32 deletions
|
|
@ -12,6 +12,7 @@
|
||||||
#include "card_state.h"
|
#include "card_state.h"
|
||||||
|
|
||||||
#include <libcockatrice/network/server/remote/game/server_card.h>
|
#include <libcockatrice/network/server/remote/game/server_card.h>
|
||||||
|
#include <libcockatrice/utility/trice_limits.h>
|
||||||
|
|
||||||
class CardDatabase;
|
class CardDatabase;
|
||||||
class CardDragItem;
|
class CardDragItem;
|
||||||
|
|
@ -21,7 +22,6 @@ class PlayerLogic;
|
||||||
class QAction;
|
class QAction;
|
||||||
class QColor;
|
class QColor;
|
||||||
|
|
||||||
const int MAX_COUNTERS_ON_CARD = 999;
|
|
||||||
const int ROTATION_DEGREES_PER_FRAME = 10;
|
const int ROTATION_DEGREES_PER_FRAME = 10;
|
||||||
|
|
||||||
class CardItem : public AbstractCardItem
|
class CardItem : public AbstractCardItem
|
||||||
|
|
|
||||||
|
|
@ -1501,7 +1501,10 @@ void PlayerActions::offsetCardCounter(int counterId, int offset)
|
||||||
int oldValue = card->getCounters().value(counterId, 0);
|
int oldValue = card->getCounters().value(counterId, 0);
|
||||||
int newValue = oldValue + offset;
|
int newValue = oldValue + offset;
|
||||||
|
|
||||||
if (newValue >= 0 && newValue <= MAX_COUNTERS_ON_CARD) {
|
// Early exit optimization: server enforces [0, MAX_COUNTERS_ON_CARD].
|
||||||
|
// Compare clamped value to allow recovery from invalid states.
|
||||||
|
int clampedValue = qBound(0, newValue, MAX_COUNTERS_ON_CARD);
|
||||||
|
if (clampedValue != oldValue) {
|
||||||
auto *cmd = new Command_SetCardCounter;
|
auto *cmd = new Command_SetCardCounter;
|
||||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||||
cmd->set_card_id(card->getId());
|
cmd->set_card_id(card->getId());
|
||||||
|
|
@ -1541,7 +1544,9 @@ void PlayerActions::actSetCardCounter(int counterId)
|
||||||
for (auto card : sel) {
|
for (auto card : sel) {
|
||||||
int oldValue = card->getCounters().value(counterId, 0);
|
int oldValue = card->getCounters().value(counterId, 0);
|
||||||
Expression exp(oldValue);
|
Expression exp(oldValue);
|
||||||
int number = static_cast<int>(exp.parse(dialog.textValue()));
|
double parsed = exp.parse(dialog.textValue());
|
||||||
|
// Clamp in double precision first to avoid UB, then cast
|
||||||
|
int number = static_cast<int>(qBound(0.0, parsed, static_cast<double>(MAX_COUNTERS_ON_CARD)));
|
||||||
|
|
||||||
auto *cmd = new Command_SetCardCounter;
|
auto *cmd = new Command_SetCardCounter;
|
||||||
cmd->set_zone(card->getZone()->getName().toStdString());
|
cmd->set_zone(card->getZone()->getName().toStdString());
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@
|
||||||
#include <libcockatrice/rng/rng_abstract.h>
|
#include <libcockatrice/rng/rng_abstract.h>
|
||||||
#include <libcockatrice/utility/trice_limits.h>
|
#include <libcockatrice/utility/trice_limits.h>
|
||||||
#include <libcockatrice/utility/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
|
#include <limits>
|
||||||
#include <ranges>
|
#include <ranges>
|
||||||
|
|
||||||
Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game,
|
Server_AbstractPlayer::Server_AbstractPlayer(Server_Game *_game,
|
||||||
|
|
@ -1091,8 +1092,9 @@ Server_AbstractPlayer::cmdCreateToken(const Command_CreateToken &cmd, ResponseCo
|
||||||
_event.set_zone_name(card->getZone()->getName().toStdString());
|
_event.set_zone_name(card->getZone()->getName().toStdString());
|
||||||
_event.set_card_id(card->getId());
|
_event.set_card_id(card->getId());
|
||||||
|
|
||||||
card->setCounter(i.key(), i.value(), &_event);
|
if (card->setCounter(i.key(), i.value(), &_event)) {
|
||||||
ges.enqueueGameEvent(_event, playerId);
|
ges.enqueueGameEvent(_event, playerId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy parent card
|
// Copy parent card
|
||||||
|
|
@ -1338,8 +1340,9 @@ Response::ResponseCode Server_AbstractPlayer::cmdSetCardCounter(const Command_Se
|
||||||
Event_SetCardCounter event;
|
Event_SetCardCounter event;
|
||||||
event.set_zone_name(zone->getName().toStdString());
|
event.set_zone_name(zone->getName().toStdString());
|
||||||
event.set_card_id(card->getId());
|
event.set_card_id(card->getId());
|
||||||
card->setCounter(cmd.counter_id(), cmd.counter_value(), &event);
|
if (card->setCounter(cmd.counter_id(), cmd.counter_value(), &event)) {
|
||||||
ges.enqueueGameEvent(event, playerId);
|
ges.enqueueGameEvent(event, playerId);
|
||||||
|
}
|
||||||
|
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
@ -1368,14 +1371,13 @@ Response::ResponseCode Server_AbstractPlayer::cmdIncCardCounter(const Command_In
|
||||||
return Response::RespNameNotFound;
|
return Response::RespNameNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
int newValue = card->getCounter(cmd.counter_id()) + cmd.counter_delta();
|
|
||||||
card->setCounter(cmd.counter_id(), newValue);
|
|
||||||
|
|
||||||
Event_SetCardCounter event;
|
Event_SetCardCounter event;
|
||||||
event.set_zone_name(zone->getName().toStdString());
|
event.set_zone_name(zone->getName().toStdString());
|
||||||
event.set_card_id(card->getId());
|
event.set_card_id(card->getId());
|
||||||
event.set_counter_id(cmd.counter_id());
|
if (!card->incrementCounter(cmd.counter_id(), cmd.counter_delta(), &event)) {
|
||||||
event.set_counter_value(newValue);
|
return Response::RespOk;
|
||||||
|
}
|
||||||
|
|
||||||
ges.enqueueGameEvent(event, playerId);
|
ges.enqueueGameEvent(event, playerId);
|
||||||
|
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,8 @@
|
||||||
#include <libcockatrice/protocol/pb/event_set_card_attr.pb.h>
|
#include <libcockatrice/protocol/pb/event_set_card_attr.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/event_set_card_counter.pb.h>
|
#include <libcockatrice/protocol/pb/event_set_card_counter.pb.h>
|
||||||
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
|
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
|
||||||
|
#include <libcockatrice/utility/trice_limits.h>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
Server_Card::Server_Card(const CardRef &cardRef, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone)
|
Server_Card::Server_Card(const CardRef &cardRef, int _id, int _coord_x, int _coord_y, Server_CardZone *_zone)
|
||||||
: zone(_zone), id(_id), coord_x(_coord_x), coord_y(_coord_y), cardRef(cardRef), tapped(false), attacking(false),
|
: zone(_zone), id(_id), coord_x(_coord_x), coord_y(_coord_y), cardRef(cardRef), tapped(false), attacking(false),
|
||||||
|
|
@ -110,8 +112,16 @@ QString Server_Card::setAttribute(CardAttribute attribute, const QString &avalue
|
||||||
return avalue;
|
return avalue;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server_Card::setCounter(int _id, int value, Event_SetCardCounter *event)
|
bool Server_Card::setCounter(int _id, int value, Event_SetCardCounter *event)
|
||||||
{
|
{
|
||||||
|
// Clamp to valid card counter range [0, MAX_COUNTERS_ON_CARD]
|
||||||
|
value = qBound(0, value, MAX_COUNTERS_ON_CARD);
|
||||||
|
|
||||||
|
const int oldValue = counters.value(_id, 0);
|
||||||
|
if (value == oldValue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
counters.insert(_id, value);
|
counters.insert(_id, value);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -122,6 +132,34 @@ void Server_Card::setCounter(int _id, int value, Event_SetCardCounter *event)
|
||||||
event->set_counter_id(_id);
|
event->set_counter_id(_id);
|
||||||
event->set_counter_value(value);
|
event->set_counter_value(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Server_Card::incrementCounter(int counterId, int delta, Event_SetCardCounter *event)
|
||||||
|
{
|
||||||
|
const int oldValue = counters.value(counterId, 0);
|
||||||
|
const auto result = static_cast<int64_t>(oldValue) + static_cast<int64_t>(delta);
|
||||||
|
// Clamp to [0, MAX_COUNTERS_ON_CARD] for card counters
|
||||||
|
const int newValue =
|
||||||
|
static_cast<int>(qBound(static_cast<int64_t>(0), result, static_cast<int64_t>(MAX_COUNTERS_ON_CARD)));
|
||||||
|
|
||||||
|
if (newValue == oldValue) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newValue) {
|
||||||
|
counters.insert(counterId, newValue);
|
||||||
|
} else {
|
||||||
|
counters.remove(counterId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event) {
|
||||||
|
event->set_counter_id(counterId);
|
||||||
|
event->set_counter_value(newValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Server_Card::setParentCard(Server_Card *_parentCard)
|
void Server_Card::setParentCard(Server_Card *_parentCard)
|
||||||
|
|
|
||||||
|
|
@ -153,7 +153,24 @@ public:
|
||||||
{
|
{
|
||||||
cardRef = _cardRef;
|
cardRef = _cardRef;
|
||||||
}
|
}
|
||||||
void setCounter(int _id, int value, Event_SetCardCounter *event = nullptr);
|
/**
|
||||||
|
* @brief Sets a card counter to an exact value with clamping.
|
||||||
|
* @param _id The counter ID.
|
||||||
|
* @param value The desired value (clamped to [0, MAX_COUNTERS_ON_CARD]; 0 removes the counter).
|
||||||
|
* @param event Optional event to populate with counter state.
|
||||||
|
* @return true if the value changed, false otherwise.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool setCounter(int _id, int value, Event_SetCardCounter *event = nullptr);
|
||||||
|
/**
|
||||||
|
* @brief Increments a card counter with overflow-safe arithmetic.
|
||||||
|
* @param counterId The counter ID to modify.
|
||||||
|
* @param delta The amount to add (may be negative for decrement).
|
||||||
|
* @param event Optional event to populate with counter state.
|
||||||
|
* @return true if the value changed, false otherwise.
|
||||||
|
* @note If counter does not exist, starts from 0. Counter is removed if result is 0.
|
||||||
|
* @note Clamps result to [0, MAX_COUNTERS_ON_CARD].
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool incrementCounter(int counterId, int delta, Event_SetCardCounter *event = nullptr);
|
||||||
void setTapped(bool _tapped)
|
void setTapped(bool _tapped)
|
||||||
{
|
{
|
||||||
tapped = _tapped;
|
tapped = _tapped;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,24 @@
|
||||||
#include "server_counter.h"
|
#include "server_counter.h"
|
||||||
|
|
||||||
#include <libcockatrice/protocol/pb/serverinfo_counter.pb.h>
|
#include <libcockatrice/protocol/pb/serverinfo_counter.pb.h>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
Server_Counter::Server_Counter(int _id, const QString &_name, const color &_counterColor, int _radius, int _count)
|
Server_Counter::Server_Counter(int _id, const QString &_name, const color &_counterColor, int _radius, int _count)
|
||||||
: id(_id), name(_name), counterColor(_counterColor), radius(_radius), count(_count)
|
: id(_id), name(_name), counterColor(_counterColor), radius(_radius), count(_count)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//! \todo Extract overflow-safe arithmetic into shared helper.
|
||||||
|
//! Duplicated in Server_Card::incrementCounter() - keep in sync if modified.
|
||||||
|
bool Server_Counter::incrementCount(int delta)
|
||||||
|
{
|
||||||
|
const int oldCount = count;
|
||||||
|
const auto result = static_cast<int64_t>(count) + static_cast<int64_t>(delta);
|
||||||
|
count = static_cast<int>(qBound(static_cast<int64_t>(std::numeric_limits<int>::min()), result,
|
||||||
|
static_cast<int64_t>(std::numeric_limits<int>::max())));
|
||||||
|
return count != oldCount;
|
||||||
|
}
|
||||||
|
|
||||||
void Server_Counter::getInfo(ServerInfo_Counter *info)
|
void Server_Counter::getInfo(ServerInfo_Counter *info)
|
||||||
{
|
{
|
||||||
info->set_id(id);
|
info->set_id(id);
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,18 @@
|
||||||
|
|
||||||
class ServerInfo_Counter;
|
class ServerInfo_Counter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Server_Counter
|
||||||
|
* @brief Represents a player counter with overflow-safe increment arithmetic.
|
||||||
|
*
|
||||||
|
* All value modifications return whether the value actually changed,
|
||||||
|
* enabling callers to skip unnecessary network events.
|
||||||
|
*
|
||||||
|
* @note Direct assignment via setCount() does not clamp; only
|
||||||
|
* incrementCount() enforces int boundary saturation.
|
||||||
|
* @note Unlike card counters, player counters are never auto-removed
|
||||||
|
* when they reach zero - they persist with value 0.
|
||||||
|
*/
|
||||||
class Server_Counter
|
class Server_Counter
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
|
|
@ -59,11 +71,33 @@ public:
|
||||||
{
|
{
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
void setCount(int _count)
|
|
||||||
|
/**
|
||||||
|
* @brief Sets the counter to an exact value.
|
||||||
|
* @param _count The new value (assigned directly without clamping).
|
||||||
|
* @return true if the value changed, false otherwise.
|
||||||
|
* @warning This performs raw assignment. For overflow-safe incrementing,
|
||||||
|
* use incrementCount().
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool setCount(int _count)
|
||||||
{
|
{
|
||||||
|
const int oldCount = count;
|
||||||
count = _count;
|
count = _count;
|
||||||
|
return count != oldCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Increments the counter by delta with overflow-safe arithmetic.
|
||||||
|
* @param delta The amount to add (may be negative for decrement).
|
||||||
|
* @return true if the value changed, false otherwise.
|
||||||
|
* @note Clamps result to [INT_MIN, INT_MAX] to prevent overflow.
|
||||||
|
*/
|
||||||
|
[[nodiscard]] bool incrementCount(int delta);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Populates info with this counter's current state for network serialization.
|
||||||
|
* @param info The protobuf message to populate.
|
||||||
|
*/
|
||||||
void getInfo(ServerInfo_Counter *info);
|
void getInfo(ServerInfo_Counter *info);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -436,17 +436,19 @@ Server_Player::cmdIncCounter(const Command_IncCounter &cmd, ResponseContainer &
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
|
|
||||||
Server_Counter *c = counters.value(cmd.counter_id(), 0);
|
const int counterId = cmd.counter_id();
|
||||||
|
Server_Counter *c = counters.value(counterId, nullptr);
|
||||||
if (!c) {
|
if (!c) {
|
||||||
return Response::RespNameNotFound;
|
return Response::RespNameNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->setCount(c->getCount() + cmd.delta());
|
bool didChange = c->incrementCount(cmd.delta());
|
||||||
|
if (didChange) {
|
||||||
Event_SetCounter event;
|
Event_SetCounter event;
|
||||||
event.set_counter_id(c->getId());
|
event.set_counter_id(c->getId());
|
||||||
event.set_value(c->getCount());
|
event.set_value(c->getCount());
|
||||||
ges.enqueueGameEvent(event, playerId);
|
ges.enqueueGameEvent(event, playerId);
|
||||||
|
}
|
||||||
|
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
@ -487,17 +489,19 @@ Server_Player::cmdSetCounter(const Command_SetCounter &cmd, ResponseContainer &
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
|
|
||||||
Server_Counter *c = counters.value(cmd.counter_id(), 0);
|
const int counterId = cmd.counter_id();
|
||||||
|
Server_Counter *c = counters.value(counterId, nullptr);
|
||||||
if (!c) {
|
if (!c) {
|
||||||
return Response::RespNameNotFound;
|
return Response::RespNameNotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
c->setCount(cmd.value());
|
bool didChange = c->setCount(cmd.value());
|
||||||
|
if (didChange) {
|
||||||
Event_SetCounter event;
|
Event_SetCounter event;
|
||||||
event.set_counter_id(c->getId());
|
event.set_counter_id(c->getId());
|
||||||
event.set_value(c->getCount());
|
event.set_value(c->getCount());
|
||||||
ges.enqueueGameEvent(event, playerId);
|
ges.enqueueGameEvent(event, playerId);
|
||||||
|
}
|
||||||
|
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
}
|
}
|
||||||
|
|
@ -512,15 +516,16 @@ Server_Player::cmdDelCounter(const Command_DelCounter &cmd, ResponseContainer &
|
||||||
return Response::RespContextError;
|
return Response::RespContextError;
|
||||||
}
|
}
|
||||||
|
|
||||||
Server_Counter *counter = counters.value(cmd.counter_id(), 0);
|
const int counterId = cmd.counter_id();
|
||||||
|
Server_Counter *counter = counters.value(counterId, nullptr);
|
||||||
if (!counter) {
|
if (!counter) {
|
||||||
return Response::RespNameNotFound;
|
return Response::RespNameNotFound;
|
||||||
}
|
}
|
||||||
counters.remove(cmd.counter_id());
|
counters.remove(counterId);
|
||||||
delete counter;
|
delete counter;
|
||||||
|
|
||||||
Event_DelCounter event;
|
Event_DelCounter event;
|
||||||
event.set_counter_id(cmd.counter_id());
|
event.set_counter_id(counterId);
|
||||||
ges.enqueueGameEvent(event, playerId);
|
ges.enqueueGameEvent(event, playerId);
|
||||||
|
|
||||||
return Response::RespOk;
|
return Response::RespOk;
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,12 @@ constexpr uint MAXIMUM_DIE_SIDES = 1000000;
|
||||||
constexpr uint MINIMUM_DICE_TO_ROLL = 1;
|
constexpr uint MINIMUM_DICE_TO_ROLL = 1;
|
||||||
constexpr uint MAXIMUM_DICE_TO_ROLL = 100;
|
constexpr uint MAXIMUM_DICE_TO_ROLL = 100;
|
||||||
|
|
||||||
|
// Card counter value bounds [0, MAX_COUNTERS_ON_CARD].
|
||||||
|
// Counters on cards (e.g., +1/+1 counters, charge counters) are non-negative physical game objects.
|
||||||
|
// The max of 999 is a display constraint (3-digit rendering) and reasonable gameplay limit.
|
||||||
|
// Server enforces these bounds; client may also check for UX optimization.
|
||||||
|
constexpr int MAX_COUNTERS_ON_CARD = 999;
|
||||||
|
|
||||||
// optimized functions to get qstrings that are at most that long
|
// optimized functions to get qstrings that are at most that long
|
||||||
static inline QString nameFromStdString(const std::string &_string)
|
static inline QString nameFromStdString(const std::string &_string)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ add_test(NAME dummy_test COMMAND dummy_test)
|
||||||
add_test(NAME expression_test COMMAND expression_test)
|
add_test(NAME expression_test COMMAND expression_test)
|
||||||
add_test(NAME test_age_formatting COMMAND test_age_formatting)
|
add_test(NAME test_age_formatting COMMAND test_age_formatting)
|
||||||
add_test(NAME password_hash_test COMMAND password_hash_test)
|
add_test(NAME password_hash_test COMMAND password_hash_test)
|
||||||
|
add_test(NAME server_card_counter_test COMMAND server_card_counter_test)
|
||||||
|
add_test(NAME server_counter_test COMMAND server_counter_test)
|
||||||
|
|
||||||
add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test)
|
add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test)
|
||||||
set_tests_properties(deck_hash_performance_test PROPERTIES TIMEOUT 5)
|
set_tests_properties(deck_hash_performance_test PROPERTIES TIMEOUT 5)
|
||||||
|
|
@ -17,6 +19,8 @@ add_executable(expression_test expression_test.cpp)
|
||||||
add_executable(test_age_formatting test_age_formatting.cpp)
|
add_executable(test_age_formatting test_age_formatting.cpp)
|
||||||
add_executable(password_hash_test password_hash_test.cpp)
|
add_executable(password_hash_test password_hash_test.cpp)
|
||||||
add_executable(deck_hash_performance_test deck_hash_performance_test.cpp)
|
add_executable(deck_hash_performance_test deck_hash_performance_test.cpp)
|
||||||
|
add_executable(server_card_counter_test server_card_counter_test.cpp)
|
||||||
|
add_executable(server_counter_test server_counter_test.cpp)
|
||||||
|
|
||||||
find_package(GTest)
|
find_package(GTest)
|
||||||
|
|
||||||
|
|
@ -48,6 +52,8 @@ if(NOT GTEST_FOUND)
|
||||||
add_dependencies(test_age_formatting gtest)
|
add_dependencies(test_age_formatting gtest)
|
||||||
add_dependencies(password_hash_test gtest)
|
add_dependencies(password_hash_test gtest)
|
||||||
add_dependencies(deck_hash_performance_test gtest)
|
add_dependencies(deck_hash_performance_test gtest)
|
||||||
|
add_dependencies(server_card_counter_test gtest)
|
||||||
|
add_dependencies(server_counter_test gtest)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
include_directories(${GTEST_INCLUDE_DIRS})
|
include_directories(${GTEST_INCLUDE_DIRS})
|
||||||
|
|
@ -61,6 +67,12 @@ target_link_libraries(
|
||||||
deck_hash_performance_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES}
|
deck_hash_performance_test libcockatrice_deck_list libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES}
|
||||||
${TEST_QT_MODULES}
|
${TEST_QT_MODULES}
|
||||||
)
|
)
|
||||||
|
target_link_libraries(
|
||||||
|
server_card_counter_test libcockatrice_network Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
||||||
|
)
|
||||||
|
target_link_libraries(
|
||||||
|
server_counter_test libcockatrice_network Threads::Threads ${GTEST_BOTH_LIBRARIES} ${TEST_QT_MODULES}
|
||||||
|
)
|
||||||
|
|
||||||
add_subdirectory(card_zone_algorithms)
|
add_subdirectory(card_zone_algorithms)
|
||||||
add_subdirectory(carddatabase)
|
add_subdirectory(carddatabase)
|
||||||
|
|
|
||||||
183
tests/server_card_counter_test.cpp
Normal file
183
tests/server_card_counter_test.cpp
Normal file
|
|
@ -0,0 +1,183 @@
|
||||||
|
/** @file server_card_counter_test.cpp
|
||||||
|
* @brief Tests for Server_Card counter operations.
|
||||||
|
* @ingroup Tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <libcockatrice/network/server/remote/game/server_card.h>
|
||||||
|
#include <libcockatrice/protocol/pb/event_set_card_counter.pb.h>
|
||||||
|
#include <libcockatrice/utility/card_ref.h>
|
||||||
|
#include <libcockatrice/utility/trice_limits.h>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, IncrementNewCounter)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
EXPECT_TRUE(card.incrementCounter(1, 10));
|
||||||
|
EXPECT_EQ(card.getCounter(1), 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, IncrementExistingCounter)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, 50));
|
||||||
|
EXPECT_TRUE(card.incrementCounter(1, 10));
|
||||||
|
EXPECT_EQ(card.getCounter(1), 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, IncrementOverflowProtection)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD));
|
||||||
|
EXPECT_FALSE(card.incrementCounter(1, 1));
|
||||||
|
EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, DecrementUnderflowProtection)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, 5));
|
||||||
|
EXPECT_TRUE(card.incrementCounter(1, -10));
|
||||||
|
EXPECT_EQ(card.getCounter(1), 0);
|
||||||
|
EXPECT_FALSE(card.getCounters().contains(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, ReturnsFalseWhenUnchanged)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, 50));
|
||||||
|
EXPECT_FALSE(card.incrementCounter(1, 0));
|
||||||
|
EXPECT_EQ(card.getCounter(1), 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, DecrementToZeroRemovesCounter)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, 10));
|
||||||
|
EXPECT_TRUE(card.incrementCounter(1, -10));
|
||||||
|
EXPECT_EQ(card.getCounter(1), 0);
|
||||||
|
EXPECT_FALSE(card.getCounters().contains(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, SetToZeroRemovesCounter)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, 10));
|
||||||
|
EXPECT_TRUE(card.setCounter(1, 0));
|
||||||
|
EXPECT_EQ(card.getCounter(1), 0);
|
||||||
|
EXPECT_FALSE(card.getCounters().contains(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, SetCounterReturnsFalseWhenUnchanged)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, 50));
|
||||||
|
EXPECT_FALSE(card.setCounter(1, 50));
|
||||||
|
EXPECT_EQ(card.getCounter(1), 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, SetCounterReturnsTrueWhenChanged)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, 50));
|
||||||
|
EXPECT_TRUE(card.setCounter(1, 100));
|
||||||
|
EXPECT_EQ(card.getCounter(1), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, SetCounterEventNotPopulatedWhenUnchanged)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, 50));
|
||||||
|
|
||||||
|
Event_SetCardCounter event;
|
||||||
|
event.set_counter_id(999);
|
||||||
|
event.set_counter_value(999);
|
||||||
|
|
||||||
|
EXPECT_FALSE(card.setCounter(1, 50, &event));
|
||||||
|
EXPECT_EQ(event.counter_id(), 999);
|
||||||
|
EXPECT_EQ(event.counter_value(), 999);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, IncrementCounterPopulatesEvent)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, 50));
|
||||||
|
|
||||||
|
Event_SetCardCounter event;
|
||||||
|
EXPECT_TRUE(card.incrementCounter(1, 10, &event));
|
||||||
|
|
||||||
|
EXPECT_EQ(event.counter_id(), 1);
|
||||||
|
EXPECT_EQ(event.counter_value(), 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, IncrementCounterEventReflectsClampedValue)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD - 5));
|
||||||
|
|
||||||
|
Event_SetCardCounter event;
|
||||||
|
EXPECT_TRUE(card.incrementCounter(1, 10, &event));
|
||||||
|
|
||||||
|
EXPECT_EQ(event.counter_id(), 1);
|
||||||
|
EXPECT_EQ(event.counter_value(), MAX_COUNTERS_ON_CARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, IncrementCounterNoEventWhenNullptr)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, 50));
|
||||||
|
EXPECT_TRUE(card.incrementCounter(1, 10, nullptr));
|
||||||
|
EXPECT_EQ(card.getCounter(1), 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, IncrementCounterEventNotPopulatedWhenUnchanged)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD));
|
||||||
|
|
||||||
|
Event_SetCardCounter event;
|
||||||
|
event.set_counter_id(999);
|
||||||
|
event.set_counter_value(999);
|
||||||
|
|
||||||
|
EXPECT_FALSE(card.incrementCounter(1, 1, &event));
|
||||||
|
EXPECT_EQ(event.counter_id(), 999);
|
||||||
|
EXPECT_EQ(event.counter_value(), 999);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, SetCounterClampsNegativeToZero)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
EXPECT_FALSE(card.setCounter(1, -5));
|
||||||
|
EXPECT_EQ(card.getCounter(1), 0);
|
||||||
|
EXPECT_FALSE(card.getCounters().contains(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, SetCounterClampsAboveMaxToMax)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
EXPECT_TRUE(card.setCounter(1, 1500));
|
||||||
|
EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, IncrementDoesNotGoBelowZero)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, 5));
|
||||||
|
EXPECT_TRUE(card.incrementCounter(1, -10));
|
||||||
|
EXPECT_EQ(card.getCounter(1), 0);
|
||||||
|
EXPECT_FALSE(card.getCounters().contains(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCardCounter, IncrementDoesNotExceedMax)
|
||||||
|
{
|
||||||
|
Server_Card card(CardRef{"TestCard", ""}, 1, 0, 0);
|
||||||
|
ASSERT_TRUE(card.setCounter(1, MAX_COUNTERS_ON_CARD - 5));
|
||||||
|
EXPECT_TRUE(card.incrementCounter(1, 10));
|
||||||
|
EXPECT_EQ(card.getCounter(1), MAX_COUNTERS_ON_CARD);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
86
tests/server_counter_test.cpp
Normal file
86
tests/server_counter_test.cpp
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
/** @file server_counter_test.cpp
|
||||||
|
* @brief Tests for Server_Counter operations.
|
||||||
|
* @ingroup Tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
#include <libcockatrice/network/server/remote/game/server_counter.h>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
TEST(ServerCounter, IncrementDoesNotOverflow)
|
||||||
|
{
|
||||||
|
Server_Counter c(1, "test", color(), 10, std::numeric_limits<int>::max());
|
||||||
|
bool changed = c.incrementCount(1);
|
||||||
|
EXPECT_FALSE(changed);
|
||||||
|
EXPECT_EQ(c.getCount(), std::numeric_limits<int>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCounter, DecrementDoesNotUnderflow)
|
||||||
|
{
|
||||||
|
Server_Counter c(1, "test", color(), 10, std::numeric_limits<int>::min());
|
||||||
|
bool changed = c.incrementCount(-1);
|
||||||
|
EXPECT_FALSE(changed);
|
||||||
|
EXPECT_EQ(c.getCount(), std::numeric_limits<int>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCounter, SetCountReturnsFalseWhenUnchanged)
|
||||||
|
{
|
||||||
|
Server_Counter c(1, "test", color(), 10, 50);
|
||||||
|
bool changed = c.setCount(50);
|
||||||
|
EXPECT_FALSE(changed);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCounter, IncrementReturnsChangeStatus)
|
||||||
|
{
|
||||||
|
Server_Counter c(1, "test", color(), 10, 50);
|
||||||
|
EXPECT_TRUE(c.incrementCount(10));
|
||||||
|
EXPECT_EQ(c.getCount(), 60);
|
||||||
|
EXPECT_FALSE(c.incrementCount(0));
|
||||||
|
EXPECT_EQ(c.getCount(), 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCounter, LargePositiveDeltaDoesNotOverflow)
|
||||||
|
{
|
||||||
|
Server_Counter c(1, "test", color(), 10, std::numeric_limits<int>::max() - 10);
|
||||||
|
bool changed = c.incrementCount(std::numeric_limits<int>::max());
|
||||||
|
EXPECT_TRUE(changed); // Value changes from INT_MAX-10 to INT_MAX (clamped)
|
||||||
|
EXPECT_EQ(c.getCount(), std::numeric_limits<int>::max());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCounter, LargeNegativeDeltaDoesNotUnderflow)
|
||||||
|
{
|
||||||
|
Server_Counter c(1, "test", color(), 10, std::numeric_limits<int>::min() + 10);
|
||||||
|
bool changed = c.incrementCount(std::numeric_limits<int>::min());
|
||||||
|
EXPECT_TRUE(changed); // Value changes from INT_MIN+10 to INT_MIN (clamped)
|
||||||
|
EXPECT_EQ(c.getCount(), std::numeric_limits<int>::min());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCounter, SetCountReturnsTrueWhenChanged)
|
||||||
|
{
|
||||||
|
Server_Counter c(1, "test", color(), 10, 50);
|
||||||
|
EXPECT_TRUE(c.setCount(100));
|
||||||
|
EXPECT_EQ(c.getCount(), 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCounter, BasicIncrementWorks)
|
||||||
|
{
|
||||||
|
Server_Counter c(1, "test", color(), 10, 50);
|
||||||
|
EXPECT_TRUE(c.incrementCount(10));
|
||||||
|
EXPECT_EQ(c.getCount(), 60);
|
||||||
|
EXPECT_TRUE(c.incrementCount(-20));
|
||||||
|
EXPECT_EQ(c.getCount(), 40);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ServerCounter, MixedExtremesDoNotClamp)
|
||||||
|
{
|
||||||
|
Server_Counter c(1, "test", color(), 10, std::numeric_limits<int>::max());
|
||||||
|
bool changed = c.incrementCount(std::numeric_limits<int>::min());
|
||||||
|
EXPECT_TRUE(changed);
|
||||||
|
EXPECT_EQ(c.getCount(), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
::testing::InitGoogleTest(&argc, argv);
|
||||||
|
return RUN_ALL_TESTS();
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue