mirror of
https://github.com/AuthMe/AuthMeReloaded.git
synced 2024-12-25 10:07:35 +01:00
Create tool task that checks Mock fields of test classes
This commit is contained in:
parent
6af65e6cd4
commit
53043ddc0d
@ -11,7 +11,7 @@ import java.lang.reflect.InvocationTargetException;
|
|||||||
/**
|
/**
|
||||||
* Functionality for constructor injection.
|
* Functionality for constructor injection.
|
||||||
*/
|
*/
|
||||||
class ConstructorInjection<T> implements Injection<T> {
|
public class ConstructorInjection<T> implements Injection<T> {
|
||||||
|
|
||||||
private final Constructor<T> constructor;
|
private final Constructor<T> constructor;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import java.util.List;
|
|||||||
/**
|
/**
|
||||||
* Functionality for field injection.
|
* Functionality for field injection.
|
||||||
*/
|
*/
|
||||||
class FieldInjection<T> implements Injection<T> {
|
public class FieldInjection<T> implements Injection<T> {
|
||||||
|
|
||||||
private final Field[] fields;
|
private final Field[] fields;
|
||||||
private final Constructor<T> defaultConstructor;
|
private final Constructor<T> defaultConstructor;
|
||||||
|
@ -5,7 +5,7 @@ package fr.xephi.authme.initialization;
|
|||||||
*
|
*
|
||||||
* @param <T> the type of the concerned object
|
* @param <T> the type of the concerned object
|
||||||
*/
|
*/
|
||||||
interface Injection<T> {
|
public interface Injection<T> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the dependencies that must be provided to instantiate the given item.
|
* Returns the dependencies that must be provided to instantiate the given item.
|
||||||
|
@ -6,7 +6,6 @@ import fr.xephi.authme.output.Messages;
|
|||||||
import fr.xephi.authme.settings.NewSetting;
|
import fr.xephi.authme.settings.NewSetting;
|
||||||
import fr.xephi.authme.settings.domain.Property;
|
import fr.xephi.authme.settings.domain.Property;
|
||||||
import fr.xephi.authme.settings.properties.SecuritySettings;
|
import fr.xephi.authme.settings.properties.SecuritySettings;
|
||||||
import fr.xephi.authme.util.BukkitService;
|
|
||||||
import fr.xephi.authme.util.ValidationService;
|
import fr.xephi.authme.util.ValidationService;
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@ -45,8 +44,6 @@ public class CommandServiceTest {
|
|||||||
private NewSetting settings;
|
private NewSetting settings;
|
||||||
@Mock
|
@Mock
|
||||||
private ValidationService validationService;
|
private ValidationService validationService;
|
||||||
@Mock
|
|
||||||
private BukkitService bukkitService;
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldSendMessage() {
|
public void shouldSendMessage() {
|
||||||
|
@ -81,7 +81,7 @@ public final class ToolsRunner {
|
|||||||
private static void collectTasksInDirectory(File dir, Map<String, ToolTask> taskCollection) {
|
private static void collectTasksInDirectory(File dir, Map<String, ToolTask> taskCollection) {
|
||||||
File[] files = dir.listFiles();
|
File[] files = dir.listFiles();
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
throw new RuntimeException("Cannot read folder '" + dir + "'");
|
throw new IllegalStateException("Cannot read folder '" + dir + "'");
|
||||||
}
|
}
|
||||||
for (File file : files) {
|
for (File file : files) {
|
||||||
if (file.isDirectory()) {
|
if (file.isDirectory()) {
|
||||||
@ -112,7 +112,7 @@ public final class ToolsRunner {
|
|||||||
return constructor.newInstance();
|
return constructor.newInstance();
|
||||||
} catch (NoSuchMethodException | InvocationTargetException |
|
} catch (NoSuchMethodException | InvocationTargetException |
|
||||||
IllegalAccessException | InstantiationException e) {
|
IllegalAccessException | InstantiationException e) {
|
||||||
throw new RuntimeException("Cannot instantiate task '" + taskClass + "'");
|
throw new IllegalStateException("Cannot instantiate task '" + taskClass + "'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,7 +137,7 @@ public final class ToolsRunner {
|
|||||||
? (Class<? extends ToolTask>) clazz
|
? (Class<? extends ToolTask>) clazz
|
||||||
: null;
|
: null;
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
throw new RuntimeException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
171
src/test/java/tools/checktestmocks/CheckTestMocks.java
Normal file
171
src/test/java/tools/checktestmocks/CheckTestMocks.java
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
package tools.checktestmocks;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
|
import com.google.common.collect.Sets;
|
||||||
|
import fr.xephi.authme.initialization.ConstructorInjection;
|
||||||
|
import fr.xephi.authme.initialization.FieldInjection;
|
||||||
|
import fr.xephi.authme.initialization.Injection;
|
||||||
|
import fr.xephi.authme.util.StringUtils;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import tools.utils.AutoToolTask;
|
||||||
|
import tools.utils.ToolsConstants;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Scanner;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Task checking if all tests' {@code @Mock} fields have a corresponding
|
||||||
|
* {@code @Inject} field in the class they are testing.
|
||||||
|
*/
|
||||||
|
public class CheckTestMocks implements AutoToolTask {
|
||||||
|
|
||||||
|
private List<String> errors = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getTaskName() {
|
||||||
|
return "checkTestMocks";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(Scanner scanner) {
|
||||||
|
executeDefault();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeDefault() {
|
||||||
|
readAndCheckFiles(new File(ToolsConstants.TEST_SOURCE_ROOT));
|
||||||
|
System.out.println(StringUtils.join("\n", errors));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recursively reads directories and checks the contained classes.
|
||||||
|
*
|
||||||
|
* @param dir the directory to read
|
||||||
|
*/
|
||||||
|
private void readAndCheckFiles(File dir) {
|
||||||
|
File[] files = dir.listFiles();
|
||||||
|
if (files == null) {
|
||||||
|
throw new IllegalStateException("Cannot read folder '" + dir + "'");
|
||||||
|
}
|
||||||
|
for (File file : files) {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
readAndCheckFiles(file);
|
||||||
|
} else if (file.isFile()) {
|
||||||
|
Class<?> clazz = loadTestClass(file);
|
||||||
|
if (clazz != null) {
|
||||||
|
checkClass(clazz);
|
||||||
|
}
|
||||||
|
// else System.out.format("No @Mock fields found in class of file '%s'%n", file.getName())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks the given test class' @Mock fields against the corresponding production class' @Inject fields.
|
||||||
|
*
|
||||||
|
* @param testClass the test class to verify
|
||||||
|
*/
|
||||||
|
private void checkClass(Class<?> testClass) {
|
||||||
|
Class<?> realClass = returnRealClass(testClass);
|
||||||
|
if (realClass != null) {
|
||||||
|
Set<Class<?>> mockFields = getMocks(testClass);
|
||||||
|
Set<Class<?>> injectFields = getRealClassDependencies(realClass);
|
||||||
|
if (!injectFields.containsAll(mockFields)) {
|
||||||
|
addErrorEntry(testClass, "Error - Found the following mocks absent as @Inject: "
|
||||||
|
+ formatClassList(Sets.difference(mockFields, injectFields)));
|
||||||
|
} else if (!mockFields.containsAll(injectFields)) {
|
||||||
|
addErrorEntry(testClass, "Info - Found @Inject fields which are not present as @Mock: "
|
||||||
|
+ formatClassList(Sets.difference(injectFields, mockFields)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addErrorEntry(Class<?> clazz, String message) {
|
||||||
|
errors.add(clazz.getSimpleName() + ": " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<?> loadTestClass(File file) {
|
||||||
|
String fileName = file.getPath();
|
||||||
|
String className = fileName
|
||||||
|
// Strip source folders and .java ending
|
||||||
|
.substring("src/test/java/".length(), fileName.length() - 5)
|
||||||
|
.replace(File.separator, ".");
|
||||||
|
try {
|
||||||
|
Class<?> clazz = CheckTestMocks.class.getClassLoader().loadClass(className);
|
||||||
|
return isTestClassWithMocks(clazz) ? clazz : null;
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new UnsupportedOperationException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<Class<?>> getMocks(Class<?> clazz) {
|
||||||
|
Set<Class<?>> result = new HashSet<>();
|
||||||
|
for (Field field : clazz.getDeclaredFields()) {
|
||||||
|
if (field.isAnnotationPresent(Mock.class)) {
|
||||||
|
result.add(field.getType());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the production class ("real class") corresponding to the given test class.
|
||||||
|
* Returns null if the production class could not be mapped or loaded.
|
||||||
|
*
|
||||||
|
* @param testClass the test class to find the corresponding production class for
|
||||||
|
* @return production class, or null if not found
|
||||||
|
*/
|
||||||
|
private static Class<?> returnRealClass(Class<?> testClass) {
|
||||||
|
String testClassName = testClass.getName();
|
||||||
|
String realClassName = testClassName.replaceAll("(Integration|Consistency)?Test$", "");
|
||||||
|
if (realClassName.equals(testClassName)) {
|
||||||
|
System.out.format("%s doesn't match typical test class naming pattern.%n", testClassName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return CheckTestMocks.class.getClassLoader().loadClass(realClassName);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
System.out.format("Real class '%s' not found for test class '%s'%n", realClassName, testClassName);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Set<Class<?>> getRealClassDependencies(Class<?> realClass) {
|
||||||
|
Injection<?> injection = ConstructorInjection.provide(realClass).get();
|
||||||
|
if (injection != null) {
|
||||||
|
return Sets.newHashSet(injection.getDependencies());
|
||||||
|
}
|
||||||
|
injection = FieldInjection.provide(realClass).get();
|
||||||
|
return injection == null
|
||||||
|
? Collections.<Class<?>>emptySet()
|
||||||
|
: Sets.newHashSet(injection.getDependencies());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isTestClassWithMocks(Class<?> clazz) {
|
||||||
|
for (Field field : clazz.getDeclaredFields()) {
|
||||||
|
if (field.isAnnotationPresent(Mock.class)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String formatClassList(Collection<Class<?>> coll) {
|
||||||
|
Collection<String> classNames = Collections2.transform(coll, new Function<Class<?>, String>() {
|
||||||
|
@Override
|
||||||
|
public String apply(Class<?> input) {
|
||||||
|
return input.getSimpleName();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return StringUtils.join(", ", classNames);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -12,6 +12,9 @@ public final class ToolsConstants {
|
|||||||
|
|
||||||
public static final String MAIN_RESOURCES_ROOT = "src/main/resources/";
|
public static final String MAIN_RESOURCES_ROOT = "src/main/resources/";
|
||||||
|
|
||||||
|
// Add specific `fr.xephi.authme` package as not to include the tool tasks in the `tools` package
|
||||||
|
public static final String TEST_SOURCE_ROOT = "src/test/java/fr/xephi/authme";
|
||||||
|
|
||||||
public static final String TOOLS_SOURCE_ROOT = "src/test/java/tools/";
|
public static final String TOOLS_SOURCE_ROOT = "src/test/java/tools/";
|
||||||
|
|
||||||
public static final String DOCS_FOLDER = "docs/";
|
public static final String DOCS_FOLDER = "docs/";
|
||||||
|
Loading…
Reference in New Issue
Block a user