mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-09-22 17:45:09 -07:00
Merge branch 'master' into tooomm-qt5
This commit is contained in:
commit
6d7dadf657
149 changed files with 2962 additions and 423 deletions
|
|
@ -11,9 +11,11 @@ add_test(NAME playmat_resolver_test COMMAND playmat_resolver_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 server_rate_limiter_test COMMAND server_rate_limiter_test)
|
||||
add_test(NAME server_developer_role_test COMMAND server_developer_role_test)
|
||||
add_test(NAME warning_categories_test COMMAND warning_categories_test)
|
||||
add_test(NAME lag_monitor_test COMMAND lag_monitor_test)
|
||||
add_test(NAME latency_tracker_test COMMAND latency_tracker_test)
|
||||
add_test(NAME metrics_registry_test COMMAND metrics_registry_test)
|
||||
|
||||
add_test(NAME deck_hash_performance_test COMMAND deck_hash_performance_test)
|
||||
set_tests_properties(deck_hash_performance_test PROPERTIES TIMEOUT 15)
|
||||
|
|
@ -28,10 +30,12 @@ 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)
|
||||
add_executable(server_rate_limiter_test server_rate_limiter_test.cpp)
|
||||
add_executable(server_developer_role_test server_developer_role_test.cpp)
|
||||
add_executable(warning_categories_test warning_categories_test.cpp)
|
||||
add_executable(lag_monitor_test ${CMAKE_SOURCE_DIR}/cockatrice/src/client/lag_monitor.cpp lag_monitor_test.cpp)
|
||||
target_include_directories(lag_monitor_test PRIVATE ${CMAKE_SOURCE_DIR}/cockatrice/src)
|
||||
add_executable(latency_tracker_test latency_tracker_test.cpp)
|
||||
add_executable(metrics_registry_test ../servatrice/src/metrics_registry.cpp metrics_registry_test.cpp)
|
||||
|
||||
# Find GTest
|
||||
find_package(GTest)
|
||||
|
|
@ -69,9 +73,11 @@ if(NOT GTEST_FOUND)
|
|||
add_dependencies(server_card_counter_test gtest)
|
||||
add_dependencies(server_counter_test gtest)
|
||||
add_dependencies(server_rate_limiter_test gtest)
|
||||
add_dependencies(server_developer_role_test gtest)
|
||||
add_dependencies(warning_categories_test gtest)
|
||||
add_dependencies(lag_monitor_test gtest)
|
||||
add_dependencies(latency_tracker_test gtest)
|
||||
add_dependencies(metrics_registry_test gtest)
|
||||
endif()
|
||||
|
||||
include_directories(${GTEST_INCLUDE_DIRS})
|
||||
|
|
@ -100,6 +106,10 @@ target_link_libraries(
|
|||
target_link_libraries(
|
||||
server_counter_test libcockatrice_network Threads::Threads ${GTEST_BOTH_LIBRARIES} ${QT_MODULES_TEST}
|
||||
)
|
||||
target_link_libraries(
|
||||
server_developer_role_test libcockatrice_network libcockatrice_rng Threads::Threads ${GTEST_BOTH_LIBRARIES}
|
||||
${QT_MODULES_TEST}
|
||||
)
|
||||
target_link_libraries(
|
||||
server_rate_limiter_test libcockatrice_utility Threads::Threads ${GTEST_BOTH_LIBRARIES} ${QT_MODULES_TEST}
|
||||
)
|
||||
|
|
@ -110,6 +120,8 @@ target_link_libraries(lag_monitor_test Threads::Threads ${GTEST_BOTH_LIBRARIES}
|
|||
target_link_libraries(
|
||||
latency_tracker_test libcockatrice_network Threads::Threads ${GTEST_BOTH_LIBRARIES} ${QT_MODULES_TEST}
|
||||
)
|
||||
target_include_directories(metrics_registry_test PRIVATE ${CMAKE_SOURCE_DIR}/servatrice/src)
|
||||
target_link_libraries(metrics_registry_test ${TEST_QT_MODULES} Threads::Threads ${GTEST_BOTH_LIBRARIES})
|
||||
|
||||
add_subdirectory(card_zone_algorithms)
|
||||
add_subdirectory(carddatabase)
|
||||
|
|
|
|||
|
|
@ -134,6 +134,35 @@ TEST_F(AddCardAlgorithmTest, MidListInsertionPreservesOrder)
|
|||
EXPECT_EQ(knownList.at(2), &b);
|
||||
}
|
||||
|
||||
// Reconnecting to a game rebuilds zones from a ServerInfo_Zone. Non-coordinate zones
|
||||
// (hand, piles, stack) report x == 0 on every card, so inserting each rebuilt card at
|
||||
// that index would reverse the received server order. Appending (-1) keeps it.
|
||||
TEST_F(AddCardAlgorithmTest, RebuildInsertAtZeroReversesServerOrder)
|
||||
{
|
||||
MockCard a, b, c;
|
||||
CardZoneAlgorithms::addCardToList(knownList, &a, 0, false);
|
||||
CardZoneAlgorithms::addCardToList(knownList, &b, 0, false);
|
||||
CardZoneAlgorithms::addCardToList(knownList, &c, 0, false);
|
||||
|
||||
EXPECT_EQ(knownList.size(), 3);
|
||||
EXPECT_EQ(knownList.at(0), &c);
|
||||
EXPECT_EQ(knownList.at(1), &b);
|
||||
EXPECT_EQ(knownList.at(2), &a);
|
||||
}
|
||||
|
||||
TEST_F(AddCardAlgorithmTest, RebuildAppendPreservesServerOrder)
|
||||
{
|
||||
MockCard a, b, c;
|
||||
CardZoneAlgorithms::addCardToList(knownList, &a, -1, false);
|
||||
CardZoneAlgorithms::addCardToList(knownList, &b, -1, false);
|
||||
CardZoneAlgorithms::addCardToList(knownList, &c, -1, false);
|
||||
|
||||
EXPECT_EQ(knownList.size(), 3);
|
||||
EXPECT_EQ(knownList.at(0), &a);
|
||||
EXPECT_EQ(knownList.at(1), &b);
|
||||
EXPECT_EQ(knownList.at(2), &c);
|
||||
}
|
||||
|
||||
TEST_F(AddCardAlgorithmTest, KeepAnnotationsFalsePassedThrough)
|
||||
{
|
||||
MockCard card;
|
||||
|
|
|
|||
83
tests/metrics_registry_test.cpp
Normal file
83
tests/metrics_registry_test.cpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
#include <QCoreApplication>
|
||||
#include <QList>
|
||||
#include <gtest/gtest.h>
|
||||
#include <metrics_registry.h>
|
||||
|
||||
TEST(MetricsRegistryTest, EmptyRegistryHasZeroedCounters)
|
||||
{
|
||||
MetricsRegistry registry;
|
||||
|
||||
EXPECT_EQ(0, registry.totalCommands());
|
||||
EXPECT_EQ(0, registry.totalTimeMs());
|
||||
EXPECT_EQ(0, registry.activeTypeCount());
|
||||
EXPECT_EQ(0, registry.getGameStartSnapshot().count);
|
||||
}
|
||||
|
||||
TEST(MetricsRegistryTest, SampleIsRecordedInTotalsAndSlot)
|
||||
{
|
||||
MetricsRegistry registry;
|
||||
registry.observeCommand(MetricsRegistry::typeIdFor(0, 1000), 7);
|
||||
|
||||
EXPECT_EQ(1, registry.totalCommands());
|
||||
EXPECT_EQ(7, registry.totalTimeMs());
|
||||
EXPECT_EQ(1, registry.activeTypeCount());
|
||||
|
||||
const auto stats = registry.collectActiveStats();
|
||||
ASSERT_EQ(1, stats.size());
|
||||
EXPECT_EQ(MetricsRegistry::typeIdFor(0, 1000), stats[0].typeId);
|
||||
EXPECT_EQ(1, stats[0].count);
|
||||
EXPECT_EQ(7, stats[0].totalMs);
|
||||
}
|
||||
|
||||
TEST(MetricsRegistryTest, KindEncodingSeparatesSameExtensionNumber)
|
||||
{
|
||||
MetricsRegistry registry;
|
||||
const int sessionPing = MetricsRegistry::typeIdFor(0, 1000);
|
||||
const int roomLeaveRoom = MetricsRegistry::typeIdFor(1, 1000);
|
||||
ASSERT_NE(sessionPing, roomLeaveRoom);
|
||||
|
||||
registry.observeCommand(sessionPing, 1);
|
||||
registry.observeCommand(roomLeaveRoom, 5000);
|
||||
|
||||
EXPECT_EQ(2, registry.activeTypeCount());
|
||||
}
|
||||
|
||||
TEST(MetricsRegistryTest, OutOfRangeIdsLandInOverflowSlot)
|
||||
{
|
||||
MetricsRegistry registry;
|
||||
registry.observeCommand(-1, 4);
|
||||
registry.observeCommand(MetricsRegistry::MaxTypes + 12345, 4);
|
||||
|
||||
EXPECT_EQ(2, registry.totalCommands());
|
||||
EXPECT_EQ(1, registry.activeTypeCount()); // both collapsed into one slot
|
||||
EXPECT_EQ(8, registry.totalTimeMs());
|
||||
}
|
||||
|
||||
TEST(MetricsRegistryTest, NegativeDurationsAreClamped)
|
||||
{
|
||||
MetricsRegistry registry;
|
||||
registry.observeCommand(MetricsRegistry::typeIdFor(0, 1000), -50);
|
||||
|
||||
EXPECT_EQ(0, registry.totalTimeMs());
|
||||
}
|
||||
|
||||
TEST(MetricsRegistryTest, GameStartTrackedSeparatelyFromCommands)
|
||||
{
|
||||
MetricsRegistry registry;
|
||||
registry.observeGameStartDurationMs(120);
|
||||
|
||||
EXPECT_EQ(0, registry.totalCommands());
|
||||
EXPECT_EQ(0, registry.totalTimeMs());
|
||||
EXPECT_EQ(0, registry.activeTypeCount());
|
||||
|
||||
const auto snapshot = registry.getGameStartSnapshot();
|
||||
EXPECT_EQ(1, snapshot.count);
|
||||
EXPECT_EQ(120, snapshot.totalMs);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
|
@ -4,6 +4,8 @@
|
|||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QObject>
|
||||
#include <QPair>
|
||||
#include <QSet>
|
||||
#include <libcockatrice/card/format/format_legality_rules.h>
|
||||
#include <libcockatrice/card/set/card_set.h>
|
||||
|
|
@ -741,6 +743,135 @@ TEST_F(OracleImporterTest, StartImportParsesSetsLazily)
|
|||
ASSERT_FALSE(importer->getCardList().value("Lazy Import Card").isNull());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Scan progress reporting tests
|
||||
// ============================================================================
|
||||
|
||||
TEST(OracleScanProgress, ScanProgressReportsMonotonicBytesToTotal)
|
||||
{
|
||||
QJsonObject setObj;
|
||||
setObj["code"] = "tst";
|
||||
setObj["name"] = "Test Set";
|
||||
setObj["type"] = "expansion";
|
||||
setObj["releaseDate"] = "2024-01-01";
|
||||
QJsonArray cards;
|
||||
for (int i = 0; i < 40; ++i) {
|
||||
QJsonObject card;
|
||||
card["name"] = QString("Card %1").arg(i);
|
||||
card["text"] = "Some rules text used to bulk up the card payload.";
|
||||
card["layout"] = "normal";
|
||||
cards.append(card);
|
||||
}
|
||||
setObj["cards"] = cards;
|
||||
|
||||
QJsonObject root;
|
||||
root["data"] = QJsonObject{{"TST", setObj}};
|
||||
|
||||
const QByteArray data = QJsonDocument(root).toJson(QJsonDocument::Compact);
|
||||
|
||||
QList<QPair<qsizetype, qsizetype>> reports;
|
||||
RawJson::ScanError error;
|
||||
const QList<RawJson::SetRange> ranges =
|
||||
RawJson::scanSetRanges(data, &error, [&reports](qsizetype bytesRead, qsizetype totalBytes) {
|
||||
reports.append({bytesRead, totalBytes});
|
||||
});
|
||||
|
||||
ASSERT_FALSE(error.isError()) << error.message.toStdString();
|
||||
ASSERT_EQ(ranges.size(), 1);
|
||||
ASSERT_FALSE(reports.isEmpty());
|
||||
ASSERT_GT(reports.size(), 1);
|
||||
|
||||
qsizetype last = 0;
|
||||
for (const auto &[bytesRead, totalBytes] : reports) {
|
||||
ASSERT_EQ(totalBytes, data.size());
|
||||
ASSERT_GE(bytesRead, last) << "scan progress must be monotonic";
|
||||
ASSERT_LE(bytesRead, totalBytes) << "scan progress must not overshoot the document size";
|
||||
last = bytesRead;
|
||||
}
|
||||
ASSERT_EQ(reports.constLast().first, data.size()) << "scan must end at 100%";
|
||||
ASSERT_LE(reports.size(), 160) << "scan reports must be throttled";
|
||||
}
|
||||
|
||||
TEST(OracleScanProgress, ScanWithoutCallbackStillParses)
|
||||
{
|
||||
QJsonObject setObj;
|
||||
setObj["code"] = "tst";
|
||||
setObj["name"] = "Test Set";
|
||||
setObj["type"] = "expansion";
|
||||
setObj["releaseDate"] = "2024-01-01";
|
||||
setObj["cards"] = QJsonArray();
|
||||
|
||||
QJsonObject root;
|
||||
root["data"] = QJsonObject{{"TST", setObj}};
|
||||
|
||||
const QByteArray data = QJsonDocument(root).toJson(QJsonDocument::Compact);
|
||||
|
||||
RawJson::ScanError error;
|
||||
const QList<RawJson::SetRange> ranges = RawJson::scanSetRanges(data, &error);
|
||||
|
||||
ASSERT_FALSE(error.isError()) << error.message.toStdString();
|
||||
ASSERT_EQ(ranges.size(), 1);
|
||||
ASSERT_EQ(ranges.first().code, "tst");
|
||||
}
|
||||
|
||||
TEST_F(OracleImporterTest, ReadSetsFromByteArrayEmitsScanProgress)
|
||||
{
|
||||
QJsonObject setObj;
|
||||
setObj["code"] = "tst";
|
||||
setObj["name"] = "Test Set";
|
||||
setObj["type"] = "expansion";
|
||||
setObj["releaseDate"] = "2024-01-01";
|
||||
QJsonArray cards;
|
||||
for (int i = 0; i < 40; ++i) {
|
||||
QJsonObject card;
|
||||
card["name"] = QString("Card %1").arg(i);
|
||||
cards.append(card);
|
||||
}
|
||||
setObj["cards"] = cards;
|
||||
|
||||
QJsonObject root;
|
||||
root["data"] = QJsonObject{{"TST", setObj}};
|
||||
|
||||
const QByteArray data = QJsonDocument(root).toJson(QJsonDocument::Compact);
|
||||
|
||||
QList<QPair<qsizetype, qsizetype>> emissions;
|
||||
QObject::connect(importer, &OracleImporter::dataReadProgress,
|
||||
[&emissions](int bytesRead, int totalBytes) { emissions.append({bytesRead, totalBytes}); });
|
||||
|
||||
ASSERT_TRUE(importer->readSetsFromByteArray(data));
|
||||
ASSERT_FALSE(emissions.isEmpty());
|
||||
for (const auto &[bytesRead, totalBytes] : emissions) {
|
||||
ASSERT_EQ(totalBytes, data.size());
|
||||
ASSERT_GE(bytesRead, 0);
|
||||
ASSERT_LE(bytesRead, totalBytes);
|
||||
}
|
||||
ASSERT_EQ(emissions.constLast().first, data.size());
|
||||
}
|
||||
|
||||
TEST_F(OracleImporterTest, DisablingProgressReportingSuppressesScanEmissions)
|
||||
{
|
||||
QJsonObject setObj;
|
||||
setObj["code"] = "tst";
|
||||
setObj["name"] = "Test Set";
|
||||
setObj["type"] = "expansion";
|
||||
setObj["releaseDate"] = "2024-01-01";
|
||||
setObj["cards"] = QJsonArray();
|
||||
QJsonObject root;
|
||||
root["data"] = QJsonObject{{"TST", setObj}};
|
||||
const QByteArray data = QJsonDocument(root).toJson(QJsonDocument::Compact);
|
||||
|
||||
int emissions = 0;
|
||||
QObject::connect(importer, &OracleImporter::dataReadProgress, [&emissions](int, int) { ++emissions; });
|
||||
|
||||
importer->setProgressReporting(false);
|
||||
ASSERT_TRUE(importer->readSetsFromByteArray(data));
|
||||
ASSERT_EQ(emissions, 0);
|
||||
|
||||
importer->setProgressReporting(true);
|
||||
ASSERT_TRUE(importer->readSetsFromByteArray(data));
|
||||
ASSERT_GT(emissions, 0);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
|
|
|
|||
137
tests/server_developer_role_test.cpp
Normal file
137
tests/server_developer_role_test.cpp
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
/** @file server_developer_role_test.cpp
|
||||
* @brief Tests for the developer staff role authorization and dispatch.
|
||||
* @ingroup Tests
|
||||
*/
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
#include <libcockatrice/network/server/remote/server.h>
|
||||
#include <libcockatrice/network/server/remote/server_protocolhandler.h>
|
||||
#include <libcockatrice/protocol/pb/command_get_server_stats.pb.h>
|
||||
#include <libcockatrice/protocol/pb/commands.pb.h>
|
||||
#include <libcockatrice/protocol/pb/developer_commands.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_user.pb.h>
|
||||
#include <libcockatrice/rng/rng_abstract.h>
|
||||
|
||||
// The server_remote library references the global RNG, which is normally
|
||||
// defined by the servatrice/client executable main(). Provide a stub so the
|
||||
// unit test can link against it.
|
||||
RNG_Abstract *rng = nullptr;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
class TestDeveloperHandler : public Server_ProtocolHandler
|
||||
{
|
||||
public:
|
||||
explicit TestDeveloperHandler(Server *_server) : Server_ProtocolHandler(_server, nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
QString getAddress() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
QString getConnectionType() const override
|
||||
{
|
||||
return {};
|
||||
}
|
||||
|
||||
// Buffer the last response code sent to the client so tests can assert on
|
||||
// the outcome of processCommandContainer().
|
||||
Response::ResponseCode lastResponseCode = Response::RespNothing;
|
||||
int dispatchCount = 0;
|
||||
|
||||
protected:
|
||||
void transmitProtocolItem(const ServerMessage &item) override
|
||||
{
|
||||
if (item.message_type() == ServerMessage::RESPONSE) {
|
||||
lastResponseCode = item.response().response_code();
|
||||
}
|
||||
}
|
||||
|
||||
Response::ResponseCode
|
||||
processExtendedDeveloperCommand(int cmdType, const DeveloperCommand &, ResponseContainer &) override
|
||||
{
|
||||
++dispatchCount;
|
||||
// Fail closed for anything not explicitly handled.
|
||||
if (cmdType != DeveloperCommand::GET_SERVER_STATS) {
|
||||
return Response::RespFunctionNotAllowed;
|
||||
}
|
||||
return Response::RespOk;
|
||||
}
|
||||
};
|
||||
|
||||
class DeveloperRoleTest : public ::testing::Test
|
||||
{
|
||||
protected:
|
||||
Server server;
|
||||
TestDeveloperHandler handler{&server};
|
||||
|
||||
void setUserLevel(uint32_t level)
|
||||
{
|
||||
ServerInfo_User user;
|
||||
user.set_user_level(level);
|
||||
handler.setUserInfo(user);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(DeveloperRoleTest, RejectsWhenNotLoggedIn)
|
||||
{
|
||||
CommandContainer cont;
|
||||
cont.add_developer_command();
|
||||
handler.processCommandContainer(cont);
|
||||
EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded);
|
||||
EXPECT_EQ(handler.dispatchCount, 0);
|
||||
}
|
||||
|
||||
TEST_F(DeveloperRoleTest, RejectsPlainUser)
|
||||
{
|
||||
setUserLevel(ServerInfo_User::IsUser | ServerInfo_User::IsRegistered);
|
||||
|
||||
CommandContainer cont;
|
||||
cont.add_developer_command();
|
||||
handler.processCommandContainer(cont);
|
||||
EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded);
|
||||
EXPECT_EQ(handler.dispatchCount, 0);
|
||||
}
|
||||
|
||||
TEST_F(DeveloperRoleTest, RejectsModeratorThatIsNotDeveloper)
|
||||
{
|
||||
setUserLevel(ServerInfo_User::IsModerator);
|
||||
|
||||
CommandContainer cont;
|
||||
cont.add_developer_command();
|
||||
handler.processCommandContainer(cont);
|
||||
EXPECT_EQ(handler.lastResponseCode, Response::RespLoginNeeded);
|
||||
}
|
||||
|
||||
TEST_F(DeveloperRoleTest, DispatchesToDeveloperCommandForDeveloper)
|
||||
{
|
||||
setUserLevel(ServerInfo_User::IsDeveloper);
|
||||
|
||||
CommandContainer cont;
|
||||
DeveloperCommand *cmd = cont.add_developer_command();
|
||||
cmd->MutableExtension(Command_GetServerStats::ext);
|
||||
handler.processCommandContainer(cont);
|
||||
EXPECT_EQ(handler.lastResponseCode, Response::RespOk);
|
||||
EXPECT_EQ(handler.dispatchCount, 1);
|
||||
}
|
||||
|
||||
TEST_F(DeveloperRoleTest, FailClosedForUnknownDeveloperCommand)
|
||||
{
|
||||
setUserLevel(ServerInfo_User::IsDeveloper);
|
||||
|
||||
CommandContainer cont;
|
||||
cont.add_developer_command(); // no extension set -> getPbExtension() returns -1
|
||||
handler.processCommandContainer(cont);
|
||||
EXPECT_EQ(handler.lastResponseCode, Response::RespFunctionNotAllowed);
|
||||
EXPECT_EQ(handler.dispatchCount, 1);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
|
|
@ -300,6 +300,12 @@ TEST_F(SettingsDefaultsTest, Chat_RoomHistory_Default)
|
|||
ASSERT_EQ(s.getRoomHistory(), true);
|
||||
}
|
||||
|
||||
TEST_F(SettingsDefaultsTest, Chat_IgnoreAllPrivateMessages_Default)
|
||||
{
|
||||
ChatSettings s(settingsPath, nullptr);
|
||||
ASSERT_EQ(s.getIgnoreAllPrivateMessages(), false);
|
||||
}
|
||||
|
||||
// --- PersonalSettings ---
|
||||
|
||||
TEST_F(SettingsDefaultsTest, Personal_Lang_Default)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue