/*******************************************************************************
* Copyright (c) 2015
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*******************************************************************************/
package jsettlers.network.client;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.util.List;
import jsettlers.network.NetworkConstants;
import jsettlers.network.TestUtils;
import jsettlers.network.NetworkConstants.ENetworkKey;
import jsettlers.network.NetworkConstants.ENetworkMessage;
import jsettlers.network.client.NetworkClient;
import jsettlers.network.client.receiver.BufferingPacketReceiver;
import jsettlers.network.client.task.TestTaskPacket;
import jsettlers.network.client.task.packets.TaskPacket;
import jsettlers.network.common.packets.ArrayOfMatchInfosPacket;
import jsettlers.network.common.packets.ChatMessagePacket;
import jsettlers.network.common.packets.MapInfoPacket;
import jsettlers.network.common.packets.MatchInfoPacket;
import jsettlers.network.common.packets.MatchInfoUpdatePacket;
import jsettlers.network.infrastructure.channel.AsyncChannel;
import jsettlers.network.infrastructure.channel.Channel;
import jsettlers.network.infrastructure.channel.TestPacket;
import jsettlers.network.infrastructure.channel.TestPacketListener;
import jsettlers.network.infrastructure.channel.reject.RejectPacket;
import jsettlers.network.server.ServerManager;
import jsettlers.network.server.db.inMemory.InMemoryDB;
import jsettlers.network.server.match.EPlayerState;
import jsettlers.network.server.match.Player;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
/**
* Tests the {@link NetworkClient}s interaction with the server.
*
* @author Andreas Eberle
*
*/
@Ignore
public class NetworkClientIT {
private static final String TEST_PLAYER_ID = "id-testPlayer";
private InMemoryDB db = new InMemoryDB();
private ServerManager manager = new ServerManager(db);
private Channel server1Channel;
private AsyncChannel client1Channel;
private NetworkClient client1;
private NetworkClientClockMock clock1;
private Channel server2Channel;
private AsyncChannel client2Channel;
private NetworkClient client2;
private NetworkClientClockMock clock2;
@Before
public void setUp() throws IOException {
manager.start();
// set up first client
AsyncChannel[] channels = TestUtils.setUpAsyncLoopbackChannels();
client1Channel = channels[0];
server1Channel = channels[1];
clock1 = new NetworkClientClockMock();
clock2 = new NetworkClientClockMock();
client1 = new NetworkClient(client1Channel, null, clock1);
manager.identifyNewChannel(server1Channel);
// set up second client
channels = TestUtils.setUpAsyncLoopbackChannels();
client2Channel = channels[0];
server2Channel = channels[1];
client2 = new NetworkClient(client2Channel, null, clock2);
manager.identifyNewChannel(server2Channel);
}
@After
public void tearDown() {
client1.close();
server1Channel.close();
client2.close();
server2Channel.close();
}
@Test
public void testConnection() throws InterruptedException {
TestPacketListener listener = new TestPacketListener(NetworkConstants.ENetworkKey.TEST_PACKET);
client1Channel.registerListener(listener);
TestPacket testPacket = new TestPacket("sdlfjsh", 2324);
server1Channel.sendPacket(NetworkConstants.ENetworkKey.TEST_PACKET, testPacket);
Thread.sleep(10);
assertEquals(1, listener.packets.size());
assertEquals(testPacket, listener.packets.get(0));
}
@Test
public void testLogIn() throws IllegalStateException, InterruptedException {
final String playerName = "Name)2020j3j";
logIn(client1, TEST_PLAYER_ID, playerName);
}
private void logIn(NetworkClient client, String playerId, String playerName) throws IllegalStateException, InterruptedException {
int currentNumberOfPlayers = db.getNumberOfPlayers();
client.logIn(playerId, playerName, null);
Thread.sleep(40);
assertEquals(EPlayerState.LOGGED_IN, client.getState());
assertEquals(currentNumberOfPlayers + 1, db.getNumberOfPlayers()); // ensure we have now one player more than before
Player p = db.getPlayer(playerId);
assertEquals(playerId, p.getId());
assertEquals(playerName, p.getPlayerInfo().getName());
}
@Test(expected = IllegalStateException.class)
public void testRequestOpenNewMatchInStateUnconnected() throws IllegalStateException {
client1.openNewMatch(null, (byte) 0, null, 4711L, null, null, null);
}
@Test(expected = IllegalStateException.class)
public void testRequestOpenNewMatchInStateInMatch() throws IllegalStateException, InterruptedException {
testOpenMatchWithLogin();
client1.openNewMatch(null, (byte) 0, null, 4711L, null, null, null);
}
@Test(expected = IllegalStateException.class)
public void testRequestOpenNewMatchInStateInRunningMatch() throws IllegalStateException, InterruptedException {
testOpenAndStartNewMatch();
client1.openNewMatch(null, (byte) 0, null, 4711L, null, null, null);
}
@Test(expected = IllegalStateException.class)
public void testRequestStartMatchInStateUnconnected() throws IllegalStateException {
client1.startMatch();
}
@Test(expected = IllegalStateException.class)
public void testRequestStartMatchInStateLoggedIn() throws IllegalStateException, InterruptedException {
testLogIn();
client1.startMatch();
}
@Test(expected = IllegalStateException.class)
public void testRequestStartMatchInStateInRunningMatch() throws IllegalStateException, InterruptedException {
testOpenAndStartNewMatch();
client1.startMatch();
}
@Test
public void testOpenAndStartNewMatch() throws IllegalStateException, InterruptedException {
openMatch("id1", "player1", client1);
client1.startMatch();
Thread.sleep(50);
assertEquals(EPlayerState.IN_RUNNING_MATCH, client1.getState());
}
@Test
public void testOpenStartAndJoinNewMatch() throws IllegalStateException, InterruptedException {
logIn(client2, "id2", "player2");
openMatch("id1", "player1", client1);
MatchInfoPacket dbMatchInfo = new MatchInfoPacket(db.getJoinableMatches().get(0));
assertEquals(dbMatchInfo, client1.getMatchInfo());
assertEquals(1, client1.getMatchInfo().getPlayers().length);
client2.joinMatch(dbMatchInfo.getId(), null, null, null);
Thread.sleep(50);
assertEquals(EPlayerState.IN_MATCH, client2.getState());
dbMatchInfo = new MatchInfoPacket(db.getJoinableMatches().get(0));
assertEquals(dbMatchInfo, client1.getMatchInfo());
assertEquals(2, client1.getMatchInfo().getPlayers().length);
client2.setReadyState(true);
Thread.sleep(50);
assertEquals(EPlayerState.IN_MATCH, client2.getState());
dbMatchInfo = new MatchInfoPacket(db.getJoinableMatches().get(0));
assertEquals(dbMatchInfo, client1.getMatchInfo());
assertEquals(2, client1.getMatchInfo().getPlayers().length);
assertTrue(client1.getMatchInfo().getPlayers()[1].isReady());
client1.startMatch();
Thread.sleep(50);
assertEquals(EPlayerState.IN_RUNNING_MATCH, client1.getState());
assertEquals(EPlayerState.IN_RUNNING_MATCH, client2.getState());
}
@Test
public void testLogInAndClose() throws IllegalStateException, InterruptedException {
testLogIn();
client1.close();
Thread.sleep(10);
assertEquals(0, db.getNumberOfPlayers());
assertEquals(EPlayerState.DISCONNECTED, client1.getState());
}
@Test
public void testCloseFromServerSide() throws IllegalStateException, InterruptedException {
logIn(client1, "id1", "player1");
logIn(client2, "id2", "player2");
assertEquals(2, db.getNumberOfPlayers());
server1Channel.close();
Thread.sleep(10);
assertEquals(1, db.getNumberOfPlayers());
assertEquals(EPlayerState.DISCONNECTED, client1.getState());
}
@Test
public void testOpenMatchWithLogin() throws IllegalStateException, InterruptedException {
openMatch("player1", "player1", client1);
}
private void openMatch(String id, String name, NetworkClient client) throws IllegalStateException, InterruptedException {
BufferingPacketReceiver<ArrayOfMatchInfosPacket> matchesReceiver = new BufferingPacketReceiver<ArrayOfMatchInfosPacket>();
assertEquals(0, matchesReceiver.popBufferedPackets().size());
client.logIn(id, name, matchesReceiver);
Thread.sleep(50);
List<ArrayOfMatchInfosPacket> arrayOfMatches = matchesReceiver.popBufferedPackets();
assertEquals(1, arrayOfMatches.size()); // check that we got one result for the request
assertEquals(0, arrayOfMatches.get(0).getMatches().length); // currently no matches should be in the result, because non should be open
BufferingPacketReceiver<MatchInfoUpdatePacket> matchUpdateListener = new BufferingPacketReceiver<MatchInfoUpdatePacket>();
final String matchName = "TestMatch";
final byte maxPlayers = (byte) 5;
final MapInfoPacket mapInfo = new MapInfoPacket("mapid92329", "mapName", "authorId", "authorName", 5);
client.openNewMatch(matchName, maxPlayers, mapInfo, -4712L, null, matchUpdateListener, null);
Thread.sleep(100);
List<MatchInfoUpdatePacket> matches = matchUpdateListener.popBufferedPackets();
assertEquals(1, matches.size());
MatchInfoPacket match = matches.get(0).getMatchInfo();
assertEquals(matchName, match.getMatchName());
assertEquals(maxPlayers, match.getMaxPlayers());
assertEquals(mapInfo, match.getMapInfo());
assertFalse(match.getPlayers()[0].isReady());
client.setReadyState(true);
Thread.sleep(150);
matches = matchUpdateListener.popBufferedPackets();
assertEquals(1, matches.size());
match = matches.get(0).getMatchInfo();
assertEquals(matchName, match.getMatchName());
assertEquals(maxPlayers, match.getMaxPlayers());
assertEquals(mapInfo, match.getMapInfo());
assertTrue(match.getPlayers()[0].isReady());
}
@Test
public void testChatMessaging() throws IllegalStateException, InterruptedException {
testLogIn();
BufferingPacketReceiver<ChatMessagePacket> chatReceiver = new BufferingPacketReceiver<ChatMessagePacket>();
client1.openNewMatch("TestMatch", 4, new MapInfoPacket("", "", "", "", 9), 923409340394293842L, null, null, chatReceiver);
Thread.sleep(80);
assertEquals(EPlayerState.IN_MATCH, client1.getState());
testSendAndReceiveChatMessage(chatReceiver);
client1.setReadyState(true);
Thread.sleep(50);
testSendAndReceiveChatMessage(chatReceiver);
client1.startMatch();
Thread.sleep(50);
assertEquals(EPlayerState.IN_RUNNING_MATCH, client1.getState());
testSendAndReceiveChatMessage(chatReceiver);
}
private void testSendAndReceiveChatMessage(BufferingPacketReceiver<ChatMessagePacket> chatReceiver) throws IllegalStateException,
InterruptedException {
final String testMessage = "TestChatMessage���lL���LP?=))(=)(�\"\\`!)�$";
client1.sendChatMessage(testMessage);
assertEquals(0, chatReceiver.popBufferedPackets().size());
Thread.sleep(50);
List<ChatMessagePacket> chatMessages = chatReceiver.popBufferedPackets();
assertEquals(1, chatMessages.size());
assertEquals(TEST_PLAYER_ID, chatMessages.get(0).getAuthorId());
assertEquals(testMessage, chatMessages.get(0).getMessage());
}
@Test
public void testTimeSynchronization() throws IllegalStateException, InterruptedException {
testOpenStartAndJoinNewMatch();
clock1.setTime(200);
clock2.setTime(210);
Thread.sleep(NetworkConstants.Client.TIME_SYNC_SEND_INTERVALL + 20); // wait for 1 synchronizations
assertEquals(0, clock1.popAdjustmentEvents().size()); // no adjustments should have happened, because the clocks are almost sync
assertEquals(0, clock2.popAdjustmentEvents().size());
clock1.setTime(2056); // put clock1 forward
Thread.sleep(3 * NetworkConstants.Client.TIME_SYNC_SEND_INTERVALL + 20); // wait for 3 synchronizations
int diff = Math.abs(clock1.getTime() - clock2.getTime());
assertTrue("diff is to high: " + diff, diff < NetworkConstants.Client.TIME_SYNC_TOLERATED_DIFFERENCE);
assertTrue(clock1.popAdjustmentEvents().size() > 0);
assertEquals(0, clock2.popAdjustmentEvents().size());
clock2.setTime(423423); // put clock2 forward
Thread.sleep(6 * NetworkConstants.Client.TIME_SYNC_SEND_INTERVALL + 20); // wait for 6 synchronizations
diff = Math.abs(clock2.getTime() - clock1.getTime());
assertTrue("diff is to high: " + diff, diff < NetworkConstants.Client.TIME_SYNC_TOLERATED_DIFFERENCE);
assertTrue(clock2.popAdjustmentEvents().size() > 0);
assertEquals(0, clock1.popAdjustmentEvents().size());
}
@Test
public void testSyncTasksDistribution() throws IllegalStateException, InterruptedException {
logIn(client1, "player1", "player1");
logIn(client2, "player2", "player2");
client1.openNewMatch("TestMatch", 4, new MapInfoPacket("", "", "", "", 4), 34L, null, null, null);
Thread.sleep(150);
assertEquals(EPlayerState.IN_MATCH, client1.getState());
MatchInfoPacket matchInfo = client1.getMatchInfo();
client2.joinMatch(matchInfo.getId(), null, null, null);
Thread.sleep(50);
assertEquals(EPlayerState.IN_MATCH, client2.getState());
client1.setReadyState(true);
client2.setReadyState(true);
Thread.sleep(30);
client2.startMatch();
Thread.sleep(30 + NetworkConstants.Client.LOCKSTEP_PERIOD); // Ensure that both clients are in a running match.
assertEquals(EPlayerState.IN_RUNNING_MATCH, client1.getState());
assertEquals(EPlayerState.IN_RUNNING_MATCH, client2.getState());
Thread.sleep(2 * NetworkConstants.Client.LOCKSTEP_PERIOD); // After two lockstep periods, there must be two locksteps.
assertEquals(NetworkConstants.Client.LOCKSTEP_DEFAULT_LEAD_STEPS, clock1.getAllowedLockstep());
assertEquals(NetworkConstants.Client.LOCKSTEP_DEFAULT_LEAD_STEPS, clock2.getAllowedLockstep());
// After more than LOCKSTEP_DEFAULT_LEAD_STEPS periods, the lockstep counter must wait, to prevent it from running away.
Thread.sleep((2 + NetworkConstants.Client.LOCKSTEP_DEFAULT_LEAD_STEPS) * NetworkConstants.Client.LOCKSTEP_PERIOD);
assertEquals(NetworkConstants.Client.LOCKSTEP_DEFAULT_LEAD_STEPS, clock1.getAllowedLockstep());
assertEquals(NetworkConstants.Client.LOCKSTEP_DEFAULT_LEAD_STEPS, clock2.getAllowedLockstep());
// Submit a task
TestTaskPacket testTask = new TestTaskPacket("dsfsdf", 2342, (byte) -23);
client2.scheduleTask(testTask);
Thread.sleep(2 * NetworkConstants.Client.TIME_SYNC_SEND_INTERVALL);
// The task must not have been submitted to the clients yet, because the lockstep is blocked.
assertEquals(0, clock1.popBufferedTasks().size());
assertEquals(0, clock2.popBufferedTasks().size());
// Now let one clock continue one lockstep period.
clock1.setTime(NetworkConstants.Client.LOCKSTEP_PERIOD + NetworkConstants.Client.TIME_SYNC_TOLERATED_DIFFERENCE + 10);
Thread.sleep(NetworkConstants.Client.TIME_SYNC_SEND_INTERVALL + 40);
List<TaskPacket> packets1 = clock1.popBufferedTasks();
assertEquals(1, packets1.size());
assertEquals(testTask, packets1.get(0));
List<TaskPacket> packets2 = clock2.popBufferedTasks();
assertEquals(1, packets2.size());
assertEquals(testTask, packets2.get(0));
Thread.sleep(2 * NetworkConstants.Client.LOCKSTEP_PERIOD); // Wait two more lockstep periods and check the run away protection again
assertEquals(NetworkConstants.Client.LOCKSTEP_DEFAULT_LEAD_STEPS + 1, clock1.getAllowedLockstep());
assertEquals(NetworkConstants.Client.LOCKSTEP_DEFAULT_LEAD_STEPS + 1, clock2.getAllowedLockstep());
}
@Test
public void testStartMatchWithUnreadyPlayers() throws IllegalStateException, InterruptedException {
logIn(client2, "id2", "player2");
openMatch("id1", "player1", client1); // open match and join client2
client2.joinMatch(client1.getMatchInfo().getId(), null, null, null);
BufferingPacketReceiver<RejectPacket> rejectReceiver2 = new BufferingPacketReceiver<RejectPacket>();
client2.registerRejectReceiver(rejectReceiver2);
Thread.sleep(50);
assertEquals(0, rejectReceiver2.popBufferedPackets().size());
client2.startMatch(); // try to start match with unready player2 => match must not start
Thread.sleep(50);
assertSingleRejectPacket(rejectReceiver2, ENetworkKey.REQUEST_START_MATCH, ENetworkMessage.NOT_ALL_PLAYERS_READY);
client2.setReadyState(true); // set player2 ready and player1 unready => match must not start
Thread.sleep(20);
client1.setReadyState(false);
Thread.sleep(20);
client2.startMatch();
Thread.sleep(50);
assertSingleRejectPacket(rejectReceiver2, ENetworkKey.REQUEST_START_MATCH, ENetworkMessage.NOT_ALL_PLAYERS_READY);
client1.setReadyState(true); // set player1 ready => must must start
Thread.sleep(20);
client2.startMatch();
Thread.sleep(50);
assertEquals(EPlayerState.IN_RUNNING_MATCH, client1.getState());
assertEquals(EPlayerState.IN_RUNNING_MATCH, client2.getState());
}
private void assertSingleRejectPacket(BufferingPacketReceiver<RejectPacket> rejectReceiver, ENetworkKey expectedRejectedKey,
ENetworkMessage expectedMessage) {
List<RejectPacket> rejectPackets = rejectReceiver.popBufferedPackets();
assertEquals(1, rejectPackets.size());
assertEquals(expectedRejectedKey, rejectPackets.get(0).getRejectedKey());
assertEquals(expectedMessage, rejectPackets.get(0).getErrorMessageId());
}
}