CC-Tweaked/src/test/java/dan200/computercraft/ingame/mod/TestLoader.java

120 lines
4.6 KiB
Java

package dan200.computercraft.ingame.mod;
import com.google.common.base.CaseFormat;
import dan200.computercraft.ingame.api.GameTest;
import net.minecraft.test.TestFunctionInfo;
import net.minecraft.test.TestRegistry;
import net.minecraft.test.TestTrackerHolder;
import net.minecraftforge.fml.ModList;
import net.minecraftforge.fml.common.ObfuscationReflectionHelper;
import net.minecraftforge.forgespi.language.ModFileScanData;
import org.objectweb.asm.Type;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.Collection;
import java.util.Set;
import java.util.function.Consumer;
/**
* Loads methods annotated with {@link GameTest} and adds them to the {@link TestRegistry}. This involves some horrible
* reflection hacks, as Proguard makes many methods (and constructors) private.
*/
class TestLoader
{
private static final Type gameTest = Type.getType( GameTest.class );
private static final Collection<TestFunctionInfo> testFunctions = ObfuscationReflectionHelper.getPrivateValue( TestRegistry.class, null, "field_229526_a_" );
private static final Set<String> testClassNames = ObfuscationReflectionHelper.getPrivateValue( TestRegistry.class, null, "field_229527_b_" );
public static void setup()
{
ModList.get().getAllScanData().stream()
.flatMap( x -> x.getAnnotations().stream() )
.filter( x -> x.getAnnotationType().equals( gameTest ) )
.forEach( TestLoader::loadTest );
}
private static void loadTest( ModFileScanData.AnnotationData annotation )
{
Class<?> klass;
Method method;
try
{
klass = TestLoader.class.getClassLoader().loadClass( annotation.getClassType().getClassName() );
// We don't know the exact signature (could suspend or not), so find something with the correct descriptor instead.
String methodName = annotation.getMemberName();
method = Arrays.stream( klass.getMethods() ).filter( x -> (x.getName() + Type.getMethodDescriptor( x )).equals( methodName ) ).findFirst()
.orElseThrow( () -> new NoSuchMethodException( "No method " + annotation.getClassType().getClassName() + "." + annotation.getMemberName() ) );
}
catch( ReflectiveOperationException e )
{
throw new RuntimeException( e );
}
String className = CaseFormat.UPPER_CAMEL.to( CaseFormat.LOWER_UNDERSCORE, klass.getSimpleName() );
String name = className + "." + method.getName().toLowerCase().replace( ' ', '_' );
GameTest test = method.getAnnotation( GameTest.class );
TestMod.log.info( "Adding test " + name );
testClassNames.add( className );
testFunctions.add( createTestFunction(
test.batch(), name, name,
test.required(),
new TestRunner( name, method ),
test.timeout(),
test.setup()
) );
}
private static TestFunctionInfo createTestFunction(
String batchName,
String testName,
String structureName,
boolean required,
Consumer<TestTrackerHolder> function,
int maxTicks,
long setupTicks
)
{
try
{
Constructor<TestFunctionInfo> ctor = TestFunctionInfo.class.getDeclaredConstructor();
ctor.setAccessible( true );
TestFunctionInfo func = ctor.newInstance();
setFinalField( func, "batchName", batchName );
setFinalField( func, "testName", testName );
setFinalField( func, "structureName", structureName );
setFinalField( func, "required", required );
setFinalField( func, "function", function );
setFinalField( func, "maxTicks", maxTicks );
setFinalField( func, "setupTicks", setupTicks );
return func;
}
catch( ReflectiveOperationException e )
{
throw new RuntimeException( e );
}
}
private static void setFinalField( TestFunctionInfo func, String name, Object value ) throws ReflectiveOperationException
{
Field field = TestFunctionInfo.class.getDeclaredField( name );
if( (field.getModifiers() & Modifier.FINAL) != 0 )
{
Field modifiers = Field.class.getDeclaredField( "modifiers" );
modifiers.setAccessible( true );
modifiers.set( field, field.getModifiers() & ~Modifier.FINAL );
}
field.setAccessible( true );
field.set( func, value );
}
}