Anton Tarasenko
2 years ago
4 changed files with 884 additions and 16 deletions
@ -0,0 +1,563 @@ |
|||||||
|
/** |
||||||
|
* Author: dkanus |
||||||
|
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore |
||||||
|
* License: GPL |
||||||
|
* Copyright 2023 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia 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 General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class TEST_Voting extends TestCase |
||||||
|
abstract |
||||||
|
dependsOn(VotingModel); |
||||||
|
|
||||||
|
enum ExpectedOutcome { |
||||||
|
TEST_EO_Continue, |
||||||
|
TEST_EO_End, |
||||||
|
TEST_EO_EndDraw, |
||||||
|
}; |
||||||
|
|
||||||
|
protected static function VotingModel MakeVotingModel(VotingModel.VotingPolicies policies) { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
model = VotingModel(__().memory.Allocate(class'VotingModel')); |
||||||
|
model.Initialize(policies); |
||||||
|
return model; |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SetVoters( |
||||||
|
VotingModel model, |
||||||
|
optional string voterID0, |
||||||
|
optional string voterID1, |
||||||
|
optional string voterID2, |
||||||
|
optional string voterID3, |
||||||
|
optional string voterID4, |
||||||
|
optional string voterID5, |
||||||
|
optional string voterID6, |
||||||
|
optional string voterID7, |
||||||
|
optional string voterID8, |
||||||
|
optional string voterID9 |
||||||
|
) { |
||||||
|
local UserID nextID; |
||||||
|
local array<UserID> voterIDs; |
||||||
|
|
||||||
|
if (voterID0 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID0)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID1 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID1)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID2 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID2)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID3 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID3)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID4 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID4)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID5 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID5)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID6 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID6)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID7 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID7)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID8 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID8)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
if (voterID9 != "") { |
||||||
|
nextID = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
nextID.Initialize(__().text.FromString(voterID9)); |
||||||
|
voterIDs[voterIDs.length] = nextID; |
||||||
|
} |
||||||
|
model.UpdatePotentialVoters(voterIDs); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function MakeFaultyYesVote( |
||||||
|
VotingModel model, |
||||||
|
string voterID, |
||||||
|
VotingModel.VotingResult expected) { |
||||||
|
local UserID id; |
||||||
|
|
||||||
|
id = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
id.Initialize(__().text.FromString(voterID)); |
||||||
|
Issue("Illegal vote had unexpected result."); |
||||||
|
TEST_ExpectTrue(model.AddVote(id, true) == expected); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function MakeFaultyNoVote( |
||||||
|
VotingModel model, |
||||||
|
string voterID, |
||||||
|
VotingModel.VotingResult expected) { |
||||||
|
local UserID id; |
||||||
|
|
||||||
|
id = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
id.Initialize(__().text.FromString(voterID)); |
||||||
|
Issue("Illegal vote had unexpected result."); |
||||||
|
TEST_ExpectTrue(model.AddVote(id, false) == expected); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function VoteYes(VotingModel model, string voterID, ExpectedOutcome expected) { |
||||||
|
local UserID id; |
||||||
|
|
||||||
|
id = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
id.Initialize(__().text.FromString(voterID)); |
||||||
|
Issue("Failed to add legitimate vote."); |
||||||
|
TEST_ExpectTrue(model.AddVote(id, true) == VFR_Success); |
||||||
|
if (expected == TEST_EO_Continue) { |
||||||
|
Issue("Vote, that shouldn't have ended voting, ended it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
} else if (expected == TEST_EO_End) { |
||||||
|
Issue("Vote, that should've ended voting with one side's victory, didn't do it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Success); |
||||||
|
} else if (expected == TEST_EO_EndDraw) { |
||||||
|
Issue("Vote, that should've ended voting with a draw, didn't do it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Draw); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static function VoteNo(VotingModel model, string voterID, ExpectedOutcome expected) { |
||||||
|
local UserID id; |
||||||
|
|
||||||
|
id = UserID(__().memory.Allocate(class'UserID')); |
||||||
|
id.Initialize(__().text.FromString(voterID)); |
||||||
|
Issue("Failed to add legitimate vote."); |
||||||
|
TEST_ExpectTrue(model.AddVote(id, false) == VFR_Success); |
||||||
|
if (expected == TEST_EO_Continue) { |
||||||
|
Issue("Vote, that shouldn't have ended voting, ended it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
} else if (expected == TEST_EO_End) { |
||||||
|
Issue("Vote, that should've ended voting with one side's victory, didn't do it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Failure); |
||||||
|
} else if (expected == TEST_EO_EndDraw) { |
||||||
|
Issue("Vote, that should've ended voting with a draw, didn't do it."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Draw); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected static function TESTS() { |
||||||
|
Test_RestrictiveVoting(); |
||||||
|
Test_CanLeaveVoting(); |
||||||
|
Test_CanChangeVoting(); |
||||||
|
Test_All(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_RestrictiveVoting() { |
||||||
|
SubTest_RestrictiveYesVoting(); |
||||||
|
SubTest_RestrictiveNoVoting(); |
||||||
|
SubTest_RestrictiveDrawVoting(); |
||||||
|
SubTest_RestrictiveFaultyVoting(); |
||||||
|
SubTest_RestrictiveDisconnectVoting(); |
||||||
|
SubTest_RestrictiveReconnectVoting(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveYesVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"yes\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveNoVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"no\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteNo(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveDrawVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"draw\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_EndDraw); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveFaultyVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"faulty\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
MakeFaultyYesVote(model, "3", VFR_AlreadyVoted); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "7", VFR_NotAllowed); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "7", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "5", VFR_AlreadyVoted); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "3", VFR_CannotChangeVote); |
||||||
|
VoteYes(model, "6", TEST_EO_End); |
||||||
|
MakeFaultyYesVote(model, "4", VFR_VotingEnded); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveDisconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"disconnect\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); // remove 1, 3, 7 - 3 "yes" votes |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteYes(model, "9", TEST_EO_Continue); |
||||||
|
// Here we're at 3 "no" votes, 3 "yes" votes out of 7 total; |
||||||
|
// disconnect "2" and "9" for "no" to win |
||||||
|
SetVoters(model, "4", "5", "6", "8", "10"); |
||||||
|
Issue("Unexpected result after voting users disconnected."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Failure); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_RestrictiveReconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing restrictive \"reconnecting\" voting."); |
||||||
|
model = MakeVotingModel(VP_Restrictive); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
// Disconnect 1 3 "yes" voters |
||||||
|
SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteYes(model, "9", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "3", VFR_NotAllowed); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteNo(model, "10", TEST_EO_EndDraw); |
||||||
|
} |
||||||
|
/* Testing restrictive "reconnecting" voting. |
||||||
|
Unexpected result after voting users reconnected. [1] */ |
||||||
|
protected static function Test_CanLeaveVoting() { |
||||||
|
SubTest_CanLeaveYesVoting(); |
||||||
|
SubTest_CanLeaveNoVoting(); |
||||||
|
SubTest_CanLeaveDrawVoting(); |
||||||
|
SubTest_CanLeaveFaultyVoting(); |
||||||
|
SubTest_CanLeaveDisconnectVoting(); |
||||||
|
SubTest_CanLeaveReconnectVoting(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveYesVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"yes\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
SetVoters(model, "1", "5", "6"); |
||||||
|
Issue("Unexpected result after voting users leaves."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
VoteYes(model, "1", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveNoVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"no\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteNo(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
SetVoters(model, "3", "4", "5"); |
||||||
|
Issue("Unexpected result after voting users leaves."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
VoteNo(model, "4", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveDrawVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"draw\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
SetVoters(model, "4"); |
||||||
|
Issue("Unexpected result after voting users leaves."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
VoteNo(model, "4", TEST_EO_EndDraw); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveFaultyVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"faulty\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
MakeFaultyYesVote(model, "3", VFR_AlreadyVoted); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "7", VFR_NotAllowed); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "7", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "5", VFR_AlreadyVoted); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "3", VFR_CannotChangeVote); |
||||||
|
SetVoters(model, "4", "5", "6"); |
||||||
|
Issue("Unexpected result after voting users leaves."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_InProgress); |
||||||
|
VoteYes(model, "6", TEST_EO_End); |
||||||
|
MakeFaultyYesVote(model, "4", VFR_VotingEnded); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveDisconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"leave\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteYes(model, "10", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanLeaveReconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"reconnecting\" voting where users are allowed to leave."); |
||||||
|
model = MakeVotingModel(VP_CanLeave); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
// Disconnect 1 3 "yes" voters |
||||||
|
SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteNo(model, "9", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "3", VFR_NotAllowed); |
||||||
|
VoteYes(model, "10", TEST_EO_EndDraw); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_CanChangeVoting() { |
||||||
|
SubTest_CanChangeYesVoting(); |
||||||
|
SubTest_CanChangeNoVoting(); |
||||||
|
SubTest_CanChangeDrawVoting(); |
||||||
|
SubTest_CanChangeFaultyVoting(); |
||||||
|
SubTest_CanChangeDisconnectVoting(); |
||||||
|
SubTest_CanChangeReconnectVoting(); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanChangeYesVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"yes\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteNo(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "6", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanChangeNoVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"no\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteNo(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanChangeDrawVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"draw\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_EndDraw); |
||||||
|
} |
||||||
|
protected static function SubTest_CanChangeFaultyVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"faulty\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6"); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "6", TEST_EO_End); |
||||||
|
MakeFaultyYesVote(model, "4", VFR_VotingEnded); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanChangeDisconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"disconnect\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "6", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
SetVoters(model, "2", "4", "5", "6", "8", "9", "10"); // remove 1, 3, 7 - 3 "yes" votes |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "9", TEST_EO_Continue); |
||||||
|
// Here we're at 3 "no" votes, 3 "yes" votes out of 7 total; |
||||||
|
// disconnect "6" and "9" for "yes" to win |
||||||
|
SetVoters(model, "2", "4", "5", "8", "10"); |
||||||
|
Issue("Unexpected result after voting users disconnected."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Success); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function SubTest_CanChangeReconnectVoting() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing \"reconnect\" voting where users are allowed to change their vote."); |
||||||
|
model = MakeVotingModel(VP_CanChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteNo(model, "2", TEST_EO_Continue); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_Continue); |
||||||
|
VoteYes(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
// Disconnect 1 3 "yes" voters |
||||||
|
SetVoters(model, "2", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "7", TEST_EO_Continue); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteYes(model, "9", TEST_EO_Continue); |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "3", VFR_NotAllowed); |
||||||
|
// Restore 3 "yes" voter |
||||||
|
SetVoters(model, "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteNo(model, "3", TEST_EO_End); |
||||||
|
} |
||||||
|
|
||||||
|
protected static function Test_All() { |
||||||
|
local VotingModel model; |
||||||
|
|
||||||
|
Context("Testing permissive voting options."); |
||||||
|
model = MakeVotingModel(VP_CanLeaveAndChangeVote); |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "1", TEST_EO_Continue); |
||||||
|
VoteYes(model, "2", TEST_EO_Continue); |
||||||
|
VoteNo(model, "3", TEST_EO_Continue); |
||||||
|
VoteYes(model, "4", TEST_EO_Continue); |
||||||
|
VoteNo(model, "5", TEST_EO_Continue); |
||||||
|
VoteNo(model, "6", TEST_EO_Continue); |
||||||
|
// Disconnect 1 and 5 voters |
||||||
|
SetVoters(model, "2", "3", "4", "6", "7", "8", "9", "10"); |
||||||
|
MakeFaultyNoVote(model, "1", VFR_NotAllowed); |
||||||
|
MakeFaultyNoVote(model, "5", VFR_NotAllowed); |
||||||
|
VoteYes(model, "3", TEST_EO_Continue); |
||||||
|
VoteNo(model, "7", TEST_EO_Continue); |
||||||
|
VoteNo(model, "8", TEST_EO_Continue); |
||||||
|
VoteNo(model, "9", TEST_EO_Continue); |
||||||
|
// Bring back 1, disconnect 3 and 6 |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9", "10"); |
||||||
|
VoteYes(model, "8", TEST_EO_Continue); |
||||||
|
VoteNo(model, "4", TEST_EO_Continue); |
||||||
|
// Disconnect 10, finishing voting (since now only 9 voters are available) |
||||||
|
SetVoters(model, "1", "2", "3", "4", "5", "6", "7", "8", "9"); |
||||||
|
Issue("Unexpected result after voting users disconnected."); |
||||||
|
TEST_ExpectTrue(model.GetStatus() == VPM_Failure); |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties { |
||||||
|
caseGroup = "Commands" |
||||||
|
caseName = "Voting model" |
||||||
|
} |
@ -0,0 +1,305 @@ |
|||||||
|
/** |
||||||
|
* Author: dkanus |
||||||
|
* Home repo: https://www.insultplayers.ru/git/AcediaFramework/AcediaCore |
||||||
|
* License: GPL |
||||||
|
* Copyright 2023 Anton Tarasenko |
||||||
|
*------------------------------------------------------------------------------ |
||||||
|
* This file is part of Acedia. |
||||||
|
* |
||||||
|
* Acedia is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU General Public License as published by |
||||||
|
* the Free Software Foundation, version 3 of the License, or |
||||||
|
* (at your option) any later version. |
||||||
|
* |
||||||
|
* Acedia 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 General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU General Public License |
||||||
|
* along with Acedia. If not, see <https://www.gnu.org/licenses/>. |
||||||
|
*/ |
||||||
|
class VotingModel extends AcediaObject; |
||||||
|
|
||||||
|
enum VotingPolicies { |
||||||
|
VP_Restrictive, |
||||||
|
VP_CanLeave, |
||||||
|
VP_CanChangeVote, |
||||||
|
VP_CanLeaveAndChangeVote |
||||||
|
}; |
||||||
|
|
||||||
|
enum VotingModelStatus { |
||||||
|
VPM_Uninitialized, |
||||||
|
VPM_InProgress, |
||||||
|
VPM_Success, |
||||||
|
VPM_Failure, |
||||||
|
VPM_Draw |
||||||
|
}; |
||||||
|
|
||||||
|
enum VotingResult { |
||||||
|
VFR_Success, |
||||||
|
VFR_NotAllowed, |
||||||
|
VFR_CannotChangeVote, |
||||||
|
VFR_AlreadyVoted, |
||||||
|
VFR_VotingEnded |
||||||
|
}; |
||||||
|
|
||||||
|
enum PlayerVoteStatus { |
||||||
|
PVS_NoVote, |
||||||
|
PVS_VoteFor, |
||||||
|
PVS_VoteAgainst |
||||||
|
}; |
||||||
|
|
||||||
|
var private VotingModelStatus status; |
||||||
|
|
||||||
|
var private bool policyCanLeave, policyCanChangeVote; |
||||||
|
|
||||||
|
var private array<UserID> votesFor, votesAgainst; |
||||||
|
var private array<UserID> storedVotesFor, storedVotesAgainst; |
||||||
|
var private array<UserID> allowedVoters; |
||||||
|
|
||||||
|
protected function Constructor() { |
||||||
|
status = VPM_Uninitialized; |
||||||
|
policyCanLeave = false; |
||||||
|
policyCanChangeVote = false; |
||||||
|
} |
||||||
|
|
||||||
|
protected function Finalizer() { |
||||||
|
_.memory.FreeMany(allowedVoters); |
||||||
|
_.memory.FreeMany(votesFor); |
||||||
|
_.memory.FreeMany(votesAgainst); |
||||||
|
_.memory.FreeMany(storedVotesFor); |
||||||
|
_.memory.FreeMany(storedVotesAgainst); |
||||||
|
allowedVoters.length = 0; |
||||||
|
votesFor.length = 0; |
||||||
|
votesAgainst.length = 0; |
||||||
|
storedVotesFor.length = 0; |
||||||
|
storedVotesAgainst.length = 0; |
||||||
|
} |
||||||
|
|
||||||
|
public final function Initialize(VotingPolicies policies) { |
||||||
|
if (status == VPM_Uninitialized) { |
||||||
|
policyCanLeave = (policies == VP_CanLeave) || (policies == VP_CanLeaveAndChangeVote); |
||||||
|
policyCanChangeVote = |
||||||
|
(policies == VP_CanChangeVote) || (policies == VP_CanLeaveAndChangeVote); |
||||||
|
} |
||||||
|
status = VPM_InProgress; |
||||||
|
} |
||||||
|
|
||||||
|
public final function VotingModelStatus GetStatus() { |
||||||
|
return status; |
||||||
|
} |
||||||
|
|
||||||
|
private final function RecountVotes() { |
||||||
|
local bool canOverturn, everyoneVoted; |
||||||
|
local int totalPossibleVotes; |
||||||
|
local int totalVotesFor, totalVotesAgainst; |
||||||
|
local int lowerVoteCount, upperVoteCount, undecidedVoteCount; |
||||||
|
|
||||||
|
if (status != VPM_InProgress) { |
||||||
|
return; |
||||||
|
} |
||||||
|
if (policyCanLeave) { |
||||||
|
totalVotesFor = votesFor.length + storedVotesFor.length; |
||||||
|
totalVotesAgainst = votesAgainst.length + storedVotesAgainst.length; |
||||||
|
totalPossibleVotes = |
||||||
|
allowedVoters.length + storedVotesFor.length + storedVotesAgainst.length; |
||||||
|
} else { |
||||||
|
totalVotesFor = votesFor.length; |
||||||
|
totalVotesAgainst = votesAgainst.length; |
||||||
|
totalPossibleVotes = allowedVoters.length; |
||||||
|
} |
||||||
|
lowerVoteCount = Min(totalVotesFor, totalVotesAgainst); |
||||||
|
upperVoteCount = Max(totalVotesFor, totalVotesAgainst); |
||||||
|
undecidedVoteCount = totalPossibleVotes - (lowerVoteCount + upperVoteCount); |
||||||
|
everyoneVoted = (undecidedVoteCount <= 0); |
||||||
|
canOverturn = lowerVoteCount + undecidedVoteCount >= upperVoteCount; |
||||||
|
if (everyoneVoted || !canOverturn) { |
||||||
|
if (totalVotesFor > totalVotesAgainst) { |
||||||
|
status = VPM_Success; |
||||||
|
} else if (totalVotesFor < totalVotesAgainst) { |
||||||
|
status = VPM_Failure; |
||||||
|
} else { |
||||||
|
status = VPM_Draw; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public final function bool AllowedToVote(UserID voter) { |
||||||
|
local int i; |
||||||
|
|
||||||
|
if (voter == none) { |
||||||
|
return false; |
||||||
|
} |
||||||
|
for (i = 0; i < allowedVoters.length; i += 1) { |
||||||
|
if (voter.IsEqual(allowedVoters[i])) { |
||||||
|
return true; |
||||||
|
} |
||||||
|
} |
||||||
|
return false; |
||||||
|
} |
||||||
|
|
||||||
|
public final function PlayerVoteStatus HasVoted(UserID voter) { |
||||||
|
local int i; |
||||||
|
|
||||||
|
if (voter == none) { |
||||||
|
return PVS_NoVote; |
||||||
|
} |
||||||
|
for (i = 0; i < votesFor.length; i += 1) { |
||||||
|
if (voter.IsEqual(votesFor[i])) { |
||||||
|
return PVS_VoteFor; |
||||||
|
} |
||||||
|
} |
||||||
|
for (i = 0; i < votesAgainst.length; i += 1) { |
||||||
|
if (voter.IsEqual(votesAgainst[i])) { |
||||||
|
return PVS_VoteAgainst; |
||||||
|
} |
||||||
|
} |
||||||
|
return PVS_NoVote; |
||||||
|
} |
||||||
|
|
||||||
|
private final function EraseVote(UserID voter) { |
||||||
|
local int i; |
||||||
|
|
||||||
|
if (voter == none) { |
||||||
|
return; |
||||||
|
} |
||||||
|
while (i < votesFor.length) { |
||||||
|
if (voter.IsEqual(votesFor[i])) { |
||||||
|
_.memory.Free(votesFor[i]); |
||||||
|
votesFor.Remove(i, 1); |
||||||
|
} else { |
||||||
|
i += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
i = 0; |
||||||
|
while (i < votesAgainst.length) { |
||||||
|
if (voter.IsEqual(votesAgainst[i])) { |
||||||
|
_.memory.Free(votesAgainst[i]); |
||||||
|
votesAgainst.Remove(i, 1); |
||||||
|
} else { |
||||||
|
i += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public final function UpdatePotentialVoters(array<UserID> potentialVoters) { |
||||||
|
local int i; |
||||||
|
|
||||||
|
_.memory.FreeMany(allowedVoters); |
||||||
|
allowedVoters.length = 0; |
||||||
|
for (i = 0; i < potentialVoters.length; i += 1) { |
||||||
|
potentialVoters[i].NewRef(); |
||||||
|
allowedVoters[i] = potentialVoters[i]; |
||||||
|
} |
||||||
|
RestoreStoredVoters(potentialVoters); |
||||||
|
FilterCurrentVoters(potentialVoters); |
||||||
|
RecountVotes(); |
||||||
|
} |
||||||
|
|
||||||
|
private final function RestoreStoredVoters(array<UserID> potentialVoters) { |
||||||
|
local int i, j; |
||||||
|
local bool isPotentialVoter; |
||||||
|
|
||||||
|
while (i < storedVotesFor.length) { |
||||||
|
isPotentialVoter = false; |
||||||
|
for (j = 0; j < potentialVoters.length; j += 1) { |
||||||
|
if (storedVotesFor[i].IsEqual(potentialVoters[j])) { |
||||||
|
isPotentialVoter = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (isPotentialVoter) { |
||||||
|
votesFor[votesFor.length] = storedVotesFor[i]; |
||||||
|
storedVotesFor.Remove(i, 1); |
||||||
|
} else { |
||||||
|
i += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
i = 0; |
||||||
|
while (i < storedVotesAgainst.length) { |
||||||
|
isPotentialVoter = false; |
||||||
|
for (j = 0; j < potentialVoters.length; j += 1) { |
||||||
|
if (storedVotesAgainst[i].IsEqual(potentialVoters[j])) { |
||||||
|
isPotentialVoter = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (isPotentialVoter) { |
||||||
|
votesAgainst[votesAgainst.length] = storedVotesAgainst[i]; |
||||||
|
storedVotesAgainst.Remove(i, 1); |
||||||
|
} else { |
||||||
|
i += 1; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
private final function FilterCurrentVoters(array<UserID> potentialVoters) { |
||||||
|
local int i, j; |
||||||
|
local bool isPotentialVoter; |
||||||
|
|
||||||
|
while (i < votesFor.length) { |
||||||
|
isPotentialVoter = false; |
||||||
|
for (j = 0; j < potentialVoters.length; j += 1) { |
||||||
|
if (votesFor[i].IsEqual(potentialVoters[j])) { |
||||||
|
isPotentialVoter = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (isPotentialVoter) { |
||||||
|
i += 1; |
||||||
|
} else { |
||||||
|
storedVotesFor[storedVotesFor.length] = votesFor[i]; |
||||||
|
votesFor.Remove(i, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
i = 0; |
||||||
|
while (i < votesAgainst.length) { |
||||||
|
isPotentialVoter = false; |
||||||
|
for (j = 0; j < potentialVoters.length; j += 1) { |
||||||
|
if (votesAgainst[i].IsEqual(potentialVoters[j])) { |
||||||
|
isPotentialVoter = true; |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
if (isPotentialVoter) { |
||||||
|
i += 1; |
||||||
|
} else { |
||||||
|
storedVotesAgainst[storedVotesAgainst.length] = votesAgainst[i]; |
||||||
|
votesAgainst.Remove(i, 1); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
public final function VotingResult AddVote(UserID voter, bool forSuccess) { |
||||||
|
local bool votesSameWay; |
||||||
|
local PlayerVoteStatus currentVote; |
||||||
|
|
||||||
|
if (status != VPM_InProgress) { |
||||||
|
return VFR_VotingEnded; |
||||||
|
} |
||||||
|
if (!AllowedToVote(voter)) { |
||||||
|
return VFR_NotAllowed; |
||||||
|
} |
||||||
|
currentVote = HasVoted(voter); |
||||||
|
votesSameWay = (forSuccess && currentVote == PVS_VoteFor) |
||||||
|
|| (!forSuccess && currentVote == PVS_VoteAgainst); |
||||||
|
if (votesSameWay) { |
||||||
|
return VFR_AlreadyVoted; |
||||||
|
} |
||||||
|
if (!policyCanChangeVote && currentVote != PVS_NoVote) { |
||||||
|
return VFR_CannotChangeVote; |
||||||
|
} |
||||||
|
EraseVote(voter); |
||||||
|
voter.NewRef(); |
||||||
|
if (forSuccess) { |
||||||
|
votesFor[votesFor.length] = voter; |
||||||
|
} else { |
||||||
|
votesAgainst[votesAgainst.length] = voter; |
||||||
|
} |
||||||
|
RecountVotes(); |
||||||
|
return VFR_Success; |
||||||
|
} |
||||||
|
|
||||||
|
defaultproperties { |
||||||
|
} |
Loading…
Reference in new issue