Add back "invokeTestMethod" function to allow control over calling the test method. ()

* Add back "invokeTestMethod" function to allow custom logic before and after tests.

* Improve TestAnnotationLocator.validateMethod
This commit is contained in:
modmuss 2025-03-06 10:54:10 +00:00 committed by GitHub
parent 4e7a6c5738
commit 81c4afbc1a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 110 additions and 7 deletions
fabric-gametest-api-v1/src
main/java/net/fabricmc/fabric
testmod
java/net/fabricmc/fabric/test/gametest
resources

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.gametest.v1;
import java.lang.reflect.Method;
import net.minecraft.test.TestContext;
/**
* Implement this interface on test suites to provide custom logic for invoking {@link GameTest} test methods.
*/
public interface CustomTestMethodInvoker {
/**
* Implement this method to provide custom logic used to invoke the test method.
* This can be used to run code before or after each test.
* You can also pass in custom parameters into the test method if desired.
* The structure will have been placed in the world before this method is invoked.
*
* @param context The vanilla test context
* @param method The test method to invoke
*/
void invokeTestMethod(TestContext context, Method method) throws ReflectiveOperationException;
}

View file

@ -37,6 +37,7 @@ import net.minecraft.test.TestEnvironmentDefinition;
import net.minecraft.test.TestInstance;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.gametest.v1.CustomTestMethodInvoker;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.entrypoint.EntrypointContainer;
@ -72,7 +73,7 @@ final class TestAnnotationLocator {
findMagicMethods(entrypoint, testClass, methods);
if (methods.isEmpty()) {
LOGGER.warn("No methods with the FabricGameTest annotation were found in {}", testClass.getName());
LOGGER.warn("No methods with the GameTest annotation were found in {}", testClass.getName());
}
return methods;
@ -82,7 +83,11 @@ final class TestAnnotationLocator {
private void findMagicMethods(EntrypointContainer<Object> entrypoint, Class<?> testClass, List<TestMethod> methods) {
for (Method method : testClass.getDeclaredMethods()) {
if (method.isAnnotationPresent(GameTest.class)) {
validateMethod(method);
if (!CustomTestMethodInvoker.class.isAssignableFrom(testClass)) {
// Only validate the test method when using the default reflection invoker
validateMethod(method);
}
methods.add(new TestMethod(method, method.getAnnotation(GameTest.class), entrypoint));
}
}
@ -93,21 +98,30 @@ final class TestAnnotationLocator {
}
private void validateMethod(Method method) {
List<String> issues = new ArrayList<>();
if (method.getParameterCount() != 1 || method.getParameterTypes()[0] != TestContext.class) {
throw new UnsupportedOperationException("Method %s must have a single parameter of type TestContext".formatted(method.getName()));
issues.add("must have a single parameter of type TestContext");
}
if (!Modifier.isPublic(method.getModifiers())) {
throw new UnsupportedOperationException("Method %s must be public".formatted(method.getName()));
issues.add("must be public");
}
if (Modifier.isStatic(method.getModifiers())) {
throw new UnsupportedOperationException("Method %s must not be static".formatted(method.getName()));
issues.add("must not be static");
}
if (method.getReturnType() != void.class) {
throw new UnsupportedOperationException("Method %s must return void".formatted(method.getName()));
issues.add("must return void");
}
if (issues.isEmpty()) {
return;
}
String methodName = method.getDeclaringClass().getName() + "#" + method.getName();
throw new UnsupportedOperationException("Test method (%s) has the following issues: %s".formatted(methodName, String.join(", ", issues)));
}
public record TestMethod(Method method, GameTest gameTest, EntrypointContainer<Object> entrypoint) {
@ -118,8 +132,15 @@ final class TestAnnotationLocator {
Consumer<TestContext> testFunction() {
return context -> {
Object instance = entrypoint.getEntrypoint();
try {
method.invoke(entrypoint.getEntrypoint(), context);
if (instance instanceof CustomTestMethodInvoker customTestMethodInvoker) {
customTestMethodInvoker.invokeTestMethod(context, method);
return;
}
method.invoke(instance, context);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to invoke test method", e);
}

View file

@ -0,0 +1,44 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.gametest;
import java.lang.reflect.Method;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
import net.minecraft.test.TestContext;
import net.minecraft.text.Text;
import net.minecraft.util.math.BlockPos;
import net.fabricmc.fabric.api.gametest.v1.CustomTestMethodInvoker;
import net.fabricmc.fabric.api.gametest.v1.GameTest;
public class CustomTestInvokerTest implements CustomTestMethodInvoker {
@Override
public void invokeTestMethod(TestContext context, Method method) throws ReflectiveOperationException {
context.setBlockState(0, 1, 0, Blocks.DIAMOND_BLOCK);
method.invoke(this, context, Blocks.DIAMOND_BLOCK);
}
@GameTest
public void testCustomInvoker(TestContext context, Block testBlock) {
context.addInstantFinalTask(() ->
context.checkBlock(new BlockPos(0, 1, 0), (block) -> block == testBlock, (b) -> Text.literal("Expect block to be diamond"))
);
}
}

View file

@ -7,6 +7,7 @@
"license": "Apache-2.0",
"entrypoints": {
"fabric-gametest" : [
"net.fabricmc.fabric.test.gametest.CustomTestInvokerTest",
"net.fabricmc.fabric.test.gametest.ExampleFabricTestSuite"
]
}