From 85d8d665aae922633aa9f5c6b92640cc13328f7f Mon Sep 17 00:00:00 2001 From: Oleg Agafonov Date: Sat, 27 Jan 2018 14:02:24 +0400 Subject: [PATCH] Tests: rewrite load tests for server stability and performance testing (see #4448) --- .../mage/test/load/LoadCallbackClient.java | 150 +++- .../org/mage/test/load/LoadPhaseManager.java | 70 +- .../java/org/mage/test/load/LoadTest.java | 707 ++++++++++++------ 3 files changed, 654 insertions(+), 273 deletions(-) diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java index 824efa60ba..a5626cbd38 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadCallbackClient.java @@ -1,28 +1,32 @@ package org.mage.test.load; +import mage.constants.PhaseStep; +import mage.constants.PlayerAction; import mage.interfaces.callback.CallbackClient; import mage.interfaces.callback.ClientCallback; import mage.remote.Session; +import mage.util.RandomUtil; import mage.utils.CompressUtil; -import mage.view.GameClientMessage; -import mage.view.GameView; -import mage.view.SimpleCardView; -import mage.view.TableClientMessage; +import mage.view.*; import org.apache.log4j.Logger; +import java.util.Random; import java.util.UUID; /** - * @author noxx + * @author JayDi85 */ public class LoadCallbackClient implements CallbackClient { - private static final Logger log = Logger.getLogger(LoadCallbackClient.class); + //private static final Logger log = Logger.getLogger(LoadCallbackClient.class); + private static final Logger log = Logger.getLogger("Load call"); private Session session; private UUID gameId; private UUID playerId; private boolean gameOver; + private String gameResult = "unknown"; + private boolean needToConcede = false; // will concede on first priority private volatile int controlCount; @@ -32,63 +36,152 @@ public class LoadCallbackClient implements CallbackClient { public void processCallback(ClientCallback callback) { //TODO controlCount = 0; - log.info(callback.getMethod()); callback.setData(CompressUtil.decompress(callback.getData())); + + /* + // random sleep can help with freezes (server concurrent access problem?!) + try { + Thread.sleep(RandomUtil.nextInt(1000)); + }catch (InterruptedException e) { + log.error("thread error", e); + } + */ + + log.info(callback.getMethod()); + log.info(getLogStartInfo() + "callback: " + callback.getMethod()); + switch (callback.getMethod()) { + case START_GAME: { TableClientMessage message = (TableClientMessage) callback.getData(); + log.info(getLogStartInfo() + "game started"); gameId = message.getGameId(); playerId = message.getPlayerId(); session.joinGame(message.getGameId()); startControlThread(); break; } - case GAME_INFORM: { + + case GAME_INFORM: + case GAME_INFORM_PERSONAL: { GameClientMessage message = (GameClientMessage) callback.getData(); - log.info("Inform: " + message.getMessage()); gameView = message.getGameView(); + log.info(getLogStartInfo() + "Inform: " + message.getMessage()); break; } - case GAME_INIT: - break; + case GAME_TARGET: { GameClientMessage message = (GameClientMessage) callback.getData(); + this.gameView = message.getGameView(); log.info("Target: " + message.getMessage()); switch (message.getMessage()) { case "Select a starting player": session.sendPlayerUUID(gameId, playerId); - break; + return; + //break; case "Select a card to discard": - log.info("hand size: " + gameView.getHand().size()); + log.info(getLogStartInfo() + "hand size: " + gameView.getHand().size()); SimpleCardView card = gameView.getHand().values().iterator().next(); session.sendPlayerUUID(gameId, card.getId()); - break; + return; + //break; + default: + log.error(getLogStartInfo() + "unknown GAME_TARGET message: " + message.toString()); } break; } + case GAME_ASK: { GameClientMessage message = (GameClientMessage) callback.getData(); - log.info("Ask: " + message.getMessage()); - if (message.getMessage().equals("Do you want to take a mulligan?")) { + log.info(getLogStartInfo() + "Ask: " + message.getMessage()); + if (message.getMessage().startsWith("Mulligan")) { session.sendPlayerBoolean(gameId, false); + return; + } else { + log.error(getLogStartInfo() + "unknown GAME_ASK message: " + message.toString()); } break; } + case GAME_SELECT: { GameClientMessage message = (GameClientMessage) callback.getData(); log.info("Select: " + message.getMessage()); - if (LoadPhaseManager.getInstance().isSkip(message.getGameView(), message.getMessage(), playerId)) { - log.info("Skipped: " + message.getMessage()); - session.sendPlayerBoolean(gameId, false); + this.gameView = message.getGameView(); + + // concede + if (needToConcede) { + log.info(getLogStartInfo() + "game conceded"); + needToConcede = false; + session.sendPlayerAction(PlayerAction.CONCEDE, gameId, null); + return; } - break; + + // end priority step + session.sendPlayerBoolean(gameId, false); + return; +/* + if (LoadPhaseManager.getInstance().isSkip(message.getGameView(), message.getMessage(), playerId)) { + log.info(getLogStartInfo() + "Skipped: " + message.getMessage()); + session.sendPlayerBoolean(gameId, false); + } else { + log.error(getLogStartInfo() + "unknown GAME_SELECT or skips message: " + message.toString()); + } + */ + //break; } + case GAME_OVER: - log.info("Game over"); + log.info(getLogStartInfo() + "Game over"); gameOver = true; break; + + case END_GAME_INFO: + GameEndView message = (GameEndView) callback.getData(); + this.gameResult = message.hasWon() ? "win" : "lose"; + log.info(getLogStartInfo() + "Game end info, " + this.gameResult); + break; + + // skip callbacks (no need to react) + case GAME_INIT: + case GAME_UPDATE: + case CHATMESSAGE: + case JOINED_TABLE: + break; + + default: + log.error(getLogStartInfo() + "Unknown callback: " + callback.getMethod() + ", " + callback.getData().toString()); + session.sendPlayerBoolean(gameId, false); + return; + //break; + } + } + + private PlayerView getPlayer() { + if ((this.gameView != null) && (this.playerId != null)) { + for (PlayerView p: this.gameView.getPlayers()) { + if (p.getPlayerId().equals(this.playerId)) { + return p; + } + } + } + return null; + } + + private String getLogStartInfo() { + String mes = ""; + + //throw new IllegalArgumentException("test exception"); + + if (this.session != null) { + mes += session.getUserName() + ": "; } + PlayerView p = getPlayer(); + if (this.gameView != null && p != null && this.gameView.getStep() != null) { + mes += "T" + this.gameView.getTurn() + "-" + this.gameView.getStep().getIndex() + ", L:" + p.getLibraryCount() + ", H:" + getPlayer().getHandCount() + ": "; + } + + return mes; } public void setSession(Session session) { @@ -108,8 +201,13 @@ public class LoadCallbackClient implements CallbackClient { } catch (InterruptedException e) { e.printStackTrace(); } + + if(isGameOver()) { + return; + } + if (controlCount > 5) { - log.warn("Game seems freezed. Sending boolean message to server."); + log.warn(getLogStartInfo() + "Game seems freezed. Sending boolean message to server."); session.sendPlayerBoolean(gameId, false); controlCount = 0; } @@ -117,4 +215,12 @@ public class LoadCallbackClient implements CallbackClient { }).start(); } + + public void setConcede(boolean needToConcede) { + this.needToConcede = needToConcede; + } + + public String getLastGameResult() { + return this.gameResult; + } } diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java index 3d651c3144..7a95f25113 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadPhaseManager.java @@ -1,13 +1,16 @@ package org.mage.test.load; +import mage.constants.PhaseStep; import mage.view.GameView; import mage.view.PlayerView; +import org.apache.log4j.Logger; import java.util.HashMap; import java.util.Map; import java.util.UUID; public class LoadPhaseManager { + private static final Logger log = Logger.getLogger("Load phase"); private static final LoadPhaseManager instance = new LoadPhaseManager(); @@ -31,52 +34,63 @@ public class LoadPhaseManager { public static String MAIN_2_OTHERS = "main2Others"; public static String END_OF_TURN_OTHERS = "endOfTurnOthers"; - private static Map mapYou = new HashMap() {{ - put("Upkeep - play instants and activated abilities.", UPKEEP_YOU); - put("Draw - play instants and activated abilities.", DRAW_YOU); - put("Precombat Main - play spells and abilities.", MAIN_YOU); - put("Begin Combat - play instants and activated abilities.", BEFORE_COMBAT_YOU); - put("End Combat - play instants and activated abilities.", END_OF_COMBAT_YOU); - put("Postcombat Main - play spells and abilities.", MAIN_2_YOU); - put("End Turn - play instants and activated abilities.", END_OF_TURN_YOU); - }}; + private static Map skipYou; + static { + skipYou = new HashMap() {{ + put(PhaseStep.UPKEEP, true); + put(PhaseStep.PRECOMBAT_MAIN, true); + put(PhaseStep.BEGIN_COMBAT, true); + put(PhaseStep.DECLARE_ATTACKERS, true); + put(PhaseStep.DECLARE_BLOCKERS, true); + put(PhaseStep.END_COMBAT, true); + put(PhaseStep.POSTCOMBAT_MAIN, true); + put(PhaseStep.END_TURN, true); + }}; + } - private static Map mapOthers = new HashMap() {{ - put("Upkeep - play instants and activated abilities.", UPKEEP_OTHERS); - put("Draw - play instants and activated abilities.", DRAW_OTHERS); - put("Precombat Main - play instants and activated abilities.", MAIN_OTHERS); - put("Begin Combat - play instants and activated abilities.", BEFORE_COMBAT_OTHERS); - put("End Combat - play instants and activated abilities.", END_OF_COMBAT_OTHERS); - put("Postcombat Main - play instants and activated abilities.", MAIN_2_OTHERS); - put("End Turn - play instants and activated abilities.", END_OF_TURN_OTHERS); - }}; + private static Map skipOthers; + static { + skipYou = new HashMap() {{ + put(PhaseStep.UPKEEP, true); + put(PhaseStep.PRECOMBAT_MAIN, true); + put(PhaseStep.BEGIN_COMBAT, true); + put(PhaseStep.DECLARE_ATTACKERS, true); + put(PhaseStep.DECLARE_BLOCKERS, true); + put(PhaseStep.END_COMBAT, true); + put(PhaseStep.POSTCOMBAT_MAIN, true); + put(PhaseStep.END_TURN, true); + }}; + } public static LoadPhaseManager getInstance() { return instance; } public boolean isSkip(GameView gameView, String message, UUID playerId) { + // skip callbacks + UUID activePlayer = null; - Map map = mapOthers; + Map map = skipOthers; for (PlayerView playerView : gameView.getPlayers()) { if (playerView.isActive()) { activePlayer = playerView.getPlayerId(); if (activePlayer.equals(playerId)) { - map = mapYou; + map = skipYou; } } } + if (activePlayer == null) { throw new IllegalStateException("No active player found."); } - for (Map.Entry entry : map.entrySet()) { - if (message.equals(entry.getKey())) { - /*if (message.equals("Precombat Main - play spells and abilities.")) { - return false; - }*/ - return true; - } + + // PROCCESS + + if(map.containsKey(gameView.getStep())){ + return map.get(gameView.getStep()); + } else { + log.error("Unknown phase manager step: " + gameView.getStep().toString()); + return false; } - return false; } } diff --git a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java index 240064072b..68e8cf9750 100644 --- a/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java +++ b/Mage.Tests/src/test/java/org/mage/test/load/LoadTest.java @@ -7,26 +7,24 @@ import mage.cards.decks.DeckCardInfo; import mage.cards.decks.DeckCardLists; import mage.cards.repository.CardInfo; import mage.cards.repository.CardRepository; -import mage.constants.ColoredManaSymbol; -import mage.constants.MatchTimeLimit; -import mage.constants.MultiplayerAttackOption; -import mage.constants.RangeOfInfluence; +import mage.constants.*; import mage.game.match.MatchOptions; import mage.player.ai.ComputerPlayer; import mage.players.PlayerType; import mage.remote.Connection; +import mage.remote.MageRemoteException; import mage.remote.Session; import mage.remote.SessionImpl; -import mage.view.GameTypeView; -import mage.view.TableView; +import mage.util.RandomUtil; +import mage.view.*; import org.apache.log4j.Logger; import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.*; /** * Intended to test Mage server under different load patterns. @@ -36,236 +34,296 @@ import java.util.UUID; * * Then it's also better to use -Xms256M -Xmx512M JVM options for these stests. * - * @author noxx + * @author JayDi85 */ + public class LoadTest { - /** - * Logger for tests - */ - private static final Logger log = Logger.getLogger(LoadTest.class); + private static final Logger logger = Logger.getLogger(LoadTest.class); - /** - * First player's username - */ - private static final String TEST_USER_NAME = "player"; - - /** - * Second player's username - */ - private static final String TEST_USER_NAME_2 = "opponent"; - - /** - * Server connection setting. - */ private static final String TEST_SERVER = "localhost"; - - /** - * Server connection setting. - */ private static final int TEST_PORT = 17171; - - /** - * Server connection setting. - */ private static final String TEST_PROXY_TYPE = "None"; + private static final String TEST_USER_NAME = "user"; - /** - * Determines how many times test will be executed in a row. - */ - private static final int EXECUTION_COUNT = 100; + @Test + public void test_CreateRandomDeck() { - /** - * Determines how many times test will be executed in a row. - */ - private static final int EXECUTION_COUNT_PLAY_GAME = 100; + Deck deck; + + deck = generateRandomDeck("G", false); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in G", + card.getColorIdentity().isGreen()); + } + + deck = generateRandomDeck("U", false); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in U", + card.getColorIdentity().isBlue()); + } + + deck = generateRandomDeck("BR", false); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in BR", + card.getColorIdentity().isBlack() || card.getColorIdentity().isRed()); + } + + deck = generateRandomDeck("BUG", false); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in BUG", + card.getColorIdentity().isBlack() || card.getColorIdentity().isBlue() || card.getColorIdentity().isGreen()); + } + + // lands + + deck = generateRandomDeck("UR", true); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in UR", + card.getColorIdentity().isBlue() || card.getColorIdentity().isRed()); + Assert.assertEquals("card " + card.getName() + " must be basic land ", Rarity.LAND, card.getRarity()); + } + + deck = generateRandomDeck("B", true); + for(Card card : deck.getCards()) { + Assert.assertTrue("card " + card.getName() + " color " + card.getColorIdentity().toString() + " must be in B", card.getColorIdentity().isBlack()); + Assert.assertEquals("card " + card.getName() + " must be basic land ", Rarity.LAND, card.getRarity()); + } + } - /** - * Tests connecting with two players, creating game and starting it. - * - * Executes the test EXECUTION_COUNT times. - * - * @throws Exception - */ @Test @Ignore - public void testStartGame() throws Exception { - DeckCardLists deckList = createDeck(); + public void test_UsersConnectToServer() throws Exception { - for (int i = 0; i < EXECUTION_COUNT; i++) { - Connection connection = createConnection(TEST_USER_NAME + i); + // simple connection to server - SimpleMageClient mageClient = new SimpleMageClient(); - Session session = new SessionImpl(mageClient); + // monitor other players + LoadPlayer monitor = new LoadPlayer("monitor"); + Assert.assertTrue(monitor.session.isConnected()); + int startUsersCount = monitor.getAllRoomUsers().size(); + int minimumSleepTime = 2000; - session.connect(connection); - UUID roomId = session.getMainRoomId(); + // user 1 + LoadPlayer player1 = new LoadPlayer("1"); + Thread.sleep(minimumSleepTime); + Assert.assertEquals("Can't see users count change 1", startUsersCount + 1, monitor.getAllRoomUsers().size()); + Assert.assertNotNull("Can't find user 1", monitor.findUser(player1.userName)); - GameTypeView gameTypeView = session.getGameTypes().get(0); - log.info("Game type view: " + gameTypeView.getName()); - MatchOptions options = createGameOptions(gameTypeView, session); + // user 2 + LoadPlayer player2 = new LoadPlayer("2"); + Thread.sleep(minimumSleepTime); + Assert.assertEquals("Can't see users count change 2", startUsersCount + 2, monitor.getAllRoomUsers().size()); + Assert.assertNotNull("Can't find user 2", monitor.findUser(player2.userName)); + } - TableView table = session.createTable(roomId, options); + @Test + @Ignore + public void test_TwoUsersPlayGameUntilEnd() { + // simple connection to server test - if (!session.joinTable(roomId, table.getTableId(), TEST_USER_NAME + i, PlayerType.HUMAN, 1, deckList,"")) { - log.error("Error while joining table"); - Assert.fail("Error while joining table"); - return; + // monitor other players + LoadPlayer monitor = new LoadPlayer("monitor"); + + // users + LoadPlayer player1 = new LoadPlayer("1"); + LoadPlayer player2 = new LoadPlayer("2"); + + // game by user 1 + GameTypeView gameType = player1.session.getGameTypes().get(0); + MatchOptions gameOptions = createSimpleGameOptions(gameType, player1.session); + TableView game = player1.session.createTable(player1.roomID, gameOptions); + UUID tableId = game.getTableId(); + Assert.assertEquals(player1.userName, game.getControllerName()); + + DeckCardLists deckList = createSimpleDeck("GR", true); + Optional checkGame; + + /* + for(DeckCardInfo info: deckList.getCards()) { + logger.info(info.getCardName()); + }*/ + + // before connect + checkGame = monitor.getTable(tableId); + Assert.assertTrue(checkGame.isPresent()); + Assert.assertEquals(2, checkGame.get().getSeats().size()); + Assert.assertEquals("", checkGame.get().getSeats().get(0).getPlayerName()); + Assert.assertEquals("", checkGame.get().getSeats().get(1).getPlayerName()); + + // connect user 1 + Assert.assertTrue(player1.session.joinTable(player1.roomID, tableId, player1.userName, PlayerType.HUMAN, 1, deckList, "")); + checkGame = monitor.getTable(tableId); + Assert.assertTrue(checkGame.isPresent()); + Assert.assertEquals(2, checkGame.get().getSeats().size()); + Assert.assertEquals(player1.userName, checkGame.get().getSeats().get(0).getPlayerName()); + Assert.assertEquals("", checkGame.get().getSeats().get(1).getPlayerName()); + + // connect user 2 + Assert.assertTrue(player2.session.joinTable(player2.roomID, tableId, player2.userName, PlayerType.HUMAN, 1, deckList, "")); + checkGame = monitor.getTable(tableId); + Assert.assertTrue(checkGame.isPresent()); + Assert.assertEquals(2, checkGame.get().getSeats().size()); + Assert.assertEquals(player1.userName, checkGame.get().getSeats().get(0).getPlayerName()); + Assert.assertEquals(player2.userName, checkGame.get().getSeats().get(1).getPlayerName()); + + // match start + Assert.assertTrue(player1.session.startMatch(player1.roomID, tableId)); + + // playing until game over + while(!player1.client.isGameOver() && !player2.client.isGameOver()) { + checkGame = monitor.getTable(tableId); + logger.warn(checkGame.get().getTableState()); + try { + Thread.sleep(1000); + } catch (InterruptedException e){ + logger.error(e.getMessage(), e); + } + } + } + + @Test + @Ignore + public void test_GameThread() { + // simple game thread to the end + + LoadGame game = new LoadGame( + "game", + "thread", + createSimpleDeck("GR", true), + createSimpleDeck("GR", true) + ); + game.gameStart(); + + while (game.isPlaying()) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + } + } + + Assert.assertEquals("finished", game.gameResult); + } + + @Test + @Ignore + public void test_GameThreadWithAbort() { + // simple game thread with game abort + + LoadGame game = new LoadGame( + "game", + "thread", + createSimpleDeck("GR", true), + createSimpleDeck("GR", true) + ); + game.gameStart(); + game.gameEnd(true); // abort -- close client thread + Assert.assertEquals("aborted", game.gameResult); + } + + @Test + @Ignore + public void test_GameThreadWithConcede() { + // simple game thread with game abort + + LoadGame game = new LoadGame( + "game", + "thread", + createSimpleDeck("GR", true), + createSimpleDeck("GR", true) + ); + game.gameStart(); + + try { + Thread.sleep(3000); + } catch (Throwable e) { + // + } + game.gameConcede(1); + game.gameWaitToStop(); + Assert.assertEquals("finished", game.gameResult); + Assert.assertEquals("lose", game.player1.lastGameResult); + Assert.assertEquals("win", game.player2.lastGameResult); + } + + @Test + @Ignore + public void test_MultipleGames() { + // multiple games until finish + + Instant startTime = Instant.now(); + + int MAX_GAMES = 30; + + // creating + logger.info("creating games..."); + ArrayList gamesList = new ArrayList<>(); + for(int i = 1; i <= MAX_GAMES; i++) { + gamesList.add(new LoadGame( + "game" + i, + "game" + i, + createSimpleDeck("GR", true), + createSimpleDeck("GR", true) + )); + } + logger.info("created " + gamesList.size() + " games"); + + // running + for(LoadGame game: gamesList) { + game.gameStart(); + } + logger.info("run " + gamesList.size() + " games"); + + // waiting + while (true) { + boolean isComplete = true; + for(LoadGame game: gamesList) { + isComplete = isComplete && !game.isPlaying(); } - /*** Connect with a second player ***/ - Connection connection2 = createConnection(TEST_USER_NAME_2 + i); - SimpleMageClient mageClient2 = new SimpleMageClient(); - Session session2 = new SessionImpl(mageClient2); - session2.connect(connection2); - UUID roomId2 = session2.getMainRoomId(); - - // connect to the table with the same deck - if (!session2.joinTable(roomId2, table.getTableId(), TEST_USER_NAME_2 + i, PlayerType.HUMAN, 1, deckList,"")) { - log.error("Error while joining table"); - Assert.fail("Error while joining table"); - return; + if(isComplete) { + break; } - /*** Start game ***/ - session.startMatch(roomId, table.getTableId()); + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + } + } + logger.info("completed " + gamesList.size() + " games"); - Thread.sleep(100); + Instant endTime = Instant.now(); + logger.info("total time: " + ChronoUnit.SECONDS.between(startTime, endTime) + " secs"); + + // check statuses + ArrayList errors = new ArrayList<>(); + for(LoadGame game: gamesList) { + if (!"finished".equals(game.gameResult)) { + errors.add(game.gameName + ": not finished, got " + game.gameResult); + } + } + + if (errors.size() > 0) { + System.out.println("Not all games finished, founded " + errors.size() + " errors: "); + for (String s: errors) { + System.out.println(s); + } + Assert.fail("Not all games finished"); } } - /** - * Tests 10 simple games played one after another. - */ - @Test - @Ignore - public void testSimpleGame() throws Exception { - final DeckCardLists deckList = createDeck(); - - for (int i = 0; i < EXECUTION_COUNT_PLAY_GAME; i++) { - final int j = i; - Thread t = new Thread(() -> { - try { - testSimpleGame0(deckList, j); - } catch (InterruptedException e) { - e.printStackTrace(); - } - }); - t.start(); - t.join(); - } - } - - /** - * Tests simple game till the end (game over). - * Players do nothing but skip phases and discard cards at the end. - * - * This results in a game that lasts until there is no cards in library. - */ - private boolean testSimpleGame0(DeckCardLists deckList, int i) throws InterruptedException { - Connection connection = createConnection(TEST_USER_NAME + i); - - SimpleMageClient mageClient = new SimpleMageClient(); - Session session = new SessionImpl(mageClient); - - session.connect(connection); - - mageClient.setSession(session); - UUID roomId = session.getMainRoomId(); - - GameTypeView gameTypeView = session.getGameTypes().get(0); - log.info("Game type view: " + gameTypeView.getName()); - MatchOptions options = createGameOptions(gameTypeView, session); - - TableView table = session.createTable(roomId, options); - - if (!session.joinTable(roomId, table.getTableId(), TEST_USER_NAME + i, PlayerType.HUMAN, 1, deckList,"")) { - log.error("Error while joining table"); - Assert.fail("Error while joining table"); - return true; - } - - /*** Connect with a second player ***/ - Connection connection2 = createConnection(TEST_USER_NAME_2 + i); - SimpleMageClient mageClient2 = new SimpleMageClient(); - Session session2 = new SessionImpl(mageClient2); - session2.connect(connection2); - - mageClient2.setSession(session2); - UUID roomId2 = session2.getMainRoomId(); - - // connect to the table with the same deck - if (!session2.joinTable(roomId2, table.getTableId(), TEST_USER_NAME_2 + i, PlayerType.HUMAN, 1, deckList,"")) { - log.error("Error while joining table"); - Assert.fail("Error while joining table"); - return true; - } - - /*** Start game ***/ - session.startMatch(roomId, table.getTableId()); - - while (!mageClient.isGameOver()) { - Thread.sleep(1000); - } - return false; - } - - /** - * Tests playing the whole game. - * Player use cheat to add lands, creatures and other cards. - * Then play only lands, one of them plays 1 damage targeting player. - * - * This results in 40 turns of the game. - */ - @Test - @Ignore - public void testPlayGame() throws Exception { - //TODO: to be implemented - } - - /** - * Creates connection to the server. - * Server should run independently. - * - * @param username - * @return - */ - private Connection createConnection(String username) { - Connection connection = new Connection(); - connection.setUsername(username); - connection.setHost(TEST_SERVER); - connection.setPort(TEST_PORT); + private Connection createSimpleConnection(String username) { + Connection con = new Connection(); + con.setUsername(username); + con.setHost(TEST_SERVER); + con.setPort(TEST_PORT); Connection.ProxyType proxyType = Connection.ProxyType.valueByText(TEST_PROXY_TYPE); - connection.setProxyType(proxyType); - return connection; + con.setProxyType(proxyType); + return con; } - /** - * Returns random deck. - * Converts deck returned by {@link #generateRandomDeck} method to {@link DeckCardLists} format. - * - * @return - */ - private DeckCardLists createDeck() { - DeckCardLists deckList = new DeckCardLists(); - Deck deck = generateRandomDeck(); - for (Card card : deck.getCards()) { - CardInfo cardInfo = CardRepository.instance.findCard(card.getExpansionSetCode(), card.getCardNumber()); - if (cardInfo != null) { - deckList.getCards().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); - } - } - return deckList; - } - - /** - * Creates game options with two human players. - * - * @param gameTypeView - * @param session - * @return - */ - private MatchOptions createGameOptions(GameTypeView gameTypeView, Session session) { + private MatchOptions createSimpleGameOptions(GameTypeView gameTypeView, Session session) { MatchOptions options = new MatchOptions("Test game", gameTypeView.getName(), false, 2); options.getPlayerTypes().add(PlayerType.HUMAN); @@ -280,21 +338,224 @@ public class LoadTest { return options; } - /** - * Generates random deck in {@link Deck} format. - * Uses {B}{R} as deck colors. - * - * @return - */ - private Deck generateRandomDeck() { - String selectedColors = "BR"; + private Deck generateRandomDeck(String colors, boolean onlyBasicLands) { + logger.info("Building " + (onlyBasicLands ? "only lands" : "random") + " deck with colors: " + colors); + List allowedColors = new ArrayList<>(); - log.info("Building deck with colors: " + selectedColors); - for (int i = 0; i < selectedColors.length(); i++) { - char c = selectedColors.charAt(i); + for (int i = 0; i < colors.length(); i++) { + char c = colors.charAt(i); allowedColors.add(ColoredManaSymbol.lookup(c)); } - List cardPool = Sets.generateRandomCardPool(45, allowedColors); - return ComputerPlayer.buildDeck(cardPool, allowedColors); + List cardPool = Sets.generateRandomCardPool(45, allowedColors, onlyBasicLands); + return ComputerPlayer.buildDeck(cardPool, allowedColors, onlyBasicLands); + } + + private DeckCardLists createSimpleDeck(String colors, boolean onlyBasicLands) { + Deck deck = generateRandomDeck(colors, onlyBasicLands); + + DeckCardLists deckList = new DeckCardLists(); + for (Card card : deck.getCards()) { + CardInfo cardInfo = CardRepository.instance.findCard(card.getExpansionSetCode(), card.getCardNumber()); + if (cardInfo != null) { + deckList.getCards().add(new DeckCardInfo(cardInfo.getName(), cardInfo.getCardNumber(), cardInfo.getSetCode())); + } + } + return deckList; + } + + private class LoadPlayer { + String userName; + Connection connection; + SimpleMageClient client; + Session session; + UUID roomID; + UUID createdTableID; + UUID connectedTableID; + DeckCardLists deckList; + String lastGameResult = ""; + + public LoadPlayer(String userPrefix) { + this.userName = TEST_USER_NAME + "_" + userPrefix + "_" + RandomUtil.nextInt(10000); + this.connection = createSimpleConnection(this.userName); + this.client = new SimpleMageClient(); + this.session = new SessionImpl(this.client); + + this.session.connect(this.connection); + this.client.setSession(this.session); + this.roomID = this.session.getMainRoomId(); + } + + public ArrayList getAllRoomUsers() { + ArrayList res = new ArrayList<>(); + try { + for (RoomUsersView roomUsers : this.session.getRoomUsers(this.roomID)) { + res.addAll(roomUsers.getUsersView()); + } + } catch (MageRemoteException e) + { + logger.error(e); + } + return res; + } + + public UsersView findUser(String userName) { + for (UsersView user: this.getAllRoomUsers()) { + if (user.getUserName().equals(userName)) { + return user; + } + } + return null; + } + + public Optional getTable(UUID tableID) { + return this.session.getTable(this.roomID, tableID); + } + + public UUID createNewTable() { + GameTypeView gameType = this.session.getGameTypes().get(0); + MatchOptions gameOptions = createSimpleGameOptions(gameType, this.session); + TableView game = this.session.createTable(this.roomID, gameOptions); + this.createdTableID = game.getTableId(); + Assert.assertEquals(this.userName, game.getControllerName()); + + connectToTable(this.createdTableID); + + return this.createdTableID; + } + + public void connectToTable(UUID tableID) { + Assert.assertTrue(this.session.joinTable(this.roomID, tableID, this.userName, PlayerType.HUMAN, 1, this.deckList, "")); + this.connectedTableID = tableID; + } + + public void startMatch() { + Assert.assertNotNull(this.createdTableID); + Assert.assertTrue(this.session.startMatch(this.roomID, this.createdTableID)); + } + + public void setDeckList(DeckCardLists deckList) { + this.deckList = deckList; + } + + public void disconnect() { + this.session.disconnect(false); + } + + public void concede() { + this.client.setConcede(true); + } + } + + private class LoadGame { + String gameName = null; + Thread runningThread = null; + LoadPlayer player1 = null; + LoadPlayer player2 = null; + Boolean abort = false; + UUID tableID = null; + String gameResult = null; + + public LoadGame(String gameName, String playerPrefix) { + this(gameName, playerPrefix, + createSimpleDeck("GR", true), + createSimpleDeck("GR", true) + ); + } + + public LoadGame(String gameName, String playerPrefix, DeckCardLists deck1, DeckCardLists deck2) { + this.gameName = gameName; + + player1 = new LoadPlayer(playerPrefix + "_" + 1); + player1.setDeckList(deck1); + + player2 = new LoadPlayer(playerPrefix + "_" + 2); + player2.setDeckList(deck2); + } + + public void gameStart() { + + this.abort = false; + + runningThread = new Thread(() -> { + try { + this.tableID = this.player1.createNewTable(); + Assert.assertNotNull(this.tableID); + this.player2.connectToTable(this.tableID); + this.gameResult = "prepared"; + + this.player1.startMatch(); + this.gameResult = "started"; + + // playing until game over or abort + while(!abort && (!player1.client.isGameOver() || !player2.client.isGameOver())) { + try { + Thread.sleep(1000); + } catch (InterruptedException e){ + logger.error(e.getMessage(), e); + } + } + + // game results + if (abort) { + this.gameResult = "aborted"; + } else { + this.gameResult = "finished"; + } + player1.lastGameResult = player1.client.getLastGameResult(); + player2.lastGameResult = player2.client.getLastGameResult(); + } catch (Throwable e) { + this.gameResult = "error"; + logger.fatal("Game thread " + this.gameName + " was stopped by error"); + e.printStackTrace(); + } + + // disconnect on end + this.player1.disconnect(); + this.player2.disconnect(); + + // clean up after game + this.runningThread = null; + this.tableID = null; + }); + + runningThread.start(); + } + + public void gameEnd() { + gameEnd(false); + } + + public void gameEnd(Boolean waitToStop) { + this.abort = true; + + if (waitToStop) { + gameWaitToStop(); + } + } + + public void gameConcede(int playerNumber) { + switch (playerNumber) { + case 1: + this.player1.concede(); + break; + case 2: + this.player2.concede(); + break; + } + } + + public void gameWaitToStop() { + while (this.runningThread != null) { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + logger.error(e.getMessage(), e); + } + } + } + + public Boolean isPlaying() { + return (this.runningThread != null); + } } }