forked from Clones/Controlify
test framework
This commit is contained in:
22
.github/workflows/gradle.yml
vendored
22
.github/workflows/gradle.yml
vendored
@ -48,3 +48,25 @@ jobs:
|
|||||||
- uses: actions/upload-artifact@v3
|
- uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
path: build/libs/*.jar
|
path: build/libs/*.jar
|
||||||
|
|
||||||
|
client_test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: In-game test
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
|
distribution: microsoft
|
||||||
|
java-version: 17
|
||||||
|
- name: Run auto test client
|
||||||
|
uses: modmuss50/xvfb-action@v1
|
||||||
|
with:
|
||||||
|
run: ./gradlew runTestmod --stracktrace --warning-mode=fail --no-daemon
|
||||||
|
- name: Upload test screenshots
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: test-screenshots
|
||||||
|
path: run/screenshots
|
||||||
|
@ -31,8 +31,26 @@ repositories {
|
|||||||
maven("https://maven.flashyreese.me/snapshots")
|
maven("https://maven.flashyreese.me/snapshots")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val testmod by sourceSets.registering {
|
||||||
|
compileClasspath += sourceSets.main.get().compileClasspath
|
||||||
|
runtimeClasspath += sourceSets.main.get().runtimeClasspath
|
||||||
|
}
|
||||||
|
|
||||||
loom {
|
loom {
|
||||||
accessWidenerPath.set(file("src/main/resources/controlify.accesswidener"))
|
accessWidenerPath.set(file("src/main/resources/controlify.accesswidener"))
|
||||||
|
|
||||||
|
runs {
|
||||||
|
register("testmod") {
|
||||||
|
client()
|
||||||
|
ideConfigGenerated(true)
|
||||||
|
name("Test Mod")
|
||||||
|
source(testmod.get())
|
||||||
|
}
|
||||||
|
|
||||||
|
named("server") { ideConfigGenerated(false) }
|
||||||
|
}
|
||||||
|
|
||||||
|
createRemapConfigurations(testmod.get())
|
||||||
}
|
}
|
||||||
|
|
||||||
val minecraftVersion = libs.versions.minecraft.get()
|
val minecraftVersion = libs.versions.minecraft.get()
|
||||||
@ -73,6 +91,9 @@ dependencies {
|
|||||||
modImplementation(files("libs/iris-5d0efad3.jar"))
|
modImplementation(files("libs/iris-5d0efad3.jar"))
|
||||||
modRuntimeOnly("org.anarres:jcpp:1.4.14")
|
modRuntimeOnly("org.anarres:jcpp:1.4.14")
|
||||||
modRuntimeOnly("io.github.douira:glsl-transformer:2.0.0-pre9")
|
modRuntimeOnly("io.github.douira:glsl-transformer:2.0.0-pre9")
|
||||||
|
|
||||||
|
// testmod
|
||||||
|
"testmodImplementation"(sourceSets.main.get().output)
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
@ -36,7 +36,7 @@ public class Controlify implements ControlifyApi {
|
|||||||
private InGameInputHandler inGameInputHandler;
|
private InGameInputHandler inGameInputHandler;
|
||||||
public InGameButtonGuide inGameButtonGuide;
|
public InGameButtonGuide inGameButtonGuide;
|
||||||
private VirtualMouseHandler virtualMouseHandler;
|
private VirtualMouseHandler virtualMouseHandler;
|
||||||
private InputMode currentInputMode;
|
private InputMode currentInputMode = InputMode.KEYBOARD_MOUSE;
|
||||||
private ControllerHIDService controllerHIDService;
|
private ControllerHIDService controllerHIDService;
|
||||||
|
|
||||||
private final ControlifyConfig config = new ControlifyConfig(this);
|
private final ControlifyConfig config = new ControlifyConfig(this);
|
||||||
@ -290,9 +290,4 @@ public class Controlify implements ControlifyApi {
|
|||||||
if (instance == null) instance = new Controlify();
|
if (instance == null) instance = new Controlify();
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull ControlifyBindingsApi bindingsApi() {
|
|
||||||
return ControllerBindings.Api.INSTANCE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -19,8 +19,6 @@ public interface ControlifyApi {
|
|||||||
@NotNull InputMode currentInputMode();
|
@NotNull InputMode currentInputMode();
|
||||||
void setInputMode(@NotNull InputMode mode);
|
void setInputMode(@NotNull InputMode mode);
|
||||||
|
|
||||||
@NotNull ControlifyBindingsApi bindingsApi();
|
|
||||||
|
|
||||||
static ControlifyApi get() {
|
static ControlifyApi get() {
|
||||||
return Controlify.instance();
|
return Controlify.instance();
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package dev.isxander.controlify.api.bind;
|
package dev.isxander.controlify.api.bind;
|
||||||
|
|
||||||
import dev.isxander.controlify.bindings.BindingSupplier;
|
import dev.isxander.controlify.bindings.BindingSupplier;
|
||||||
|
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||||
import dev.isxander.controlify.bindings.GamepadBinds;
|
import dev.isxander.controlify.bindings.GamepadBinds;
|
||||||
import net.minecraft.client.KeyMapping;
|
import net.minecraft.client.KeyMapping;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
@ -29,4 +30,8 @@ public interface ControlifyBindingsApi {
|
|||||||
* @return the binding supplier to fetch the binding for a specific controller.
|
* @return the binding supplier to fetch the binding for a specific controller.
|
||||||
*/
|
*/
|
||||||
BindingSupplier registerBind(GamepadBinds bind, ResourceLocation id, KeyMapping override, BooleanSupplier toggleOverride);
|
BindingSupplier registerBind(GamepadBinds bind, ResourceLocation id, KeyMapping override, BooleanSupplier toggleOverride);
|
||||||
|
|
||||||
|
static ControlifyBindingsApi get() {
|
||||||
|
return ControllerBindings.Api.INSTANCE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
package dev.isxander.controlify.test;
|
||||||
|
|
||||||
|
public class Assertions {
|
||||||
|
public static void assertEquals(Object expected, Object actual) {
|
||||||
|
assertTrue(expected.equals(actual), "Expected " + expected + " but got " + actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertEquals(Object expected, Object actual, String message) {
|
||||||
|
assertTrue(expected.equals(actual), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertNotNull(Object object) {
|
||||||
|
assertTrue(object != null, "Expected object to not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertNotNull(Object object, String message) {
|
||||||
|
assertTrue(object != null, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertNull(Object object) {
|
||||||
|
assertTrue(object == null, "Expected object to be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertNull(Object object, String message) {
|
||||||
|
assertTrue(object == null, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertTrue(boolean condition) {
|
||||||
|
assertTrue(condition, "Condition not met");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertTrue(boolean condition, String message) {
|
||||||
|
if (!condition) {
|
||||||
|
throw new AssertionError(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertFalse(boolean condition) {
|
||||||
|
assertTrue(!condition, "Condition not met");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void assertFalse(boolean condition, String message) {
|
||||||
|
assertTrue(!condition, message);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,132 @@
|
|||||||
|
package dev.isxander.controlify.test;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||||
|
import dev.isxander.controlify.controller.Controller;
|
||||||
|
import dev.isxander.controlify.controller.ControllerConfig;
|
||||||
|
import dev.isxander.controlify.controller.ControllerState;
|
||||||
|
import dev.isxander.controlify.controller.ControllerType;
|
||||||
|
import net.minecraft.client.Minecraft;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class ClientTestHelper {
|
||||||
|
public static void waitForLoadingComplete() {
|
||||||
|
waitFor("Loading to complete", client -> client.getOverlay() == null, Duration.ofMinutes(5));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void waitFor(String what, Predicate<Minecraft> condition, Duration timeout) {
|
||||||
|
final LocalDateTime end = LocalDateTime.now().plus(timeout);
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
boolean result = submitAndWait(condition::test);
|
||||||
|
|
||||||
|
if (result) break;
|
||||||
|
|
||||||
|
if (LocalDateTime.now().isAfter(end)) {
|
||||||
|
throw new RuntimeException("Timed out waiting for " + what + " to complete. (timeout: " + timeout + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
waitFor(Duration.ofSeconds(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void waitFor(Duration duration) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(duration.toMillis());
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> CompletableFuture<T> submit(Function<Minecraft, T> function) {
|
||||||
|
return Minecraft.getInstance().submit(() -> function.apply(Minecraft.getInstance()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T submitAndWait(Function<Minecraft, T> function) {
|
||||||
|
return submit(function).join();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Controller<?, ?> createFakeController() {
|
||||||
|
return new Controller<>() {
|
||||||
|
private final ControllerBindings<ControllerState> bindings = new ControllerBindings<>(this);
|
||||||
|
private final ControllerConfig config = new ControllerConfig() {
|
||||||
|
@Override
|
||||||
|
public void setDeadzone(int axis, float deadzone) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public float getDeadzone(int axis) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String uid() {
|
||||||
|
return "FAKE";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String guid() {
|
||||||
|
return "FAKE";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControllerBindings<ControllerState> bindings() {
|
||||||
|
return bindings;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControllerConfig config() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControllerConfig defaultConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resetConfig() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setConfig(Gson gson, JsonElement json) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControllerType type() {
|
||||||
|
return ControllerType.UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String name() {
|
||||||
|
return "FAKE CONTROLLER";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControllerState state() {
|
||||||
|
return ControllerState.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ControllerState prevState() {
|
||||||
|
return ControllerState.EMPTY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void updateState() {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,93 @@
|
|||||||
|
package dev.isxander.controlify.test;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import net.fabricmc.api.ClientModInitializer;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.List;
|
||||||
|
import static dev.isxander.controlify.test.ClientTestHelper.*;
|
||||||
|
|
||||||
|
public class ControlifyAutoTestClient implements ClientModInitializer {
|
||||||
|
private static final Logger LOGGER = LoggerFactory.getLogger("Controlify Auto Test");
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onInitializeClient() {
|
||||||
|
var thread = new Thread(() -> {
|
||||||
|
try {
|
||||||
|
runTests(ControlifyTests.class);
|
||||||
|
} catch (Throwable t) {
|
||||||
|
t.printStackTrace();
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
thread.setName("Controlify Auto Test Thread");
|
||||||
|
thread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void runTests(Class<?> testClass) throws Exception {
|
||||||
|
List<Test> preLoadTests = Lists.newArrayList();
|
||||||
|
List<Test> postLoadTests = Lists.newArrayList();
|
||||||
|
|
||||||
|
Object testObject = testClass.getConstructor().newInstance();
|
||||||
|
|
||||||
|
Method[] methods = testClass.getDeclaredMethods();
|
||||||
|
for (var method : methods) {
|
||||||
|
method.setAccessible(true);
|
||||||
|
|
||||||
|
Test.PreLoad preLoad = method.getAnnotation(Test.PreLoad.class);
|
||||||
|
if (preLoad != null) {
|
||||||
|
if (method.getParameterCount() > 0)
|
||||||
|
throw new RuntimeException("PreLoad test method " + method.getName() + " has parameters!");
|
||||||
|
|
||||||
|
preLoadTests.add(new Test(() -> {
|
||||||
|
try {
|
||||||
|
method.invoke(testObject);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}, preLoad.value()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Test.PostLoad postLoad = method.getAnnotation(Test.PostLoad.class);
|
||||||
|
if (postLoad != null) {
|
||||||
|
if (method.getParameterCount() > 0)
|
||||||
|
throw new RuntimeException("PostLoad test method " + method.getName() + " has parameters!");
|
||||||
|
|
||||||
|
postLoadTests.add(new Test(() -> {
|
||||||
|
try {
|
||||||
|
method.invoke(testObject);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}, postLoad.value()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean success = true;
|
||||||
|
for (var test : preLoadTests) {
|
||||||
|
success &= wrapTest(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
waitForLoadingComplete();
|
||||||
|
|
||||||
|
for (var test : postLoadTests) {
|
||||||
|
success &= wrapTest(test);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.exit(success ? 0 : 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean wrapTest(Test test) {
|
||||||
|
LOGGER.info("\u001b[36mRunning test " + test.name() + "...");
|
||||||
|
try {
|
||||||
|
test.method().run();
|
||||||
|
LOGGER.info("\u001b[32mPassed test " + test.name() + "!");
|
||||||
|
return true;
|
||||||
|
} catch (Throwable t) {
|
||||||
|
LOGGER.error("\u001b[31mFailed test " + test.name() + "!", t);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package dev.isxander.controlify.test;
|
||||||
|
|
||||||
|
import dev.isxander.controlify.InputMode;
|
||||||
|
import dev.isxander.controlify.api.ControlifyApi;
|
||||||
|
import dev.isxander.controlify.api.bind.ControlifyBindingsApi;
|
||||||
|
import dev.isxander.controlify.api.event.ControlifyEvents;
|
||||||
|
import dev.isxander.controlify.bindings.BindingSupplier;
|
||||||
|
import dev.isxander.controlify.bindings.GamepadBinds;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
|
import static dev.isxander.controlify.test.Assertions.*;
|
||||||
|
import static dev.isxander.controlify.test.ClientTestHelper.*;
|
||||||
|
|
||||||
|
public class ControlifyTests {
|
||||||
|
BindingSupplier binding = null;
|
||||||
|
|
||||||
|
@Test.PreLoad("Binding registry test")
|
||||||
|
void bindingRegistryTest() {
|
||||||
|
var registry = ControlifyBindingsApi.get();
|
||||||
|
assertNotNull(registry, "Binding registry is null");
|
||||||
|
binding = registry.registerBind(GamepadBinds.A_BUTTON, new ResourceLocation("controlify", "test_bind"));
|
||||||
|
assertNotNull(binding, "Bind registry failed - BindingSupplier is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test.PostLoad("BindingSupplier getter test")
|
||||||
|
void bindingSupplierGetterTest() {
|
||||||
|
var controller = createFakeController();
|
||||||
|
assertNotNull(binding.get(controller), "Bind registry failed - Bind for fake controller is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test.PostLoad("Input mode changed event test")
|
||||||
|
void checkInputModeChangedEvent() {
|
||||||
|
var api = ControlifyApi.get();
|
||||||
|
|
||||||
|
AtomicBoolean called = new AtomicBoolean(false);
|
||||||
|
|
||||||
|
ControlifyEvents.INPUT_MODE_CHANGED.register(mode -> called.set(true));
|
||||||
|
api.setInputMode(InputMode.CONTROLLER);
|
||||||
|
api.setInputMode(InputMode.KEYBOARD_MOUSE);
|
||||||
|
|
||||||
|
assertTrue(called.get(), "Input mode changed event was not called");
|
||||||
|
}
|
||||||
|
}
|
20
src/testmod/java/dev/isxander/controlify/test/Test.java
Normal file
20
src/testmod/java/dev/isxander/controlify/test/Test.java
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package dev.isxander.controlify.test;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
public record Test(Runnable method, String name) {
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
public @interface PreLoad {
|
||||||
|
String value();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Target(ElementType.METHOD)
|
||||||
|
public @interface PostLoad {
|
||||||
|
String value();
|
||||||
|
}
|
||||||
|
}
|
8
src/testmod/resources/controlify-test.mixins.json
Normal file
8
src/testmod/resources/controlify-test.mixins.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"package": "dev.isxander.controlify.test.mixins",
|
||||||
|
"required": true,
|
||||||
|
"minVersion": "0.8",
|
||||||
|
"compatibilityLevel": "JAVA_17",
|
||||||
|
"mixins": [
|
||||||
|
]
|
||||||
|
}
|
19
src/testmod/resources/fabric.mod.json
Normal file
19
src/testmod/resources/fabric.mod.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"id": "controlify-test-client",
|
||||||
|
"version": "0",
|
||||||
|
"name": "Controlify Test Client",
|
||||||
|
"description": "Auto test module for Controlify.",
|
||||||
|
"environment": "client",
|
||||||
|
"entrypoints": {
|
||||||
|
"client": [
|
||||||
|
"dev.isxander.controlify.test.ControlifyAutoTestClient"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"mixins": [
|
||||||
|
"controlify-test.mixins.json"
|
||||||
|
],
|
||||||
|
"depends": {
|
||||||
|
"controlify": "*"
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user