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
|
||||
with:
|
||||
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")
|
||||
}
|
||||
|
||||
val testmod by sourceSets.registering {
|
||||
compileClasspath += sourceSets.main.get().compileClasspath
|
||||
runtimeClasspath += sourceSets.main.get().runtimeClasspath
|
||||
}
|
||||
|
||||
loom {
|
||||
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()
|
||||
@ -73,6 +91,9 @@ dependencies {
|
||||
modImplementation(files("libs/iris-5d0efad3.jar"))
|
||||
modRuntimeOnly("org.anarres:jcpp:1.4.14")
|
||||
modRuntimeOnly("io.github.douira:glsl-transformer:2.0.0-pre9")
|
||||
|
||||
// testmod
|
||||
"testmodImplementation"(sourceSets.main.get().output)
|
||||
}
|
||||
|
||||
tasks {
|
||||
|
@ -36,7 +36,7 @@ public class Controlify implements ControlifyApi {
|
||||
private InGameInputHandler inGameInputHandler;
|
||||
public InGameButtonGuide inGameButtonGuide;
|
||||
private VirtualMouseHandler virtualMouseHandler;
|
||||
private InputMode currentInputMode;
|
||||
private InputMode currentInputMode = InputMode.KEYBOARD_MOUSE;
|
||||
private ControllerHIDService controllerHIDService;
|
||||
|
||||
private final ControlifyConfig config = new ControlifyConfig(this);
|
||||
@ -290,9 +290,4 @@ public class Controlify implements ControlifyApi {
|
||||
if (instance == null) instance = new Controlify();
|
||||
return instance;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NotNull ControlifyBindingsApi bindingsApi() {
|
||||
return ControllerBindings.Api.INSTANCE;
|
||||
}
|
||||
}
|
||||
|
@ -19,8 +19,6 @@ public interface ControlifyApi {
|
||||
@NotNull InputMode currentInputMode();
|
||||
void setInputMode(@NotNull InputMode mode);
|
||||
|
||||
@NotNull ControlifyBindingsApi bindingsApi();
|
||||
|
||||
static ControlifyApi get() {
|
||||
return Controlify.instance();
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package dev.isxander.controlify.api.bind;
|
||||
|
||||
import dev.isxander.controlify.bindings.BindingSupplier;
|
||||
import dev.isxander.controlify.bindings.ControllerBindings;
|
||||
import dev.isxander.controlify.bindings.GamepadBinds;
|
||||
import net.minecraft.client.KeyMapping;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
@ -29,4 +30,8 @@ public interface ControlifyBindingsApi {
|
||||
* @return the binding supplier to fetch the binding for a specific controller.
|
||||
*/
|
||||
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