#347 Create tests and add check for missing settings in NewSetting

This commit is contained in:
ljacqu 2016-01-08 21:22:26 +01:00
parent 30db03837a
commit 69c225c850
8 changed files with 309 additions and 59 deletions

View File

@ -1,9 +1,8 @@
package fr.xephi.authme.settings.custom;
import com.google.common.annotations.VisibleForTesting;
import fr.xephi.authme.ConsoleLogger;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import fr.xephi.authme.util.CollectionUtils;
import fr.xephi.authme.util.StringUtils;
@ -12,8 +11,6 @@ import org.bukkit.configuration.file.YamlConfiguration;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -24,22 +21,21 @@ import java.util.Map;
*/
public class NewSetting {
private static final List<Class<? extends SettingsClass>> CONFIGURATION_CLASSES = Arrays.asList(
ConverterSettings.class, DatabaseSettings.class, EmailSettings.class, HooksSettings.class,
ProtectionSettings.class, PurgeSettings.class, SecuritySettings.class);
private File file;
private YamlConfiguration configuration;
public NewSetting(File file) {
this.configuration = YamlConfiguration.loadConfiguration(file);
this.file = file;
PropertyMap propertyMap = SettingsFieldRetriever.getAllPropertyFields();
if (!containsAllSettings(propertyMap)) {
save(propertyMap);
}
}
// TODO: No way of passing just a YamlConfiguration object (later on for mocking purposes) ?
// If not, best is probably to keep this constructor as package-private with @VisibleForTesting
// but it's not a satisfying solution
public NewSetting(YamlConfiguration yamlConfiguration, String file) {
@VisibleForTesting
NewSetting(YamlConfiguration yamlConfiguration, String file) {
this.configuration = yamlConfiguration;
this.file = new File(file);
}
@ -49,14 +45,16 @@ public class NewSetting {
}
public void save() {
PropertyMap properties = getAllPropertyFields();
save(SettingsFieldRetriever.getAllPropertyFields());
}
public void save(PropertyMap propertyMap) {
try (FileWriter writer = new FileWriter(file)) {
writer.write("");
// Contains all but the last node of the setting, e.g. [DataSource, mysql] for "DataSource.mysql.username"
List<String> currentPath = new ArrayList<>();
for (Map.Entry<Property, String[]> entry : properties.entrySet()) {
for (Map.Entry<Property<?>, String[]> entry : propertyMap.entrySet()) {
Property<?> property = entry.getKey();
// Handle properties
@ -107,6 +105,15 @@ public class NewSetting {
}
}
private boolean containsAllSettings(PropertyMap propertyMap) {
for (Property<?> property : propertyMap.keySet()) {
if (!property.isPresent(configuration)) {
return false;
}
}
return true;
}
private static String indent(int level) {
// YAML uses indentation of 4 spaces
StringBuilder sb = new StringBuilder(level * 4);
@ -116,40 +123,4 @@ public class NewSetting {
return sb.toString();
}
private static PropertyMap getAllPropertyFields() {
PropertyMap properties = new PropertyMap();
for (Class<?> clazz : CONFIGURATION_CLASSES) {
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
Property property = getFieldIfRelevant(field);
if (property != null) {
properties.put(property, getCommentsForField(field));
}
}
}
return properties;
}
private static String[] getCommentsForField(Field field) {
if (field.isAnnotationPresent(Comment.class)) {
return field.getAnnotation(Comment.class).value();
}
return new String[0];
}
private static Property<?> getFieldIfRelevant(Field field) {
field.setAccessible(true);
if (field.isAccessible() && Property.class.equals(field.getType()) && Modifier.isStatic(field.getModifiers())) {
try {
return (Property) field.get(null);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not fetch field '" + field.getName() + "' from class '"
+ field.getDeclaringClass().getSimpleName() + "': " + StringUtils.formatException(e));
}
}
return null;
}
}

View File

@ -0,0 +1,67 @@
package fr.xephi.authme.settings.custom;
import fr.xephi.authme.settings.domain.Comment;
import fr.xephi.authme.settings.domain.Property;
import fr.xephi.authme.settings.domain.SettingsClass;
import fr.xephi.authme.settings.propertymap.PropertyMap;
import fr.xephi.authme.util.StringUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;
/**
* Utility class responsible for the retrieval of all {@link Property} fields via reflections.
*/
final class SettingsFieldRetriever {
/** The classes to scan for properties. */
private static final List<Class<? extends SettingsClass>> CONFIGURATION_CLASSES = Arrays.asList(
ConverterSettings.class, DatabaseSettings.class, EmailSettings.class, HooksSettings.class,
ProtectionSettings.class, PurgeSettings.class, SecuritySettings.class);
private SettingsFieldRetriever() {
}
/**
* Scan all given classes for their properties and return the generated {@link PropertyMap}.
*
* @return PropertyMap containing all found properties and their associated comments
* @see #CONFIGURATION_CLASSES
*/
public static PropertyMap getAllPropertyFields() {
PropertyMap properties = new PropertyMap();
for (Class<?> clazz : CONFIGURATION_CLASSES) {
Field[] declaredFields = clazz.getDeclaredFields();
for (Field field : declaredFields) {
Property property = getFieldIfRelevant(field);
if (property != null) {
properties.put(property, getCommentsForField(field));
}
}
}
return properties;
}
private static String[] getCommentsForField(Field field) {
if (field.isAnnotationPresent(Comment.class)) {
return field.getAnnotation(Comment.class).value();
}
return new String[0];
}
private static Property<?> getFieldIfRelevant(Field field) {
field.setAccessible(true);
if (field.isAccessible() && Property.class.equals(field.getType()) && Modifier.isStatic(field.getModifiers())) {
try {
return (Property) field.get(null);
} catch (IllegalAccessException e) {
throw new IllegalStateException("Could not fetch field '" + field.getName() + "' from class '"
+ field.getDeclaringClass().getSimpleName() + "': " + StringUtils.formatException(e));
}
}
return null;
}
}

View File

@ -74,6 +74,16 @@ public class Property<T> {
return type.asYaml(this, configuration);
}
/**
* Return whether or not the given configuration file contains the property.
*
* @param configuration The configuration file to verify
* @return True if the property is present, false otherwise
*/
public boolean isPresent(YamlConfiguration configuration) {
return type.contains(this, configuration);
}
// -----
// Trivial getters
// -----

View File

@ -41,6 +41,17 @@ public abstract class PropertyType<T> {
return asYaml(getFromFile(property, configuration));
}
/**
* Return whether the property is present in the given configuration.
*
* @param property The property to search for
* @param configuration The configuration to verify
* @return True if the property is present, false otherwise
*/
public boolean contains(Property<T> property, YamlConfiguration configuration) {
return configuration.contains(property.getPath());
}
/**
* Transform the given value to YAML.
*
@ -49,10 +60,6 @@ public abstract class PropertyType<T> {
*/
protected abstract List<String> asYaml(T value);
protected boolean contains(Property<T> property, YamlConfiguration configuration) {
return configuration.contains(property.getPath());
}
/**
* Boolean property.
@ -145,6 +152,11 @@ public abstract class PropertyType<T> {
}
return resultLines;
}
@Override
public boolean contains(Property<List<String>> property, YamlConfiguration configuration) {
return configuration.contains(property.getPath()) && configuration.isList(property.getPath());
}
}
}

View File

@ -14,7 +14,7 @@ import java.util.TreeMap;
*/
public class PropertyMap {
private Map<Property, String[]> propertyMap;
private Map<Property<?>, String[]> propertyMap;
private PropertyMapComparator comparator;
/**
@ -41,8 +41,17 @@ public class PropertyMap {
*
* @return The entry set
*/
public Set<Map.Entry<Property, String[]>> entrySet() {
public Set<Map.Entry<Property<?>, String[]>> entrySet() {
return propertyMap.entrySet();
}
/**
* Return the key set of the map, i.e. all property objects it holds.
*
* @return The key set
*/
public Set<Property<?>> keySet() {
return propertyMap.keySet();
}
}

View File

@ -30,7 +30,6 @@ public class NewSettingsWriteTest {
}
private File getConfigFile() {
URL url = getClass().getClassLoader().getResource(CONFIG_FILE);
if (url == null) {

View File

@ -0,0 +1,182 @@
package fr.xephi.authme.settings.domain;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.BeforeClass;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import java.util.Arrays;
import java.util.List;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyDouble;
import static org.mockito.Matchers.anyInt;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Test for {@link PropertyType} and the contained subtypes.
*/
public class PropertyTypeTest {
private static YamlConfiguration configuration;
@BeforeClass
public static void setUpYamlConfigurationMock() {
configuration = mock(YamlConfiguration.class);
when(configuration.getBoolean(eq("bool.path.test"), anyBoolean())).thenReturn(true);
when(configuration.getBoolean(eq("bool.path.wrong"), anyBoolean())).thenAnswer(secondParameter());
when(configuration.getDouble(eq("double.path.test"), anyDouble())).thenReturn(-6.4);
when(configuration.getDouble(eq("double.path.wrong"), anyDouble())).thenAnswer(secondParameter());
when(configuration.getInt(eq("int.path.test"), anyInt())).thenReturn(27);
when(configuration.getInt(eq("int.path.wrong"), anyInt())).thenAnswer(secondParameter());
when(configuration.getString(eq("str.path.test"), anyString())).thenReturn("Test value");
when(configuration.getString(eq("str.path.wrong"), anyString())).thenAnswer(secondParameter());
when(configuration.isList("list.path.test")).thenReturn(true);
when(configuration.getStringList("list.path.test")).thenReturn(Arrays.asList("test1", "Test2", "3rd test"));
when(configuration.isList("list.path.wrong")).thenReturn(false);
}
/* Boolean */
@Test
public void shouldGetBoolValue() {
// given
Property<Boolean> property = Property.newProperty("bool.path.test", false);
// when
boolean result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(true));
}
@Test
public void shouldGetBoolDefault() {
// given
Property<Boolean> property = Property.newProperty("bool.path.wrong", true);
// when
boolean result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(true));
}
/* Double */
@Test
public void shouldGetDoubleValue() {
// given
Property<Double> property = Property.newProperty(PropertyType.DOUBLE, "double.path.test", 3.8);
// when
double result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(-6.4));
}
@Test
public void shouldGetDoubleDefault() {
// given
Property<Double> property = Property.newProperty(PropertyType.DOUBLE, "double.path.wrong", 12.0);
// when
double result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(12.0));
}
/* Integer */
@Test
public void shouldGetIntValue() {
// given
Property<Integer> property = Property.newProperty("int.path.test", 3);
// when
int result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(27));
}
@Test
public void shouldGetIntDefault() {
// given
Property<Integer> property = Property.newProperty("int.path.wrong", -10);
// when
int result = property.getFromFile(configuration);
// then
assertThat(result, equalTo(-10));
}
/* String */
@Test
public void shouldGetStringValue() {
// given
Property<String> property = Property.newProperty("str.path.test", "unused default");
// when
String result = property.getFromFile(configuration);
// then
assertThat(result, equalTo("Test value"));
}
@Test
public void shouldGetStringDefault() {
// given
Property<String> property = Property.newProperty("str.path.wrong", "given default value");
// when
String result = property.getFromFile(configuration);
// then
assertThat(result, equalTo("given default value"));
}
/* String list */
@Test
public void shouldGetStringListValue() {
// given
Property<List<String>> property = Property.newProperty(PropertyType.STRING_LIST, "list.path.test", "1", "b");
// when
List<String> result = property.getFromFile(configuration);
// then
assertThat(result, contains("test1", "Test2", "3rd test"));
}
@Test
public void shouldGetStringListDefault() {
// given
Property<List<String>> property =
Property.newProperty(PropertyType.STRING_LIST, "list.path.wrong", "default", "list", "elements");
// when
List<String> result = property.getFromFile(configuration);
// then
assertThat(result, contains("default", "list", "elements"));
}
private static <T> Answer<T> secondParameter() {
return new Answer<T>() {
@Override
public T answer(InvocationOnMock invocation) throws Throwable {
// Return the second parameter -> the default
return (T) invocation.getArguments()[1];
}
};
}
}

View File

@ -34,9 +34,9 @@ public class PropertyMapTest {
}
// then
Set<Map.Entry<Property, String[]>> entrySet = map.entrySet();
Set<Map.Entry<Property<?>, String[]>> entrySet = map.entrySet();
List<String> resultPaths = new ArrayList<>(entrySet.size());
for (Map.Entry<Property, String[]> entry : entrySet) {
for (Map.Entry<Property<?>, String[]> entry : entrySet) {
resultPaths.add(entry.getKey().getPath());
}