mirror of
https://github.com/FancyInnovations/FancyPlugins.git
synced 2025-12-06 07:43:36 +00:00
Refactor packet tests and update dependencies
This commit is contained in:
87
libraries/plugin-tests/build.gradle.kts
Normal file
87
libraries/plugin-tests/build.gradle.kts
Normal file
@@ -0,0 +1,87 @@
|
||||
plugins {
|
||||
id("java")
|
||||
id("maven-publish")
|
||||
id("com.github.johnrengelman.shadow")
|
||||
}
|
||||
|
||||
group = "de.oliver"
|
||||
version = "1.0.0"
|
||||
description = "Library for defining and running tests in a Minecraft server environment"
|
||||
|
||||
java {
|
||||
toolchain.languageVersion.set(JavaLanguageVersion.of(17))
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://repo.papermc.io/repository/maven-public/")
|
||||
maven("https://repo.fancyplugins.de/releases")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("io.papermc.paper:paper-api:1.19.4-R0.1-SNAPSHOT")
|
||||
compileOnly("com.google.code.gson:gson:2.11.0")
|
||||
implementation("org.jetbrains:annotations:26.0.2")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
|
||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.11.1")
|
||||
testImplementation("com.google.code.gson:gson:2.11.0")
|
||||
}
|
||||
|
||||
tasks {
|
||||
publishing {
|
||||
repositories {
|
||||
maven {
|
||||
name = "fancypluginsReleases"
|
||||
url = uri("https://repo.fancyplugins.de/releases")
|
||||
credentials(PasswordCredentials::class)
|
||||
authentication {
|
||||
isAllowInsecureProtocol = true
|
||||
create<BasicAuthentication>("basic")
|
||||
}
|
||||
}
|
||||
|
||||
maven {
|
||||
name = "fancypluginsSnapshots"
|
||||
url = uri("https://repo.fancyplugins.de/snapshots")
|
||||
credentials(PasswordCredentials::class)
|
||||
authentication {
|
||||
isAllowInsecureProtocol = true
|
||||
create<BasicAuthentication>("basic")
|
||||
}
|
||||
}
|
||||
}
|
||||
publications {
|
||||
create<MavenPublication>("maven") {
|
||||
groupId = project.group.toString()
|
||||
artifactId = project.name
|
||||
version = project.version.toString()
|
||||
from(project.components["java"])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
compileJava {
|
||||
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
|
||||
// Set the release flag. This configures what version bytecode the compiler will emit, as well as what JDK APIs are usable.
|
||||
// See https://openjdk.java.net/jeps/247 for more information.
|
||||
options.release.set(17)
|
||||
}
|
||||
|
||||
java {
|
||||
withSourcesJar()
|
||||
withJavadocJar()
|
||||
}
|
||||
|
||||
javadoc {
|
||||
options.encoding = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
}
|
||||
processResources {
|
||||
filteringCharset = Charsets.UTF_8.name() // We want UTF-8 for everything
|
||||
}
|
||||
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,243 @@
|
||||
package de.oliver.plugintests;
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package de.oliver.plugintests;
|
||||
|
||||
import de.oliver.plugintests.annotations.FPAfterEach;
|
||||
import de.oliver.plugintests.annotations.FPBeforeEach;
|
||||
import de.oliver.plugintests.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() + ")";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.oliver.plugintests.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 {
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package de.oliver.plugintests.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 {
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package de.oliver.plugintests.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;
|
||||
}
|
||||
Reference in New Issue
Block a user