#1125 Create infrastructure for Limbo persistence + restore 5.2 JSON storage
- Introduce configurable storage mechanism - LimboPersistence wraps a LimboPersistenceHandler, of which there are multiple implementations - Outside of the limbo.persistence package, classes only talk to LimboPersistence - Restore the way of persisting to JSON from 5.2 (SeparateFilePersistenceHandler) - Add handling for stored limbo players - Merge any existing LimboPlayers together with the goal of only keeping one version of a LimboPlayer: there is no way for a player to be online without triggering the creation of a LimboPlayer first, so we can guarantee that the in-memory LimboPlayer is the most up-to-date, i.e. when restoring limbo data we don't have to check against the disk. - Create and delete LimboPlayers at the same time when LimboPlayers are added or removed from the in-memory map - Catch all exceptions in LimboPersistence so a handler throwing an unexpected exception does not stop the limbo process (#1070) - Extend debug command /authme debug limbo to show LimboPlayer information on disk, too
This commit is contained in:
@@ -48,14 +48,13 @@ public class LimboServiceHelperTest {
|
||||
// given
|
||||
Location newLocation = mock(Location.class);
|
||||
LimboPlayer newLimbo = new LimboPlayer(newLocation, false, "grp-new", true, 0.3f, 0.0f);
|
||||
Location oldLocation = mock(Location.class);
|
||||
LimboPlayer oldLimbo = new LimboPlayer(oldLocation, false, "", false, 0.1f, 0.1f);
|
||||
LimboPlayer oldLimbo = new LimboPlayer(null, false, "", false, 0.1f, 0.1f);
|
||||
|
||||
// when
|
||||
LimboPlayer result = limboServiceHelper.merge(newLimbo, oldLimbo);
|
||||
|
||||
// then
|
||||
assertThat(result.getLocation(), equalTo(oldLocation));
|
||||
assertThat(result.getLocation(), equalTo(newLocation));
|
||||
assertThat(result.isOperator(), equalTo(false));
|
||||
assertThat(result.getGroup(), equalTo("grp-new"));
|
||||
assertThat(result.isCanFly(), equalTo(true));
|
||||
|
||||
@@ -4,6 +4,7 @@ import ch.jalu.injector.testing.DelayedInjectionRunner;
|
||||
import ch.jalu.injector.testing.InjectDelayed;
|
||||
import fr.xephi.authme.ReflectionTestUtils;
|
||||
import fr.xephi.authme.TestHelper;
|
||||
import fr.xephi.authme.data.limbo.persistence.LimboPersistence;
|
||||
import fr.xephi.authme.permission.PermissionsManager;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.SpawnLoader;
|
||||
@@ -57,6 +58,9 @@ public class LimboServiceTest {
|
||||
@Mock
|
||||
private LimboPlayerTaskManager taskManager;
|
||||
|
||||
@Mock
|
||||
private LimboPersistence limboPersistence;
|
||||
|
||||
@BeforeClass
|
||||
public static void initLogger() {
|
||||
TestHelper.setupLogger();
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import ch.jalu.injector.testing.BeforeInjecting;
|
||||
import ch.jalu.injector.testing.DelayedInjectionRunner;
|
||||
import ch.jalu.injector.testing.InjectDelayed;
|
||||
import fr.xephi.authme.ReflectionTestUtils;
|
||||
import fr.xephi.authme.TestHelper;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
import fr.xephi.authme.initialization.factory.Factory;
|
||||
import fr.xephi.authme.settings.Settings;
|
||||
import fr.xephi.authme.settings.properties.LimboSettings;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.hamcrest.Matcher;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static org.hamcrest.Matchers.both;
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.instanceOf;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
import static org.mockito.hamcrest.MockitoHamcrest.argThat;
|
||||
|
||||
/**
|
||||
* Test for {@link LimboPersistence}.
|
||||
*/
|
||||
@RunWith(DelayedInjectionRunner.class)
|
||||
public class LimboPersistenceTest {
|
||||
|
||||
@InjectDelayed
|
||||
private LimboPersistence limboPersistence;
|
||||
|
||||
@Mock
|
||||
private Factory<LimboPersistenceHandler> handlerFactory;
|
||||
|
||||
@Mock
|
||||
private Settings settings;
|
||||
|
||||
@BeforeClass
|
||||
public static void setUpLogger() {
|
||||
TestHelper.setupLogger();
|
||||
}
|
||||
|
||||
@BeforeInjecting
|
||||
@SuppressWarnings("unchecked")
|
||||
public void setUpMocks() {
|
||||
given(settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE)).willReturn(LimboPersistenceType.DISABLED);
|
||||
given(handlerFactory.newInstance(any(Class.class)))
|
||||
.willAnswer(invocation -> mock(invocation.getArgument(0)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldInitializeProperly() {
|
||||
// given / when / then
|
||||
assertThat(getHandler(), instanceOf(NoOpPersistenceHandler.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDelegateToHandler() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
LimboPersistenceHandler handler = getHandler();
|
||||
LimboPlayer limbo = mock(LimboPlayer.class);
|
||||
given(handler.getLimboPlayer(player)).willReturn(limbo);
|
||||
|
||||
// when
|
||||
LimboPlayer result = limboPersistence.getLimboPlayer(player);
|
||||
limboPersistence.saveLimboPlayer(player, mock(LimboPlayer.class));
|
||||
limboPersistence.removeLimboPlayer(mock(Player.class));
|
||||
|
||||
// then
|
||||
assertThat(result, equalTo(limbo));
|
||||
verify(handler).getLimboPlayer(player);
|
||||
verify(handler).saveLimboPlayer(eq(player), argThat(notNullAndDifferentFrom(limbo)));
|
||||
verify(handler).removeLimboPlayer(argThat(notNullAndDifferentFrom(player)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReloadProperly() {
|
||||
// given
|
||||
given(settings.getProperty(LimboSettings.LIMBO_PERSISTENCE_TYPE))
|
||||
.willReturn(LimboPersistenceType.INDIVIDUAL_FILES);
|
||||
|
||||
// when
|
||||
limboPersistence.reload(settings);
|
||||
|
||||
// then
|
||||
assertThat(getHandler(), instanceOf(LimboPersistenceType.INDIVIDUAL_FILES.getImplementationClass()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldNotReinitializeHandlerForSameType() {
|
||||
// given
|
||||
LimboPersistenceHandler currentHandler = getHandler();
|
||||
Mockito.reset(handlerFactory);
|
||||
given(currentHandler.getType()).willCallRealMethod();
|
||||
|
||||
// when
|
||||
limboPersistence.reload(settings);
|
||||
|
||||
// then
|
||||
verifyZeroInteractions(handlerFactory);
|
||||
assertThat(currentHandler, sameInstance(getHandler()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandleExceptionWhenGettingLimbo() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
Logger logger = TestHelper.setupLogger();
|
||||
LimboPersistenceHandler handler = getHandler();
|
||||
doThrow(IllegalAccessException.class).when(handler).getLimboPlayer(player);
|
||||
|
||||
// when
|
||||
LimboPlayer result = limboPersistence.getLimboPlayer(player);
|
||||
|
||||
// then
|
||||
assertThat(result, nullValue());
|
||||
verify(logger).warning(argThat(containsString("[IllegalAccessException]")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandleExceptionWhenSavingLimbo() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
LimboPlayer limbo = mock(LimboPlayer.class);
|
||||
Logger logger = TestHelper.setupLogger();
|
||||
LimboPersistenceHandler handler = getHandler();
|
||||
doThrow(IllegalStateException.class).when(handler).saveLimboPlayer(player, limbo);
|
||||
|
||||
// when
|
||||
limboPersistence.saveLimboPlayer(player, limbo);
|
||||
|
||||
// then
|
||||
verify(logger).warning(argThat(containsString("[IllegalStateException]")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldHandleExceptionWhenRemovingLimbo() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
Logger logger = TestHelper.setupLogger();
|
||||
LimboPersistenceHandler handler = getHandler();
|
||||
doThrow(UnsupportedOperationException.class).when(handler).removeLimboPlayer(player);
|
||||
|
||||
// when
|
||||
limboPersistence.removeLimboPlayer(player);
|
||||
|
||||
// then
|
||||
verify(logger).warning(argThat(containsString("[UnsupportedOperationException]")));
|
||||
}
|
||||
|
||||
private LimboPersistenceHandler getHandler() {
|
||||
return ReflectionTestUtils.getFieldValue(LimboPersistence.class, limboPersistence, "handler");
|
||||
}
|
||||
|
||||
private static <T> Matcher<T> notNullAndDifferentFrom(T o) {
|
||||
return both(not(sameInstance(o))).and(not(nullValue()));
|
||||
}
|
||||
}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
package fr.xephi.authme.data.limbo.persistence;
|
||||
|
||||
import ch.jalu.injector.testing.BeforeInjecting;
|
||||
import ch.jalu.injector.testing.DelayedInjectionRunner;
|
||||
import ch.jalu.injector.testing.InjectDelayed;
|
||||
import fr.xephi.authme.TestHelper;
|
||||
import fr.xephi.authme.data.limbo.LimboPlayer;
|
||||
import fr.xephi.authme.initialization.DataFolder;
|
||||
import fr.xephi.authme.service.BukkitService;
|
||||
import fr.xephi.authme.util.FileUtils;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.UUID;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.hamcrest.Matchers.nullValue;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Test for {@link SeparateFilePersistenceHandler}.
|
||||
*/
|
||||
@RunWith(DelayedInjectionRunner.class)
|
||||
public class SeparateFilePersistenceHandlerTest {
|
||||
|
||||
private static final UUID SAMPLE_UUID = UUID.nameUUIDFromBytes("PersistenceTest".getBytes());
|
||||
private static final String SOURCE_FOLDER = TestHelper.PROJECT_ROOT + "data/backup/";
|
||||
|
||||
@InjectDelayed
|
||||
private SeparateFilePersistenceHandler handler;
|
||||
|
||||
@Mock
|
||||
private BukkitService bukkitService;
|
||||
|
||||
@DataFolder
|
||||
private File dataFolder;
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
|
||||
@BeforeInjecting
|
||||
public void copyTestFiles() throws IOException {
|
||||
dataFolder = temporaryFolder.newFolder();
|
||||
File playerFolder = new File(dataFolder, FileUtils.makePath("playerdata", SAMPLE_UUID.toString()));
|
||||
if (!playerFolder.mkdirs()) {
|
||||
throw new IllegalStateException("Cannot create '" + playerFolder.getAbsolutePath() + "'");
|
||||
}
|
||||
Files.copy(TestHelper.getJarPath(FileUtils.makePath(SOURCE_FOLDER, "sample-folder", "data.json")),
|
||||
new File(playerFolder, "data.json").toPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReadDataFromFile() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
given(player.getUniqueId()).willReturn(SAMPLE_UUID);
|
||||
World world = mock(World.class);
|
||||
given(bukkitService.getWorld("nether")).willReturn(world);
|
||||
|
||||
// when
|
||||
LimboPlayer data = handler.getLimboPlayer(player);
|
||||
|
||||
// then
|
||||
assertThat(data, not(nullValue()));
|
||||
assertThat(data.isOperator(), equalTo(true));
|
||||
assertThat(data.isCanFly(), equalTo(true));
|
||||
assertThat(data.getWalkSpeed(), equalTo(0.2f));
|
||||
assertThat(data.getFlySpeed(), equalTo(0.1f));
|
||||
assertThat(data.getGroup(), equalTo("players"));
|
||||
Location location = data.getLocation();
|
||||
assertThat(location.getX(), equalTo(-113.219));
|
||||
assertThat(location.getY(), equalTo(72.0));
|
||||
assertThat(location.getZ(), equalTo(130.637));
|
||||
assertThat(location.getWorld(), equalTo(world));
|
||||
assertThat(location.getPitch(), equalTo(24.15f));
|
||||
assertThat(location.getYaw(), equalTo(-292.484f));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldReturnNullForUnavailablePlayer() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
given(player.getUniqueId()).willReturn(UUID.nameUUIDFromBytes("other-player".getBytes()));
|
||||
|
||||
// when
|
||||
LimboPlayer data = handler.getLimboPlayer(player);
|
||||
|
||||
// then
|
||||
assertThat(data, nullValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldSavePlayerData() {
|
||||
// given
|
||||
Player player = mock(Player.class);
|
||||
UUID uuid = UUID.nameUUIDFromBytes("New player".getBytes());
|
||||
given(player.getUniqueId()).willReturn(uuid);
|
||||
|
||||
|
||||
World world = mock(World.class);
|
||||
given(world.getName()).willReturn("player-world");
|
||||
Location location = new Location(world, 0.2, 102.25, -89.28, 3.02f, 90.13f);
|
||||
String group = "primary-grp";
|
||||
LimboPlayer limbo = new LimboPlayer(location, true, group, true, 1.2f, 0.8f);
|
||||
|
||||
// when
|
||||
handler.saveLimboPlayer(player, limbo);
|
||||
|
||||
// then
|
||||
File playerFile = new File(dataFolder, FileUtils.makePath("playerdata", uuid.toString(), "data.json"));
|
||||
assertThat(playerFile.exists(), equalTo(true));
|
||||
// TODO ljacqu 20160711: Check contents of file
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user