diff --git a/src/main/java/net/minecraftforge/fml/common/Loader.java b/src/main/java/net/minecraftforge/fml/common/Loader.java index b6cd20ca0c..982c38973c 100644 --- a/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -204,7 +204,7 @@ public class Loader } modClassLoader = new ModClassLoader(getClass().getClassLoader()); - if (!mccversion.equals(MC_VERSION)) + if (mccversion !=null && !mccversion.equals(MC_VERSION)) { FMLLog.severe("This version of FML is built for Minecraft %s, we have detected Minecraft %s in your minecraft jar file", mccversion, MC_VERSION); throw new LoaderException(String.format("This version of FML is built for Minecraft %s, we have detected Minecraft %s in your minecraft jar file", mccversion, MC_VERSION)); @@ -1023,7 +1023,10 @@ public class Loader public void fireRemapEvent(Map remapBlocks, Map remapItems, boolean isFreezing) { - modController.propogateStateMessage(new FMLModIdMappingEvent(remapBlocks, remapItems, isFreezing)); + if (modController!=null) + { + modController.propogateStateMessage(new FMLModIdMappingEvent(remapBlocks, remapItems, isFreezing)); + } } public void runtimeDisableMod(String modId) diff --git a/src/main/java/net/minecraftforge/fml/common/ModClassLoader.java b/src/main/java/net/minecraftforge/fml/common/ModClassLoader.java index d11135bb64..ea234836d1 100644 --- a/src/main/java/net/minecraftforge/fml/common/ModClassLoader.java +++ b/src/main/java/net/minecraftforge/fml/common/ModClassLoader.java @@ -55,7 +55,10 @@ public class ModClassLoader extends URLClassLoader public ModClassLoader(ClassLoader parent) { super(new URL[0], null); - this.mainClassLoader = (LaunchClassLoader)parent; + if (parent instanceof LaunchClassLoader) + { + this.mainClassLoader = (LaunchClassLoader)parent; + } this.sources = Lists.newArrayList(); } diff --git a/src/main/java/net/minecraftforge/fml/common/registry/PersistentRegistryManager.java b/src/main/java/net/minecraftforge/fml/common/registry/PersistentRegistryManager.java index da64ad9869..1f5670a187 100644 --- a/src/main/java/net/minecraftforge/fml/common/registry/PersistentRegistryManager.java +++ b/src/main/java/net/minecraftforge/fml/common/registry/PersistentRegistryManager.java @@ -61,7 +61,7 @@ import com.google.common.collect.Sets.SetView; @SuppressWarnings("WeakerAccess") public class PersistentRegistryManager { - private enum PersistentRegistry + enum PersistentRegistry { ACTIVE, VANILLA, FROZEN, STAGING; diff --git a/src/main/java/net/minecraftforge/fml/relauncher/FMLRelaunchLog.java b/src/main/java/net/minecraftforge/fml/relauncher/FMLRelaunchLog.java index 1c206f00b8..41691d54ae 100644 --- a/src/main/java/net/minecraftforge/fml/relauncher/FMLRelaunchLog.java +++ b/src/main/java/net/minecraftforge/fml/relauncher/FMLRelaunchLog.java @@ -54,6 +54,8 @@ public class FMLRelaunchLog { private static void configureLogging() { log.myLog = LogManager.getLogger("FML"); + // Default side to client for test harness purposes + if (side == null) side = Side.CLIENT; ThreadContext.put("side", side.name().toLowerCase(Locale.ENGLISH)); configured = true; diff --git a/src/test/java/net/minecraftforge/fml/common/registry/ForgeTestRunner.java b/src/test/java/net/minecraftforge/fml/common/registry/ForgeTestRunner.java new file mode 100644 index 0000000000..cc4fbb053f --- /dev/null +++ b/src/test/java/net/minecraftforge/fml/common/registry/ForgeTestRunner.java @@ -0,0 +1,146 @@ +package net.minecraftforge.fml.common.registry; + +import java.net.URLClassLoader; +import java.util.Collections; +import java.util.Set; + +import com.google.common.collect.Sets; +import org.junit.runner.Description; +import org.junit.runner.Runner; +import org.junit.runner.notification.Failure; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.JUnit4; +import org.junit.runners.model.InitializationError; + +/** + * Uses {@code ResettingClassLoader} to load the test class, meaning the + * {@code Quarantine} annotation can be used to ensure certain classes are + * loaded separately. + * + * Use of a separate class loader allows classes to be reloaded for each test + * class, which is handy when you're testing frameworks that make use of static + * members. + * + * The selective quarantining is required because if the test class and its + * 'children' are all loaded by a different class loader, then the {@code Test} + * annotations yield different {@code Class} instances. JUnit then thinks there + * are no runnable methods, because it looks them up by Class. + * + * This is a simplified copy of https://github.com/BinaryTweed/quarantining-test-runner + * tailored for Minecraft use. + * + */ +public class ForgeTestRunner extends Runner +{ + private final Object innerRunner; + private final Class innerRunnerClass; + + public ForgeTestRunner(Class testFileClass) throws InitializationError + { + Class delegateRunningTo = JUnit4.class; + + String testFileClassName = testFileClass.getName(); + String delegateRunningToClassName = delegateRunningTo.getName(); + + String[] allPatterns = new String[] {testFileClassName, delegateRunningToClassName}; + + ResettingClassLoader classLoader = new ResettingClassLoader(allPatterns); + + try + { + innerRunnerClass = classLoader.loadClass(delegateRunningToClassName); + Class testClass = classLoader.loadClass(testFileClassName); + innerRunner = innerRunnerClass.cast(innerRunnerClass.getConstructor(Class.class).newInstance(testClass)); + } + catch (Exception e) + { + throw new InitializationError(e); + } + } + + + @Override + public Description getDescription() + { + try + { + return (Description) innerRunnerClass.getMethod("getDescription").invoke(innerRunner); + } + catch (Exception e) + { + throw new RuntimeException("Could not get description", e); + } + } + + @Override + public void run(RunNotifier notifier) + { + try + { + innerRunnerClass.getMethod("run", RunNotifier.class).invoke(innerRunner, notifier); + } + catch (Exception e) + { + notifier.fireTestFailure(new Failure(getDescription(), e)); + } + } + + /** + * If a class name starts with any of the supplied patterns, it is loaded by + * this classloader; otherwise it is loaded by the parent classloader. + * + */ + private class ResettingClassLoader extends URLClassLoader + { + private final Set quarantinedClassNames; + + /** + * + * @param quarantinedClassNames prefixes to match against when deciding how to load a class + */ + public ResettingClassLoader(String... quarantinedClassNames) + { + super(((URLClassLoader) getSystemClassLoader()).getURLs()); + + this.quarantinedClassNames = Sets.newHashSet(); + Collections.addAll(this.quarantinedClassNames, quarantinedClassNames); + Collections.addAll(this.quarantinedClassNames, "net.minecraft", "net.minecraftforge"); + } + + + /** + * If a class name starts with any of the supplied patterns, it is loaded by + * this classloader; otherwise it is loaded by the parent classloader. + * + * @param name class to load + */ + @Override + public Class loadClass(String name) throws ClassNotFoundException + { + boolean quarantine = false; + + for(String quarantinedPattern : quarantinedClassNames) + { + if(name.startsWith(quarantinedPattern)) + { + quarantine = true; + break; + } + } + + if(quarantine) + { + try + { + return findClass(name); + } + catch (ClassNotFoundException e) + { + throw e; + } + } + + return super.loadClass(name); + } + } +} diff --git a/src/test/java/net/minecraftforge/fml/common/registry/FreezingTests.java b/src/test/java/net/minecraftforge/fml/common/registry/FreezingTests.java new file mode 100644 index 0000000000..6cdd8101cf --- /dev/null +++ b/src/test/java/net/minecraftforge/fml/common/registry/FreezingTests.java @@ -0,0 +1,105 @@ +package net.minecraftforge.fml.common.registry; + +import net.minecraft.block.Block; +import net.minecraft.item.Item; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.Loader; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Created by cpw on 04/07/16. + */ +@RunWith(ForgeTestRunner.class) +public class FreezingTests +{ + + private static RTest r1; + private static RTest r2; + private static RTest r3; + private static RTest r4; + private static RTest r5; + private static RTest r6; + private static PersistentRegistryManager.GameDataSnapshot ss; + + static class RTest extends IForgeRegistryEntry.Impl { + public RTest(String name) { + setRegistryName(name); + } + } + + public static IForgeRegistry registry; + public static ResourceLocation resloc = new ResourceLocation("fmltest:test"); + + @BeforeClass + public static void setup() + { + Loader.instance(); + System.setProperty("fml.queryResult", "confirm"); + registry = PersistentRegistryManager.createRegistry(resloc, RTest.class, null, 0, 255, false, null, null, null); + PersistentRegistryManager.createRegistry(PersistentRegistryManager.BLOCKS, Block.class, null, 0, 255, false, null, null, null); + PersistentRegistryManager.createRegistry(PersistentRegistryManager.ITEMS, Item.class, null, 0, 255, false, null, null, null); + r1 = new RTest("test1"); + r2 = new RTest("test2"); + r3 = new RTest("test3"); + r4 = new RTest("test4"); + r5 = new RTest("test5"); + r6 = new RTest("test6"); + ss = new PersistentRegistryManager.GameDataSnapshot(); + ss.entries.put(PersistentRegistryManager.BLOCKS, new PersistentRegistryManager.GameDataSnapshot.Entry()); + ss.entries.put(PersistentRegistryManager.ITEMS, new PersistentRegistryManager.GameDataSnapshot.Entry()); + PersistentRegistryManager.findRegistryByType(RTest.class).register(r1); + PersistentRegistryManager.findRegistryByType(RTest.class).register(r2); + PersistentRegistryManager.findRegistryByType(RTest.class).register(r3); + PersistentRegistryManager.findRegistryByType(RTest.class).register(r4); + ss.entries.put(resloc, new PersistentRegistryManager.GameDataSnapshot.Entry(PersistentRegistryManager.PersistentRegistry.ACTIVE.getRegistry(RTest.class))); + PersistentRegistryManager.PersistentRegistry.ACTIVE.clean(); + PersistentRegistryManager.PersistentRegistry.FROZEN.clean(); + registry = PersistentRegistryManager.createRegistry(resloc, RTest.class, null, 0, 255, false, null, null, null); + PersistentRegistryManager.createRegistry(PersistentRegistryManager.BLOCKS, Block.class, null, 0, 255, false, null, null, null); + PersistentRegistryManager.createRegistry(PersistentRegistryManager.ITEMS, Item.class, null, 0, 255, false, null, null, null); + } + + @Test + public void testFreezeCycle() + { + PersistentRegistryManager.findRegistryByType(RTest.class).register(r6); + PersistentRegistryManager.findRegistryByType(RTest.class).register(r5); + PersistentRegistryManager.findRegistryByType(RTest.class).register(r4); + PersistentRegistryManager.findRegistryByType(RTest.class).register(r3); + FMLControlledNamespacedRegistry r = (FMLControlledNamespacedRegistry)PersistentRegistryManager.findRegistry(r3); + int r3id = r.getId(r3); + PersistentRegistryManager.freezeData(); + RTest q1 = PersistentRegistryManager.PersistentRegistry.FROZEN.getRegistry(RTest.class).getValue(new ResourceLocation("test3")); + assertEquals("Frozen object is the same", r3, q1); + q1 = PersistentRegistryManager.PersistentRegistry.ACTIVE.getRegistry(RTest.class).getValue(new ResourceLocation("test3")); + assertEquals("Active object is the same", r3, q1); + PersistentRegistryManager.injectSnapshot(ss, false, false); + assertNotEquals("IDs don't match", r3id, r.getId(r3)); + q1 = PersistentRegistryManager.PersistentRegistry.FROZEN.getRegistry(RTest.class).getValue(new ResourceLocation("test3")); + assertEquals("Frozen object is the same", r3, q1); + q1 = PersistentRegistryManager.PersistentRegistry.ACTIVE.getRegistry(RTest.class).getValue(new ResourceLocation("test3")); + assertEquals("Active object is the same", r3, q1); + PersistentRegistryManager.revertToFrozen(); + assertEquals("IDs match", r3id, r.getId(r3)); + q1 = PersistentRegistryManager.PersistentRegistry.FROZEN.getRegistry(RTest.class).getValue(new ResourceLocation("test3")); + assertEquals("Frozen object is the same", r3, q1); + q1 = PersistentRegistryManager.PersistentRegistry.ACTIVE.getRegistry(RTest.class).getValue(new ResourceLocation("test3")); + assertEquals("Active object is the same", r3, q1); + PersistentRegistryManager.injectSnapshot(ss, true, true); + assertNotEquals("IDs don't match", r3id, r.getId(r3)); + q1 = PersistentRegistryManager.PersistentRegistry.FROZEN.getRegistry(RTest.class).getValue(new ResourceLocation("test3")); + assertEquals("Frozen object is the same", r3, q1); + q1 = PersistentRegistryManager.PersistentRegistry.ACTIVE.getRegistry(RTest.class).getValue(new ResourceLocation("test3")); + assertEquals("Active object is the same", r3, q1); + PersistentRegistryManager.revertToFrozen(); + assertEquals("IDs match", r3id, r.getId(r3)); + q1 = PersistentRegistryManager.PersistentRegistry.FROZEN.getRegistry(RTest.class).getValue(new ResourceLocation("test3")); + assertEquals("Frozen object is the same", r3, q1); + q1 = PersistentRegistryManager.PersistentRegistry.ACTIVE.getRegistry(RTest.class).getValue(new ResourceLocation("test3")); + assertEquals("Active object is the same", r3, q1); + } +} diff --git a/src/test/java/net/minecraftforge/fml/common/registry/VanillaRegistryTests.java b/src/test/java/net/minecraftforge/fml/common/registry/VanillaRegistryTests.java new file mode 100644 index 0000000000..c2edccf8df --- /dev/null +++ b/src/test/java/net/minecraftforge/fml/common/registry/VanillaRegistryTests.java @@ -0,0 +1,99 @@ +package net.minecraftforge.fml.common.registry; + +import net.minecraft.block.Block; +import net.minecraft.block.material.Material; +import net.minecraft.init.Blocks; +import net.minecraft.init.Bootstrap; +import net.minecraft.init.Items; +import net.minecraft.item.Item; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.common.Loader; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Vanilla registry tests + */ +@RunWith(ForgeTestRunner.class) +public class VanillaRegistryTests +{ + @BeforeClass + public static void setupHarness() + { + Loader.instance(); + Bootstrap.register(); + } + + @Test + public void testSetup() + { + // All the blocks loaded + assertEquals("We have all the blocks via GameData",219,Block.REGISTRY.getKeys().size()); + + // All the items loaded + assertEquals("We have all the items via GameData",371,Item.REGISTRY.getKeys().size()); + + // Our lookups find the same stuff vanilla sees + final IForgeRegistry blocks = PersistentRegistryManager.findRegistry(Blocks.AIR); + assertEquals("We have the right blocks for a block", blocks, Block.REGISTRY); + + // We can look up stuff through our APIs + Block bl = blocks.getValue(new ResourceLocation("minecraft:air")); + assertEquals("We got air when we asked for it", Blocks.AIR, bl); + + // Default values work + Block blch = blocks.getValue(new ResourceLocation("minecraft:cheese")); + assertEquals("We got air when we asked for cheese", Blocks.AIR, blch); + + // Our lookups find the same stuff vanilla sees + final IForgeRegistry items = PersistentRegistryManager.findRegistry(Items.BED); + assertEquals("We have the right items for an item", items, Item.REGISTRY); + + // We can look up stuff through our APIs + Item it = items.getValue(new ResourceLocation("minecraft:bed")); + assertEquals("We got a bed item when we asked for it", Items.BED, it); + + // We find nothing for a non-defaulted registry + Item none = items.getValue(new ResourceLocation("minecraft:cheese")); + assertEquals("We got nothing (items) when we asked for cheese", null, none); + } + + @Test + public void testRegistration() + { + Block myBlock = GameRegistry.register(new Block(Material.CAKE) {}, new ResourceLocation("minecraft:testy")); + assertNotNull("Registered my block", myBlock); + + // Our lookups find the same stuff vanilla sees + final IForgeRegistry blocks = PersistentRegistryManager.findRegistry(myBlock); + assertEquals("We have the right blocks for a block", blocks, Block.REGISTRY); + + Block found = blocks.getValue(new ResourceLocation("minecraft:testy")); + assertEquals("Registry lookup works", myBlock, found); + } + + @Test + public void testRegistryStates() + { + final FMLControlledNamespacedRegistry blockVanilla = PersistentRegistryManager.PersistentRegistry.VANILLA.getRegistry(Block.class); + final FMLControlledNamespacedRegistry blockActive = PersistentRegistryManager.PersistentRegistry.ACTIVE.getRegistry(Block.class); + + assertNotEquals("Registry states are distinct", blockActive, blockVanilla); + + final Block stoneActive = blockActive.getValue(new ResourceLocation("minecraft:stone")); + final Block stoneVanilla = blockVanilla.getValue(new ResourceLocation("minecraft:stone")); + + assertEquals("Stone from active and vanilla are the same", stoneActive, stoneVanilla); + + int activeId = blockActive.getId(stoneActive); + int vanillaId = blockVanilla.getId(stoneVanilla); + + assertEquals("Stone has correct id", 1, activeId); + assertEquals("Stone has correct id", 1, vanillaId); + } + + +}