Refactor packet tests and update dependencies

This commit is contained in:
Oliver
2025-03-07 12:46:22 +01:00
parent c1f5e420ca
commit 9e6be5b4a1
17 changed files with 124 additions and 34 deletions

View File

@@ -1,243 +0,0 @@
package de.oliver.fancylib.tests;
/**
* A generic class for making assertions on the expected values.
*
* @param <T> the type of the value to be asserted.
*/
public class Expectable<T> {
/**
* The value that is being wrapped by this Expectable instance.
* This is the object against which all expectations will be verified.
*/
private final T t;
private Expectable(T t) {
this.t = t;
}
/**
* Creates a new instance of Expectable for the given value.
*
* @param <T> the type of the value being tested
* @param t the actual value to create an expectation for
* @return a new Expectable instance for the given value
*/
public static <T> Expectable<T> expect(T t) {
return new Expectable<>(t);
}
/**
* Ensures that the actual value is not null.
* <p>
* Throws an AssertionError if the value of the field 't' is null,
* indicating that the actual value is expected to be non-null.
*
* @throws AssertionError if the value of the field 't' is null
*/
public void toBeDefined() {
if (t == null) {
throw new AssertionError("Expected not null but got null");
}
}
/**
* Asserts that the value of the field 't' is null.
* <p>
* Throws an AssertionError if the value of 't' is not null,
* indicating the expectation that the value should be null.
*
* @throws AssertionError if the value of 't' is not null
*/
public void toBeNull() {
if (t != null) {
throw new AssertionError("Expected null but got not null");
}
}
/**
* Asserts that the actual value is equal to the expected value.
*
* @param expected the value that the actual value is expected to be equal to
* @throws AssertionError if the actual value is not equal to the expected value
*/
public void toBe(T expected) {
if (t != expected) {
throw new AssertionError("Expected " + expected + " but got " + t);
}
}
/**
* Asserts that the actual value is equal to the expected value using the {@code equals} method.
*
* @param expected the value that the actual value is expected to be equal to
* @throws AssertionError if the actual value is not equal to the expected value
*/
public void toEqual(T expected) {
if (!t.equals(expected)) {
throw new AssertionError("Expected " + expected + " but got " + t);
}
}
/**
* Asserts that the actual value is greater than the expected value.
*
* @param expected the value that the actual value is expected to be greater than
* @throws AssertionError if the actual value is not greater than the expected value,
* or if the type of the actual value is not one of Integer, Long, Float, or Double
*/
public void toBeGreaterThan(T expected) {
if (t instanceof Integer) {
if ((Integer) t <= (Integer) expected) {
throw new AssertionError("Expected " + t + " to be greater than " + expected);
} else {
return;
}
} else if (t instanceof Long) {
if ((Long) t <= (Long) expected) {
throw new AssertionError("Expected " + t + " to be greater than " + expected);
} else {
return;
}
} else if (t instanceof Float) {
if ((Float) t <= (Float) expected) {
throw new AssertionError("Expected " + t + " to be greater than " + expected);
} else {
return;
}
} else if (t instanceof Double) {
if ((Double) t <= (Double) expected) {
throw new AssertionError("Expected " + t + " to be greater than " + expected);
} else {
return;
}
}
throw new AssertionError("toBeGreaterThan can only be used on Integers, Longs, Floats, and Doubles");
}
/**
* Asserts that the actual value is less than the expected value.
*
* @param expected the value that the actual value is expected to be less than
* @throws AssertionError if the actual value is not less than the expected value,
* or if the type of the actual value is not one of Integer, Long, Float, or Double
*/
public void toBeLessThan(T expected) {
if (t instanceof Integer) {
if ((Integer) t >= (Integer) expected) {
throw new AssertionError("Expected " + t + " to be less than " + expected);
} else {
return;
}
} else if (t instanceof Long) {
if ((Long) t >= (Long) expected) {
throw new AssertionError("Expected " + t + " to be less than " + expected);
} else {
return;
}
} else if (t instanceof Float) {
if ((Float) t >= (Float) expected) {
throw new AssertionError("Expected " + t + " to be less than " + expected);
} else {
return;
}
} else if (t instanceof Double) {
if ((Double) t >= (Double) expected) {
throw new AssertionError("Expected " + t + " to be less than " + expected);
} else {
return;
}
}
throw new AssertionError("toBeLessThan can only be used on Integers, Longs, Floats, and Doubles");
}
/**
* Asserts that the actual value is an instance of the expected class.
* This method checks whether the value held in the field 't' is an instance of the provided Class.
*
* @param expected the Class object that the actual value is expected to be an instance of
* @throws AssertionError if the actual value is not an instance of the expected class
*/
public void toBeInstanceOf(Class<?> expected) {
if (!expected.isInstance(t)) {
throw new AssertionError("Expected " + t + " to be an instance of " + expected);
}
}
/**
* Asserts that the given expected value is contained within the actual value.
* <p>
* This method checks if the expected value is present in a String, Iterable, or Array.
* If the actual value is a String, it uses the contains method to check if the expected value
* is a substring. If the actual value is an Iterable, it checks if the expected value is an element.
* If the actual value is an Array, it checks if the expected value is present in the array.
*
* @param expected the value that is expected to be contained within the actual value
* @throws AssertionError if the expected value is not contained within the actual value
*/
public void toContain(Object expected) {
if (t instanceof String) {
if (!((String) t).contains((String) expected)) {
throw new AssertionError("Expected " + expected + " to be contained in " + t);
} else {
return;
}
} else if (t instanceof Iterable) {
if (!((Iterable<?>) t).spliterator().tryAdvance(o -> {
if (o.equals(expected)) {
return;
}
throw new AssertionError("Expected " + expected + " to be contained in " + t);
})) {
throw new AssertionError("Expected " + expected + " to be contained in " + t);
} else {
return;
}
} else if (t instanceof Object[]) {
for (Object o : (Object[]) t) {
if (o.equals(expected)) {
return;
}
}
throw new AssertionError("Expected " + expected + " to be contained in " + t);
}
throw new AssertionError("toContain can only be used on Strings, Iterables and Arrays");
}
/**
* Asserts that the actual value has the expected length.
* This method checks if the actual value is a String, Iterable, or Array,
* and compares their length or size to the given expected length.
*
* @param expected the expected length of the actual value
* @throws AssertionError if the actual value does not have the expected length,
* or if the actual value is not of type String, Iterable, or Array
*/
public void toHaveLength(int expected) {
if (t instanceof String) {
if (((String) t).length() != expected) {
throw new AssertionError("Expected " + expected + " but got " + ((String) t).length());
} else {
return;
}
} else if (t instanceof Iterable) {
if (((Iterable<?>) t).spliterator().getExactSizeIfKnown() != expected) {
throw new AssertionError("Expected " + expected + " but got " + ((Iterable<?>) t).spliterator().getExactSizeIfKnown());
} else {
return;
}
} else if (t instanceof Object[]) {
if (((Object[]) t).length != expected) {
throw new AssertionError("Expected " + expected + " but got " + ((Object[]) t).length);
} else {
return;
}
}
throw new AssertionError("toHaveLength can only be used on Strings");
}
}

View File

@@ -1,147 +0,0 @@
package de.oliver.fancylib.tests;
import de.oliver.fancylib.tests.annotations.FPAfterEach;
import de.oliver.fancylib.tests.annotations.FPBeforeEach;
import de.oliver.fancylib.tests.annotations.FPTest;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.entity.Player;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
/**
* FPTestClass is a record that encapsulates information about a test class and its associated test methods.
* This class supports running tests annotated with {@link FPTest}.
*
* @param testClass the test class to run tests for (must be annotated with {@link FPTest})
* @param beforeEach the method annotated with {@link FPBeforeEach} to run before each test
* @param afterEach the method annotated with {@link FPAfterEach} to run after each test
* @param testMethods the list of test methods annotated with {@link FPTest}
*/
public record FPTestClass(
Class<?> testClass,
Method beforeEach,
Method afterEach,
List<Method> testMethods
) {
private static final Logger logger = Logger.getLogger(FPTestClass.class.getName());
/**
* Creates an instance of FPTestClass by inspecting the provided test class for methods annotated
* with FPTest, FPBeforeEach, and FPAfterEach annotations.
* These methods are used to define the setup, teardown, and test methods for the class.
*
* @param testClass the class to be inspected for annotated methods
* @return an instance of FPTestClass containing the test class and its annotated methods
*/
public static FPTestClass fromClass(Class<?> testClass) {
Method beforeEach = null;
Method afterEach = null;
List<Method> testMethods = new ArrayList<>();
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(FPTest.class)) {
if (method.getParameterCount() != 1) continue;
if (method.getParameterTypes()[0] != Player.class) continue;
testMethods.add(method);
continue;
}
if (method.isAnnotationPresent(FPBeforeEach.class)) {
if (method.getParameterCount() != 1) continue;
if (method.getParameterTypes()[0] != Player.class) continue;
beforeEach = method;
continue;
}
if (method.isAnnotationPresent(FPAfterEach.class)) {
if (method.getParameterCount() != 1) continue;
if (method.getParameterTypes()[0] != Player.class) continue;
afterEach = method;
}
}
return new FPTestClass(testClass, beforeEach, afterEach, testMethods);
}
/**
* Runs the test methods belonging to the test class, performing any necessary setup and teardown operations.
*
* @param player The player context to pass to the test methods.
* @return true if all tests completed successfully, false if any test failed or an unexpected exception occurred.
*/
public boolean runTests(Player player) {
logger.info("Running tests for " + testClass.getSimpleName());
player.sendMessage(MiniMessage.miniMessage().deserialize("<green>Running tests for " + testClass.getSimpleName()));
for (Method testMethod : testMethods) {
Object testClassObj;
try {
testClassObj = testClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
logger.warning("Failed to create test class instance: " + e.getMessage());
return false;
}
FPTest fpTest = testMethod.getAnnotation(FPTest.class);
if (fpTest.skip()) {
logger.info("Skipping test " + displayName(testMethod));
player.sendMessage(MiniMessage.miniMessage().deserialize("<gold>Skipping test " + displayName(testMethod)));
continue;
}
long testStart = System.currentTimeMillis();
try {
if (beforeEach != null) beforeEach.invoke(testClassObj, player);
testMethod.invoke(testClassObj, player);
if (afterEach != null) afterEach.invoke(testClassObj, player);
} catch (InvocationTargetException e) {
logger.warning("Test " + displayName(testMethod) + " failed with exception: " + e.getCause().getMessage());
player.sendMessage(MiniMessage.miniMessage().deserialize("<red>Test " + displayName(testMethod) + " failed with exception: " + e.getCause().getMessage()));
return false;
} catch (Exception e) {
logger.warning("Unexpected exception in test " + fpTest.name() + ": " + e.getMessage());
return false;
}
long testEnd = System.currentTimeMillis();
logger.info("Test " + displayName(testMethod) + " took " + (testEnd - testStart) + "ms");
player.sendMessage(MiniMessage.miniMessage().deserialize("<green>Test " + displayName(testMethod) + " took " + (testEnd - testStart) + "ms"));
try {
Thread.sleep(100);
} catch (InterruptedException e) {
logger.warning("Thread interrupted while waiting between tests: " + e.getMessage());
}
}
return true;
}
/**
* Generates a display name for a given test method, incorporating annotation details if present.
*
* @param m the method for which to generate the display name
* @return a display name that includes the test class and method name, and optionally the value of the FPTest annotation's name attribute if the annotation is present
*/
public String displayName(Method m) {
if (!m.isAnnotationPresent(FPTest.class)) {
return testClass.getSimpleName() + "#" + m.getName();
}
FPTest fpTest = m.getAnnotation(FPTest.class);
return testClass.getSimpleName() + "#" + m.getName() + " (" + fpTest.name() + ")";
}
}

View File

@@ -1,16 +0,0 @@
package de.oliver.fancylib.tests.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Annotation to mark a method that should be executed after each test case in a test class.
* This annotation is used to identify methods that perform teardown operations, ensuring
* that the test environment is cleaned up and reset after each individual test method is executed.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FPAfterEach {
}

View File

@@ -1,16 +0,0 @@
package de.oliver.fancylib.tests.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* FPBeforeEach is a custom annotation designed to be used on methods that should be executed before each test method.
* Methods annotated with FPBeforeEach are typically used to perform setup operations needed before executing each test case.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FPBeforeEach {
}

View File

@@ -1,31 +0,0 @@
package de.oliver.fancylib.tests.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* FPTest is a custom annotation designed to be used on methods for marking them as test cases.
* It helps to identify methods that should be treated as test cases in the testing framework.
* The annotation's attributes allow for providing a human-readable test name and an optional flag to skip the test.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface FPTest {
/**
* Specifies the name of the test case. This name is used to identify the test case
* in reports, logs, and other contexts where the test case is referenced.
*
* @return the name of the test case
*/
String name();
/**
* Indicates whether the annotated test case should be skipped during test execution.
*
* @return true if the test case should be skipped, false otherwise
*/
boolean skip() default false;
}