From 754b79d045b96758b0c5cd20ff860122e4f7e246 Mon Sep 17 00:00:00 2001 From: Manu Date: Fri, 1 May 2026 19:05:42 +0200 Subject: [PATCH 1/9] Validate troop limit commands --- libs/s25main/buildings/nobMilitary.cpp | 3 +++ tests/s25Main/integration/testAttacking.cpp | 12 ++++++++++++ 2 files changed, 15 insertions(+) diff --git a/libs/s25main/buildings/nobMilitary.cpp b/libs/s25main/buildings/nobMilitary.cpp index 6ca29ccaac..605dd15480 100644 --- a/libs/s25main/buildings/nobMilitary.cpp +++ b/libs/s25main/buildings/nobMilitary.cpp @@ -661,6 +661,9 @@ unsigned nobMilitary::CalcRequiredNumTroops(FrontierDistance assumedFrontierDist void nobMilitary::SetTroopLimit(const unsigned rank, const unsigned limit) { + if(rank >= NUM_SOLDIER_RANKS) + return; + troop_limits[rank] = limit; RegulateTroops(); } diff --git a/tests/s25Main/integration/testAttacking.cpp b/tests/s25Main/integration/testAttacking.cpp index 975d9c4cc1..16f66ae9d4 100644 --- a/tests/s25Main/integration/testAttacking.cpp +++ b/tests/s25Main/integration/testAttacking.cpp @@ -418,6 +418,18 @@ BOOST_FIXTURE_TEST_CASE(StartAttack, AttackFixture<>) TestFailingAttack(gwv, milBld1Pos, attackSrc, 1u); } +BOOST_FIXTURE_TEST_CASE(TroopLimitCommandIgnoresInvalidRank, AttackFixture<>) +{ + const unsigned maxTroops = milBld0->GetMaxTroopsCt(); + const unsigned limit = maxTroops + 10; + + this->SetTroopLimit(milBld0Pos, 0, limit); + BOOST_TEST_REQUIRE(milBld0->GetTroopLimit(0) == limit); + + this->SetTroopLimit(milBld0Pos, NUM_SOLDIER_RANKS, 0); + BOOST_TEST_REQUIRE(milBld0->GetTroopLimit(0) == limit); +} + BOOST_FIXTURE_TEST_CASE(ConquerBld, AttackFixture<>) { AddSoldiers(milBld0Pos, 1, 5); From c42cc5f3ee8e3dd5204848d5b5efc2e577cf8073 Mon Sep 17 00:00:00 2001 From: Manu Date: Tue, 26 May 2026 14:37:41 +0200 Subject: [PATCH 2/9] Validate serialized troop limit rank --- libs/s25main/GameCommands.h | 13 ++++++++++++- libs/s25main/buildings/nobMilitary.cpp | 4 +--- tests/s25Main/integration/testGameCommands.cpp | 13 +++++++++++++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/libs/s25main/GameCommands.h b/libs/s25main/GameCommands.h index db8c094f63..0479af7f03 100644 --- a/libs/s25main/GameCommands.h +++ b/libs/s25main/GameCommands.h @@ -17,6 +17,7 @@ #include "gameTypes/PactTypes.h" #include "gameTypes/SettingsTypes.h" #include "gameTypes/TempleProductionMode.h" +#include "gameData/MilitaryConsts.h" #include "s25util/Serializer.h" #include #include @@ -25,6 +26,16 @@ class GameWorld; namespace gc { +namespace detail { + inline uint8_t popTroopLimitRank(Serializer& ser) + { + const auto rank = ser.PopUnsignedChar(); + if(rank > MAX_MILITARY_RANK) + throw helpers::makeOutOfRange(rank, MAX_MILITARY_RANK); + return rank; + } +} // namespace detail + /// Basisklasse für sämtliche GameCommands mit Koordinaten class Coords : public GameCommand { @@ -220,7 +231,7 @@ class SetTroopLimit : public Coords : Coords(GCType::SetTroopLimit, pt), rank(rank), count(count) {} SetTroopLimit(Serializer& ser) - : Coords(GCType::SetTroopLimit, ser), rank(ser.PopUnsignedChar()), count(ser.PopUnsignedInt()) + : Coords(GCType::SetTroopLimit, ser), rank(detail::popTroopLimitRank(ser)), count(ser.PopUnsignedInt()) {} public: diff --git a/libs/s25main/buildings/nobMilitary.cpp b/libs/s25main/buildings/nobMilitary.cpp index 99a237e99b..323d49af8e 100644 --- a/libs/s25main/buildings/nobMilitary.cpp +++ b/libs/s25main/buildings/nobMilitary.cpp @@ -696,9 +696,7 @@ unsigned nobMilitary::CalcRequiredNumTroops(FrontierDistance assumedFrontierDist void nobMilitary::SetTroopLimit(const unsigned rank, const unsigned limit) { - if(rank >= NUM_SOLDIER_RANKS) - return; - + RTTR_Assert(rank <= MAX_MILITARY_RANK); troop_limits[rank] = limit; RegulateTroops(); } diff --git a/tests/s25Main/integration/testGameCommands.cpp b/tests/s25Main/integration/testGameCommands.cpp index be9876d878..47884c1def 100644 --- a/tests/s25Main/integration/testGameCommands.cpp +++ b/tests/s25Main/integration/testGameCommands.cpp @@ -12,6 +12,8 @@ #include "enum_cast.hpp" #include "factories/BuildingFactory.h" #include "figures/nofPassiveSoldier.h" +#include "helpers/serializeEnums.h" +#include "helpers/serializePoint.h" #include "postSystem/PostBox.h" #include "worldFixtures/WorldWithGCExecution.h" #include "worldFixtures/initGameRNG.hpp" @@ -55,6 +57,17 @@ static void dummySuppressUnused(std::ostream& out) BOOST_AUTO_TEST_SUITE(GameCommandSuite) +BOOST_AUTO_TEST_CASE(SetTroopLimitRejectsInvalidSerializedRank) +{ + gc::Deserializer ser; + helpers::pushEnum(ser, gc::GCType::SetTroopLimit); + helpers::pushPoint(ser, MapPoint(0, 0)); + ser.PushUnsignedChar(static_cast(MAX_MILITARY_RANK + 1u)); + ser.PushUnsignedInt(0); + + BOOST_REQUIRE_THROW(gc::GameCommand::Deserialize(ser), std::range_error); +} + BOOST_FIXTURE_TEST_CASE(PlaceFlagTest, WorldWithGCExecution2P) { (void)&dummySuppressUnused; From 31b6a918b9198f0f746634bc8818c7f390bffc17 Mon Sep 17 00:00:00 2001 From: Manu Date: Tue, 26 May 2026 14:40:49 +0200 Subject: [PATCH 3/9] Update invalid troop limit command test --- tests/s25Main/integration/testAttacking.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/s25Main/integration/testAttacking.cpp b/tests/s25Main/integration/testAttacking.cpp index 675da53b41..be871b9ef1 100644 --- a/tests/s25Main/integration/testAttacking.cpp +++ b/tests/s25Main/integration/testAttacking.cpp @@ -418,7 +418,7 @@ BOOST_FIXTURE_TEST_CASE(StartAttack, AttackFixture<>) TestFailingAttack(gwv, milBld1Pos, attackSrc, 1u); } -BOOST_FIXTURE_TEST_CASE(TroopLimitCommandIgnoresInvalidRank, AttackFixture<>) +BOOST_FIXTURE_TEST_CASE(TroopLimitCommandRejectsInvalidSerializedRank, AttackFixture<>) { const unsigned maxTroops = milBld0->GetMaxTroopsCt(); const unsigned limit = maxTroops + 10; @@ -426,7 +426,7 @@ BOOST_FIXTURE_TEST_CASE(TroopLimitCommandIgnoresInvalidRank, AttackFixture<>) this->SetTroopLimit(milBld0Pos, 0, limit); BOOST_TEST_REQUIRE(milBld0->GetTroopLimit(0) == limit); - this->SetTroopLimit(milBld0Pos, NUM_SOLDIER_RANKS, 0); + BOOST_REQUIRE_THROW(this->SetTroopLimit(milBld0Pos, NUM_SOLDIER_RANKS, 0), std::range_error); BOOST_TEST_REQUIRE(milBld0->GetTroopLimit(0) == limit); } From 79e37c659f9946c8c864d1b386abbe8f195f90fe Mon Sep 17 00:00:00 2001 From: Manu Date: Wed, 3 Jun 2026 17:39:59 +0200 Subject: [PATCH 4/9] Address troop limit rank review feedback --- libs/s25main/GameCommands.h | 4 ++-- libs/s25main/buildings/nobMilitary.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libs/s25main/GameCommands.h b/libs/s25main/GameCommands.h index 0479af7f03..f044528130 100644 --- a/libs/s25main/GameCommands.h +++ b/libs/s25main/GameCommands.h @@ -27,7 +27,7 @@ class GameWorld; namespace gc { namespace detail { - inline uint8_t popTroopLimitRank(Serializer& ser) + inline uint8_t popSoldierRank(Serializer& ser) { const auto rank = ser.PopUnsignedChar(); if(rank > MAX_MILITARY_RANK) @@ -231,7 +231,7 @@ class SetTroopLimit : public Coords : Coords(GCType::SetTroopLimit, pt), rank(rank), count(count) {} SetTroopLimit(Serializer& ser) - : Coords(GCType::SetTroopLimit, ser), rank(detail::popTroopLimitRank(ser)), count(ser.PopUnsignedInt()) + : Coords(GCType::SetTroopLimit, ser), rank(detail::popSoldierRank(ser)), count(ser.PopUnsignedInt()) {} public: diff --git a/libs/s25main/buildings/nobMilitary.cpp b/libs/s25main/buildings/nobMilitary.cpp index 323d49af8e..47c8a7b05e 100644 --- a/libs/s25main/buildings/nobMilitary.cpp +++ b/libs/s25main/buildings/nobMilitary.cpp @@ -696,7 +696,7 @@ unsigned nobMilitary::CalcRequiredNumTroops(FrontierDistance assumedFrontierDist void nobMilitary::SetTroopLimit(const unsigned rank, const unsigned limit) { - RTTR_Assert(rank <= MAX_MILITARY_RANK); + RTTR_Assert(rank < troop_limits.size()); troop_limits[rank] = limit; RegulateTroops(); } From 514a5099187015329a77aae5f64d7d76c16eb715 Mon Sep 17 00:00:00 2001 From: Manu Date: Mon, 22 Jun 2026 12:55:40 +0200 Subject: [PATCH 5/9] Apply remaining troop limit command review feedback --- libs/s25main/GameCommands.h | 2 +- tests/s25Main/integration/testAttacking.cpp | 19 +++++++++++++------ .../s25Main/integration/testGameCommands.cpp | 18 +++++++++++------- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/libs/s25main/GameCommands.h b/libs/s25main/GameCommands.h index f044528130..0243830829 100644 --- a/libs/s25main/GameCommands.h +++ b/libs/s25main/GameCommands.h @@ -540,7 +540,7 @@ class ChangeReserve : public Coords : Coords(GCType::ChangeReserve, pt), rank(rank), count(count) {} ChangeReserve(Serializer& ser) - : Coords(GCType::ChangeReserve, ser), rank(ser.PopUnsignedChar()), count(ser.PopUnsignedInt()) + : Coords(GCType::ChangeReserve, ser), rank(detail::popSoldierRank(ser)), count(ser.PopUnsignedInt()) {} public: diff --git a/tests/s25Main/integration/testAttacking.cpp b/tests/s25Main/integration/testAttacking.cpp index be871b9ef1..9b74e7514c 100644 --- a/tests/s25Main/integration/testAttacking.cpp +++ b/tests/s25Main/integration/testAttacking.cpp @@ -420,14 +420,21 @@ BOOST_FIXTURE_TEST_CASE(StartAttack, AttackFixture<>) BOOST_FIXTURE_TEST_CASE(TroopLimitCommandRejectsInvalidSerializedRank, AttackFixture<>) { - const unsigned maxTroops = milBld0->GetMaxTroopsCt(); - const unsigned limit = maxTroops + 10; + std::array expectedLimits{}; + for(unsigned rank = 0; rank < expectedLimits.size(); ++rank) + { + expectedLimits[rank] = (rank + 1) * 3; + this->SetTroopLimit(milBld0Pos, rank, expectedLimits[rank]); + } + + for(unsigned rank = 0; rank < expectedLimits.size(); ++rank) + BOOST_TEST_REQUIRE(milBld0->GetTroopLimit(rank) == expectedLimits[rank]); - this->SetTroopLimit(milBld0Pos, 0, limit); - BOOST_TEST_REQUIRE(milBld0->GetTroopLimit(0) == limit); + BOOST_REQUIRE_THROW(this->SetTroopLimit(milBld0Pos, NUM_SOLDIER_RANKS, expectedLimits.back() + 7), + std::range_error); - BOOST_REQUIRE_THROW(this->SetTroopLimit(milBld0Pos, NUM_SOLDIER_RANKS, 0), std::range_error); - BOOST_TEST_REQUIRE(milBld0->GetTroopLimit(0) == limit); + for(unsigned rank = 0; rank < expectedLimits.size(); ++rank) + BOOST_TEST_REQUIRE(milBld0->GetTroopLimit(rank) == expectedLimits[rank]); } BOOST_FIXTURE_TEST_CASE(ConquerBld, AttackFixture<>) diff --git a/tests/s25Main/integration/testGameCommands.cpp b/tests/s25Main/integration/testGameCommands.cpp index 47884c1def..92b2cc9d90 100644 --- a/tests/s25Main/integration/testGameCommands.cpp +++ b/tests/s25Main/integration/testGameCommands.cpp @@ -57,15 +57,19 @@ static void dummySuppressUnused(std::ostream& out) BOOST_AUTO_TEST_SUITE(GameCommandSuite) -BOOST_AUTO_TEST_CASE(SetTroopLimitRejectsInvalidSerializedRank) +BOOST_AUTO_TEST_CASE(SoldierRankCommandsRejectInvalidSerializedRank) { - gc::Deserializer ser; - helpers::pushEnum(ser, gc::GCType::SetTroopLimit); - helpers::pushPoint(ser, MapPoint(0, 0)); - ser.PushUnsignedChar(static_cast(MAX_MILITARY_RANK + 1u)); - ser.PushUnsignedInt(0); + const gc::GCType commands[] = {gc::GCType::SetTroopLimit, gc::GCType::ChangeReserve}; + for(const auto command : commands) + { + gc::Deserializer ser; + helpers::pushEnum(ser, command); + helpers::pushPoint(ser, MapPoint(0, 0)); + ser.PushUnsignedChar(static_cast(MAX_MILITARY_RANK + 1u)); + ser.PushUnsignedInt(0); - BOOST_REQUIRE_THROW(gc::GameCommand::Deserialize(ser), std::range_error); + BOOST_REQUIRE_THROW(gc::GameCommand::Deserialize(ser), std::range_error); + } } BOOST_FIXTURE_TEST_CASE(PlaceFlagTest, WorldWithGCExecution2P) From 2f5c2ff3cc7113906d69cbf5cce2186b33683b66 Mon Sep 17 00:00:00 2001 From: Manu Date: Tue, 23 Jun 2026 23:40:41 +0200 Subject: [PATCH 6/9] Address troop limit command test review feedback --- tests/s25Main/integration/testAttacking.cpp | 6 ++- .../s25Main/integration/testGameCommands.cpp | 44 +++++++++++++++---- 2 files changed, 40 insertions(+), 10 deletions(-) diff --git a/tests/s25Main/integration/testAttacking.cpp b/tests/s25Main/integration/testAttacking.cpp index 9b74e7514c..04b03860a3 100644 --- a/tests/s25Main/integration/testAttacking.cpp +++ b/tests/s25Main/integration/testAttacking.cpp @@ -421,16 +421,18 @@ BOOST_FIXTURE_TEST_CASE(StartAttack, AttackFixture<>) BOOST_FIXTURE_TEST_CASE(TroopLimitCommandRejectsInvalidSerializedRank, AttackFixture<>) { std::array expectedLimits{}; + const auto firstLimit = rttr::test::randomValue(1u, 20u); + const auto limitStep = rttr::test::randomValue(1u, 5u); for(unsigned rank = 0; rank < expectedLimits.size(); ++rank) { - expectedLimits[rank] = (rank + 1) * 3; + expectedLimits[rank] = firstLimit + rank * limitStep; this->SetTroopLimit(milBld0Pos, rank, expectedLimits[rank]); } for(unsigned rank = 0; rank < expectedLimits.size(); ++rank) BOOST_TEST_REQUIRE(milBld0->GetTroopLimit(rank) == expectedLimits[rank]); - BOOST_REQUIRE_THROW(this->SetTroopLimit(milBld0Pos, NUM_SOLDIER_RANKS, expectedLimits.back() + 7), + BOOST_REQUIRE_THROW(this->SetTroopLimit(milBld0Pos, NUM_SOLDIER_RANKS, expectedLimits.back() + limitStep), std::range_error); for(unsigned rank = 0; rank < expectedLimits.size(); ++rank) diff --git a/tests/s25Main/integration/testGameCommands.cpp b/tests/s25Main/integration/testGameCommands.cpp index 92b2cc9d90..9f92d94c57 100644 --- a/tests/s25Main/integration/testGameCommands.cpp +++ b/tests/s25Main/integration/testGameCommands.cpp @@ -28,6 +28,7 @@ #include "gameData/ShieldConsts.h" #include "rttr/test/random.hpp" #include +#include #include #if defined(PVS_STUDIO) || defined(__clang_analyzer__) @@ -55,19 +56,46 @@ static void dummySuppressUnused(std::ostream& out) } // LCOV_EXCL_STOP +namespace { +class CommandCaptureFactory : public GameCommandFactory +{ +public: + gc::GameCommandPtr lastCommand; + +private: + bool AddGC(gc::GameCommandPtr gc) override + { + lastCommand = std::move(gc); + return true; + } +}; + +template +gc::Deserializer serializeCapturedCommand(CreateCommand&& createCommand) +{ + CommandCaptureFactory factory; + createCommand(factory); + BOOST_TEST_REQUIRE(factory.lastCommand); + + gc::Deserializer ser; + factory.lastCommand->Serialize(ser); + return ser; +} +} // namespace + BOOST_AUTO_TEST_SUITE(GameCommandSuite) BOOST_AUTO_TEST_CASE(SoldierRankCommandsRejectInvalidSerializedRank) { - const gc::GCType commands[] = {gc::GCType::SetTroopLimit, gc::GCType::ChangeReserve}; - for(const auto command : commands) + const auto invalidRank = static_cast(MAX_MILITARY_RANK + 1u); + const auto pt = rttr::test::randomPoint(0, 20); + const auto count = rttr::test::randomValue(0u, 100u); + auto serializedCommands = std::array{ + serializeCapturedCommand([&](CommandCaptureFactory& factory) { factory.SetTroopLimit(pt, invalidRank, count); }), + serializeCapturedCommand([&](CommandCaptureFactory& factory) { factory.ChangeReserve(pt, invalidRank, count); })}; + + for(auto& ser : serializedCommands) { - gc::Deserializer ser; - helpers::pushEnum(ser, command); - helpers::pushPoint(ser, MapPoint(0, 0)); - ser.PushUnsignedChar(static_cast(MAX_MILITARY_RANK + 1u)); - ser.PushUnsignedInt(0); - BOOST_REQUIRE_THROW(gc::GameCommand::Deserialize(ser), std::range_error); } } From cbe3c1e891cb9f6fa164ca7245fc553fa71abce1 Mon Sep 17 00:00:00 2001 From: Manu Date: Tue, 23 Jun 2026 23:51:08 +0200 Subject: [PATCH 7/9] Harden troop limit command test setup --- tests/s25Main/integration/testAttacking.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/s25Main/integration/testAttacking.cpp b/tests/s25Main/integration/testAttacking.cpp index 04b03860a3..5e367a1041 100644 --- a/tests/s25Main/integration/testAttacking.cpp +++ b/tests/s25Main/integration/testAttacking.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -426,6 +427,10 @@ BOOST_FIXTURE_TEST_CASE(TroopLimitCommandRejectsInvalidSerializedRank, AttackFix for(unsigned rank = 0; rank < expectedLimits.size(); ++rank) { expectedLimits[rank] = firstLimit + rank * limitStep; + const bool isUniqueLimit = + std::find(expectedLimits.begin(), expectedLimits.begin() + rank, expectedLimits[rank]) + == expectedLimits.begin() + rank; + BOOST_TEST_REQUIRE(isUniqueLimit); this->SetTroopLimit(milBld0Pos, rank, expectedLimits[rank]); } From 567045aec4945966fd48f8894cc7b24ccd1736e4 Mon Sep 17 00:00:00 2001 From: Manu Date: Fri, 26 Jun 2026 01:42:20 +0200 Subject: [PATCH 8/9] Simplify soldier rank command tests --- tests/s25Main/integration/testAttacking.cpp | 10 ++-- .../s25Main/integration/testGameCommands.cpp | 51 ++++++++----------- 2 files changed, 23 insertions(+), 38 deletions(-) diff --git a/tests/s25Main/integration/testAttacking.cpp b/tests/s25Main/integration/testAttacking.cpp index 5e367a1041..3dcb6320f0 100644 --- a/tests/s25Main/integration/testAttacking.cpp +++ b/tests/s25Main/integration/testAttacking.cpp @@ -30,7 +30,6 @@ #include #include #include -#include #include #include #include @@ -422,15 +421,12 @@ BOOST_FIXTURE_TEST_CASE(StartAttack, AttackFixture<>) BOOST_FIXTURE_TEST_CASE(TroopLimitCommandRejectsInvalidSerializedRank, AttackFixture<>) { std::array expectedLimits{}; - const auto firstLimit = rttr::test::randomValue(1u, 20u); - const auto limitStep = rttr::test::randomValue(1u, 5u); + const auto firstLimit = rttr::test::randomValue(1u, 20u); + const auto limitStep = rttr::test::randomValue(1u, 5u); + // Use unique values to detect overwriting for(unsigned rank = 0; rank < expectedLimits.size(); ++rank) { expectedLimits[rank] = firstLimit + rank * limitStep; - const bool isUniqueLimit = - std::find(expectedLimits.begin(), expectedLimits.begin() + rank, expectedLimits[rank]) - == expectedLimits.begin() + rank; - BOOST_TEST_REQUIRE(isUniqueLimit); this->SetTroopLimit(milBld0Pos, rank, expectedLimits[rank]); } diff --git a/tests/s25Main/integration/testGameCommands.cpp b/tests/s25Main/integration/testGameCommands.cpp index 9f92d94c57..8cb8db762a 100644 --- a/tests/s25Main/integration/testGameCommands.cpp +++ b/tests/s25Main/integration/testGameCommands.cpp @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: GPL-2.0-or-later +#include "GameCommands.h" #include "GamePlayer.h" #include "PointOutput.h" #include "RttrForeachPt.h" @@ -28,7 +29,6 @@ #include "gameData/ShieldConsts.h" #include "rttr/test/random.hpp" #include -#include #include #if defined(PVS_STUDIO) || defined(__clang_analyzer__) @@ -57,47 +57,36 @@ static void dummySuppressUnused(std::ostream& out) // LCOV_EXCL_STOP namespace { -class CommandCaptureFactory : public GameCommandFactory +class TestSetTroopLimit : public gc::SetTroopLimit { public: - gc::GameCommandPtr lastCommand; - -private: - bool AddGC(gc::GameCommandPtr gc) override - { - lastCommand = std::move(gc); - return true; - } + TestSetTroopLimit(const MapPoint pt, const uint8_t rank, const uint32_t count) : gc::SetTroopLimit(pt, rank, count) + {} }; -template -gc::Deserializer serializeCapturedCommand(CreateCommand&& createCommand) +class TestChangeReserve : public gc::ChangeReserve { - CommandCaptureFactory factory; - createCommand(factory); - BOOST_TEST_REQUIRE(factory.lastCommand); - - gc::Deserializer ser; - factory.lastCommand->Serialize(ser); - return ser; -} +public: + TestChangeReserve(const MapPoint pt, const uint8_t rank, const uint32_t count) : gc::ChangeReserve(pt, rank, count) + {} +}; } // namespace BOOST_AUTO_TEST_SUITE(GameCommandSuite) BOOST_AUTO_TEST_CASE(SoldierRankCommandsRejectInvalidSerializedRank) { - const auto invalidRank = static_cast(MAX_MILITARY_RANK + 1u); - const auto pt = rttr::test::randomPoint(0, 20); - const auto count = rttr::test::randomValue(0u, 100u); - auto serializedCommands = std::array{ - serializeCapturedCommand([&](CommandCaptureFactory& factory) { factory.SetTroopLimit(pt, invalidRank, count); }), - serializeCapturedCommand([&](CommandCaptureFactory& factory) { factory.ChangeReserve(pt, invalidRank, count); })}; - - for(auto& ser : serializedCommands) - { - BOOST_REQUIRE_THROW(gc::GameCommand::Deserialize(ser), std::range_error); - } + constexpr auto invalidRank = static_cast(MAX_MILITARY_RANK + 1u); + const auto pt = rttr::test::randomPoint(); + const auto count = rttr::test::randomValue(); + + gc::Deserializer troopLimitCommand; + TestSetTroopLimit(pt, invalidRank, count).Serialize(troopLimitCommand); + BOOST_REQUIRE_THROW(gc::GameCommand::Deserialize(troopLimitCommand), std::range_error); + + gc::Deserializer reserveCommand; + TestChangeReserve(pt, invalidRank, count).Serialize(reserveCommand); + BOOST_REQUIRE_THROW(gc::GameCommand::Deserialize(reserveCommand), std::range_error); } BOOST_FIXTURE_TEST_CASE(PlaceFlagTest, WorldWithGCExecution2P) From e317c439dd939803d85966ef79e2d4a5ac0c22bf Mon Sep 17 00:00:00 2001 From: Manu Date: Sat, 27 Jun 2026 23:13:15 +0200 Subject: [PATCH 9/9] Clarify soldier rank test constructors --- tests/s25Main/integration/testGameCommands.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/s25Main/integration/testGameCommands.cpp b/tests/s25Main/integration/testGameCommands.cpp index 8cb8db762a..22ba315bde 100644 --- a/tests/s25Main/integration/testGameCommands.cpp +++ b/tests/s25Main/integration/testGameCommands.cpp @@ -57,6 +57,8 @@ static void dummySuppressUnused(std::ostream& out) // LCOV_EXCL_STOP namespace { +// Expose the protected constructors only to serialize the actual command format in this test, +// rather than duplicating the production Serialize() implementation. class TestSetTroopLimit : public gc::SetTroopLimit { public: