wip: add overengineered full socket test

This commit is contained in:
punchready 2021-06-08 05:01:32 +02:00
parent 8daa7afac3
commit afc413507a
4 changed files with 282 additions and 30 deletions

View File

@ -45,7 +45,7 @@ public class UserManager {
} }
/** Called on a new WebSocket connection. Places the WebSocket and its ResourceDescriptor in a HashMap. */ /** Called on a new WebSocket connection. Places the WebSocket and its ResourceDescriptor in a HashMap. */
void connectUser(WebSocket conn) { public void connectUser(WebSocket conn) {
Logger.debug("Connected new user"); Logger.debug("Connected new user");
synchronized(clients) { synchronized(clients) {
clients.put(conn, new Client(conn)); clients.put(conn, new Client(conn));
@ -57,7 +57,7 @@ public class UserManager {
* @param conn is the {@link WebSocket} that sent the message * @param conn is the {@link WebSocket} that sent the message
* @param message is the {@link String} sent by the connection * @param message is the {@link String} sent by the connection
*/ */
void messageReceived(WebSocket conn, String message) { public void messageReceived(WebSocket conn, String message) {
Logger.debug("Message received from {}", conn); Logger.debug("Message received from {}", conn);
Client client = clients.get(conn); Client client = clients.get(conn);
@ -90,7 +90,7 @@ public class UserManager {
} }
/** Called on closed connection. */ /** Called on closed connection. */
void disconnectUser(WebSocket conn, boolean closedByRemote) { public void disconnectUser(WebSocket conn, boolean closedByRemote) {
Logger.info("Disconnecting client '{}'", conn); Logger.info("Disconnecting client '{}'", conn);
Client client = clients.get(conn); Client client = clients.get(conn);

View File

@ -0,0 +1,81 @@
package uulm.teamname.marvelous.server;
import uulm.teamname.marvelous.gamelibrary.config.*;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ThreadLocalRandom;
public class BaseGameLogicTest {
protected static final Iterator<Integer> randomIntegers = ThreadLocalRandom.current().ints().iterator();
protected static final PartyConfig partyConfig = new PartyConfig();
protected static final CharacterConfig characterConfig = new CharacterConfig();
protected static final ScenarioConfig scenarioConfig = new ScenarioConfig();
protected static final ArrayList<Integer> player1Selection = new ArrayList<>();
protected static final ArrayList<Integer> player2Selection = new ArrayList<>();
protected static void generate() {
partyConfig.maxRounds = 50;
partyConfig.mindStoneCD = 2;
partyConfig.powerStoneCD = 3;
partyConfig.realityStoneCD = 4;
partyConfig.soulStoneCD = 5;
partyConfig.spaceStoneCD = 6;
partyConfig.timeStoneCD = 7;
partyConfig.mindStoneDMG = 3;
characterConfig.characters = new CharacterProperties[24];
for(int i = 0; i < characterConfig.characters.length; i++) {
characterConfig.characters[i] = generateCharacter(i);
}
scenarioConfig.name = generateName(20);
scenarioConfig.author = generateName(20);
scenarioConfig.scenario = new FieldType[20][20];
for(int x = 0; x < scenarioConfig.scenario[0].length; x++) {
for(int y = 0; y < scenarioConfig.scenario.length; y++) {
if(Math.abs(randomIntegers.next() % 100) < 10) {
scenarioConfig.scenario[y][x] = FieldType.ROCK;
}else {
scenarioConfig.scenario[y][x] = FieldType.GRASS;
}
}
}
for(int i = 0; i < 6; i++) {
player1Selection.add(i);
}
for(int i = 6; i < 12; i++) {
player2Selection.add(i);
}
}
private static String generateName(int length) {
StringBuilder name = new StringBuilder();
for (int j = 0; j < length; j++) {
name.append((char) (
65 + Math.abs(randomIntegers.next() % 26) + 32 * Math.abs(randomIntegers.next() % 2)
));
}
return name.toString();
}
private static CharacterProperties generateCharacter(int id) {
CharacterProperties props = new CharacterProperties();
props.characterID = id;
props.name = generateName(10);
props.HP = Math.abs(randomIntegers.next() % 15) + 5;
props.MP = Math.abs(randomIntegers.next() % 5) + 2;
props.AP = Math.abs(randomIntegers.next() % 5) + 2;
props.meleeDamage = Math.abs(randomIntegers.next() % 5) + 2;
props.rangedDamage = Math.abs(randomIntegers.next() % 5) + 2;
props.attackRange = Math.abs(randomIntegers.next() % 5) + 2;
return props;
}
}

View File

@ -0,0 +1,198 @@
package uulm.teamname.marvelous.server;
import org.java_websocket.WebSocket;
import org.junit.jupiter.api.*;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.tinylog.configuration.Configuration;
import uulm.teamname.marvelous.gamelibrary.json.JSON;
import uulm.teamname.marvelous.gamelibrary.messages.BasicMessage;
import uulm.teamname.marvelous.gamelibrary.messages.RoleEnum;
import uulm.teamname.marvelous.gamelibrary.messages.client.CharacterSelectionMessage;
import uulm.teamname.marvelous.gamelibrary.messages.client.HelloServerMessage;
import uulm.teamname.marvelous.gamelibrary.messages.client.PlayerReadyMessage;
import uulm.teamname.marvelous.gamelibrary.messages.server.*;
import uulm.teamname.marvelous.server.lobbymanager.LobbyManager;
import uulm.teamname.marvelous.server.netconnector.UserManager;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import static org.mockito.Mockito.*;
class MarvelousServerTest extends BaseGameLogicTest {
private static MockedStatic<Server> serverMock;
private static JSON json;
@BeforeAll
static void start() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<UserManager> um = UserManager.class.getDeclaredConstructor();
um.setAccessible(true);
um.newInstance();
Constructor<LobbyManager> lm = LobbyManager.class.getDeclaredConstructor();
lm.setAccessible(true);
lm.newInstance();
serverMock = Mockito.mockStatic(Server.class);
generate();
serverMock.when(Server::getMaxLobbies).thenReturn(10);
serverMock.when(Server::getPartyConfig).thenReturn(partyConfig);
serverMock.when(Server::getScenarioConfig).thenReturn(scenarioConfig);
serverMock.when(Server::getCharacterConfig).thenReturn(characterConfig);
json = new JSON(characterConfig);
Map<String, String> map = new HashMap<>();
Configuration.replace(map);
map.put("writer1", "console");
map.put("writer1.level", "trace");
map.put("writer1.format", "[{thread}] {level}: {message}");
Configuration.replace(map);
}
@Test
void main() {
UserManager m = UserManager.getInstance();
WebSocket p1 = mock(WebSocket.class);
WebSocket p2 = mock(WebSocket.class);
WebSocket s1 = mock(WebSocket.class);
WebSocket s2 = mock(WebSocket.class);
m.connectUser(p1);
ensureHandshake(m, p1, "Player 1", "1234", false);
m.connectUser(p2);
ensureHandshake(m, p2, "Player 2", "4321", false);
m.connectUser(s1);
ensureHandshake(m, s1, "Spectator 1", "3333", false);
m.connectUser(s2);
ensureHandshake(m, s2, "Spectator 2", "4444", false);
ensurePlayerReady(m, p1, true, RoleEnum.PLAYER);
ensurePlayerReady(m, p2, true, RoleEnum.PLAYER);
ensureSpectatorReady(m, s1, true, RoleEnum.SPECTATOR);
//these are broken right now because Server doesn't get mocked in lobby threads
/*
ensureCharacterSelection(m, p1, false);
ensureCharacterSelection(m, p2, true);
ensureReceived(p1, new GameStructureMessage());
ensureReceived(p2, new GameStructureMessage());
ensureReceived(s1, new GameStructureMessage());
*/
}
private void ensureHandshake(UserManager m, WebSocket c, String name, String deviceID, boolean runningGame) {
HelloServerMessage message = new HelloServerMessage();
message.name = name;
message.deviceID = deviceID;
HelloClientMessage response = new HelloClientMessage();
response.runningGame = runningGame;
ensureResponse(m, c, message, response);
}
private void ensurePlayerReady(UserManager m, WebSocket c, boolean startGame, RoleEnum role) {
PlayerReadyMessage message = new PlayerReadyMessage();
message.startGame = startGame;
message.role = role;
GameAssignmentMessage response = new GameAssignmentMessage();
//properties are left null because we can't test their content (they are random)
ensureResponse(m, c, message, response);
}
private void ensureSpectatorReady(UserManager m, WebSocket c, boolean startGame, RoleEnum role) {
PlayerReadyMessage message = new PlayerReadyMessage();
message.startGame = startGame;
message.role = role;
GeneralAssignmentMessage response = new GeneralAssignmentMessage();
//properties are left null because we can't test their content (they are random)
ensureResponse(m, c, message, response);
}
private void ensureCharacterSelection(UserManager m, WebSocket c, boolean selectionComplete) {
CharacterSelectionMessage message = new CharacterSelectionMessage();
message.characters = new Boolean[12];
for(int i = 0; i < 6; i++) {
message.characters[i] = true;
}
for(int i = 6; i < 12; i++) {
message.characters[i] = false;
}
ConfirmSelectionMessage response = new ConfirmSelectionMessage();
response.selectionComplete = selectionComplete;
ensureResponse(m, c, message, response);
}
/** Ensures that the given socket received the given response in response to the given message. */
private void ensureResponse(UserManager m, WebSocket c, BasicMessage message, BasicMessage response) {
Optional<String> in = json.stringify(message);
if(in.isPresent()) {
m.messageReceived(c, in.get());
verify(c).send((String)argThat(
(out)->verifyResponse((String)out, response)
));
clearInvocations(c);
}else {
throw new IllegalArgumentException("[TEST] Message in test call could not be serialized!");
}
}
/** Ensures that the given socket received the given response. */
private void ensureReceived(WebSocket c, BasicMessage response) {
verify(c).send((String)argThat(
(out)->verifyResponse((String)out, response)
));
}
/**
* Verifies a response message and requires all non-null fields of the expected message
* to equal the respective values in the actual received message.
*/
private <T extends BasicMessage> boolean verifyResponse(String actual, T expected) {
Optional<BasicMessage> in = json.parse(actual);
if(in.isPresent()) {
T message = (T) in.get();
for(Field field: expected.getClass().getDeclaredFields()) {
try {
Object value = field.get(expected);
if(value != null && !value.equals(field.get(message))) {
throw new IllegalArgumentException("[TEST] Field " + field.getName() + " expected to be '" + value + "' but was '" + field.get(message) + "'");
}
}catch(IllegalAccessException ignore) { }
}
return true;
}else {
throw new IllegalArgumentException("[TEST] Response in test call could not be deserialized!");
}
}
@AfterAll
static void stop() {
serverMock.close();
}
}

View File

@ -1,27 +0,0 @@
package uulm.teamname.marvelous.server.netconnector;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.net.InetSocketAddress;
import static org.mockito.Mockito.*;
class MarvelousServerTest {
MarvelousServer server;
@BeforeEach
void setUp() {
server = spy(MarvelousServer.class);
}
@Test
@Disabled
void runServerForTesting() throws InterruptedException {
// server = new MarvelousServer(new InetSocketAddress(1234));
// Thread serverThread = new Thread(() -> server.start());
// serverThread.join();
}
}