diff --git a/build.gradle b/build.gradle index 1a16a7e34..8f891094b 100644 --- a/build.gradle +++ b/build.gradle @@ -66,8 +66,10 @@ version = getVersionFromJava(file("src/main/java/net/minecraftforge/common/Forge extractForgeSources { exclude "**/SideOnly.java", "**/Side.java" } extractForgeResources { exclude "**/log4j2.xml" } -genGradleProjects { - addTestCompileDep "junit:junit:4.12" +genGradleProjects { + addTestCompileDep "junit:junit:4.12" // TODO update unit tests to junit 5 and remove this + addTestCompileDep "org.junit.jupiter:junit-jupiter-api:5.0.0" + addTestCompileDep "org.opentest4j:opentest4j:1.0.0" // needed for junit 5 addTestCompileDep "org.hamcrest:hamcrest-core:1.3" filter { dep -> !dep.contains("scala") } } diff --git a/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java b/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java index d63e4b64e..3cc51e84d 100644 --- a/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java +++ b/src/main/java/net/minecraftforge/fml/common/FMLModContainer.java @@ -51,6 +51,7 @@ import net.minecraftforge.fml.common.event.FMLFingerprintViolationEvent; import net.minecraftforge.fml.common.network.NetworkRegistry; import net.minecraftforge.fml.common.versioning.ArtifactVersion; import net.minecraftforge.fml.common.versioning.DefaultArtifactVersion; +import net.minecraftforge.fml.common.versioning.DependencyParser; import net.minecraftforge.fml.common.versioning.VersionParser; import net.minecraftforge.fml.common.versioning.VersionRange; import net.minecraftforge.fml.relauncher.Side; @@ -67,14 +68,11 @@ import java.util.function.Function; import com.google.common.base.Strings; import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableList.Builder; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ListMultimap; -import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.SetMultimap; -import com.google.common.collect.Sets; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; @@ -140,19 +138,18 @@ public class FMLModContainer implements ModContainer String modid = (String)this.descriptor.get("modid"); if (Strings.isNullOrEmpty(modid)) { - throw new IllegalArgumentException("Modid cannot be null or empty"); + throw new IllegalArgumentException("The modId is null or empty"); } if (modid.length() > 64) { - FMLLog.bigWarning("The modid {} is longer than the recommended maximum of 64 characters. Truncation is enforced in 1.11", modid); - throw new IllegalArgumentException(String.format("The modid %s is longer than the recommended maximum of 64 characters. Truncation is enforced in 1.11", modid)); + throw new IllegalArgumentException(String.format("The modId %s is longer than the maximum of 64 characters.", modid)); } if (!modid.equals(modid.toLowerCase(Locale.ENGLISH))) { - FMLLog.bigWarning("The modid {} is not the same as it's lowercase version. Lowercasing is enforced in 1.11", modid); - throw new IllegalArgumentException(String.format("The modid %s is not the same as it's lowercase version. Lowercasing will be enforced in 1.11", modid)); + throw new IllegalArgumentException(String.format("The modId %s must be all lowercase.", modid)); } } + private ILanguageAdapter getLanguageAdapter() { if (languageAdapter == null) @@ -212,17 +209,15 @@ public class FMLModContainer implements ModContainer if (overridesMetadata || !modMetadata.useDependencyInformation) { - Set requirements = Sets.newHashSet(); - List dependencies = Lists.newArrayList(); - List dependants = Lists.newArrayList(); annotationDependencies = (String)descriptor.get("dependencies"); - Loader.instance().computeDependencies(annotationDependencies, requirements, dependencies, dependants); - dependants.addAll(Loader.instance().getInjectedBefore(getModId())); - dependencies.addAll(Loader.instance().getInjectedAfter(getModId())); - modMetadata.requiredMods = requirements; - modMetadata.dependencies = dependencies; - modMetadata.dependants = dependants; - modLog.trace("Parsed dependency info : {} {} {}", requirements, dependencies, dependants); + DependencyParser dependencyParser = new DependencyParser(getModId(), FMLCommonHandler.instance().getSide()); + DependencyParser.DependencyInfo info = dependencyParser.parseDependencies(annotationDependencies); + info.dependants.addAll(Loader.instance().getInjectedBefore(getModId())); + info.dependencies.addAll(Loader.instance().getInjectedAfter(getModId())); + modMetadata.requiredMods = info.requirements; + modMetadata.dependencies = info.dependencies; + modMetadata.dependants = info.dependants; + modLog.trace("Parsed dependency info : Requirements: {} After:{} Before:{}", info.requirements, info.dependencies, info.dependants); } else { diff --git a/src/main/java/net/minecraftforge/fml/common/Loader.java b/src/main/java/net/minecraftforge/fml/common/Loader.java index 5646f8b36..d38e7c1d5 100644 --- a/src/main/java/net/minecraftforge/fml/common/Loader.java +++ b/src/main/java/net/minecraftforge/fml/common/Loader.java @@ -26,7 +26,6 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -35,6 +34,7 @@ import java.util.Properties; import java.util.Set; import net.minecraft.util.ResourceLocation; +import net.minecraftforge.common.ForgeVersion; import net.minecraftforge.common.capabilities.CapabilityManager; import net.minecraftforge.common.config.ConfigManager; import net.minecraftforge.common.crafting.CraftingHelper; @@ -52,6 +52,7 @@ import net.minecraftforge.fml.common.toposort.ModSortingException; import net.minecraftforge.fml.common.toposort.TopologicalSort; import net.minecraftforge.fml.common.toposort.ModSortingException.SortingExceptionData; import net.minecraftforge.fml.common.versioning.ArtifactVersion; +import net.minecraftforge.fml.common.versioning.DependencyParser; import net.minecraftforge.fml.common.versioning.VersionParser; import net.minecraftforge.fml.relauncher.ModListHelper; import net.minecraftforge.fml.relauncher.Side; @@ -62,7 +63,6 @@ import org.apache.commons.io.IOUtils; import org.apache.logging.log4j.Level; import com.google.common.base.CharMatcher; -import java.util.function.Function; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.ArrayListMultimap; @@ -124,9 +124,7 @@ import javax.annotation.Nullable; @SuppressWarnings("unused") public class Loader { - public static final String MC_VERSION = net.minecraftforge.common.ForgeVersion.mcVersion; - private static final Splitter DEPENDENCYPARTSPLITTER = Splitter.on(":").omitEmptyStrings().trimResults(); - private static final Splitter DEPENDENCYSPLITTER = Splitter.on(";").omitEmptyStrings().trimResults(); + public static final String MC_VERSION = ForgeVersion.mcVersion; /** * The singleton instance */ @@ -695,77 +693,17 @@ public class Loader return modClassLoader; } + /** + * @deprecated use {@link DependencyParser#parseDependencies(String)} + */ + @Deprecated // TODO: remove in 1.13 public void computeDependencies(String dependencyString, Set requirements, List dependencies, List dependants) { - if (dependencyString == null || dependencyString.length() == 0) - { - return; - } - - boolean parseFailure = false; - - for (String dep : DEPENDENCYSPLITTER.split(dependencyString)) - { - List depparts = Lists.newArrayList(DEPENDENCYPARTSPLITTER.split(dep)); - // Need two parts to the string - if (depparts.size() != 2) - { - parseFailure = true; - continue; - } - String instruction = depparts.get(0); - String target = depparts.get(1); - boolean targetIsAll = target.startsWith("*"); - - // Cannot have an "all" relationship with anything except pure * - if (targetIsAll && target.length() > 1) - { - parseFailure = true; - continue; - } - - // If this is a required element, add it to the required list - if ("required-before".equals(instruction) || "required-after".equals(instruction)) - { - // You can't require everything - if (!targetIsAll) - { - requirements.add(VersionParser.parseVersionReference(target)); - } - else - { - parseFailure = true; - continue; - } - } - - // You cannot have a versioned dependency on everything - if (targetIsAll && target.indexOf('@') > -1) - { - parseFailure = true; - continue; - } - // before elements are things we are loaded before (so they are our dependants) - if ("required-before".equals(instruction) || "before".equals(instruction)) - { - dependants.add(VersionParser.parseVersionReference(target)); - } - // after elements are things that load before we do (so they are out dependencies) - else if ("required-after".equals(instruction) || "after".equals(instruction)) - { - dependencies.add(VersionParser.parseVersionReference(target)); - } - else - { - parseFailure = true; - } - } - - if (parseFailure) - { - FMLLog.log.warn("Unable to parse dependency string {}", dependencyString); - throw new LoaderException(String.format("Unable to parse dependency string %s", dependencyString)); - } + DependencyParser dependencyParser = new DependencyParser("unknown", FMLCommonHandler.instance().getSide()); + DependencyParser.DependencyInfo info = dependencyParser.parseDependencies(dependencyString); + requirements.addAll(info.requirements); + dependencies.addAll(info.dependencies); + dependants.addAll(info.dependants); } public Map getIndexedModList() diff --git a/src/main/java/net/minecraftforge/fml/common/Mod.java b/src/main/java/net/minecraftforge/fml/common/Mod.java index 7ff9bd665..97608a5d1 100644 --- a/src/main/java/net/minecraftforge/fml/common/Mod.java +++ b/src/main/java/net/minecraftforge/fml/common/Mod.java @@ -101,24 +101,37 @@ public @interface Mod /** * A dependency string for this mod, which specifies which mod(s) it depends on in order to run. * - * A dependency string can start with the following four prefixes: - * before, after, required-before, required-after + * A dependency string must start with a combination of these prefixes, separated by "-": + * [before, after], [required], [client, server] + * At least one "before", "after", or "required" must be specified. * Then ":" and the mod id. - * - * Optionally, a version range can be specified for the mod by adding "@" and then the version range. - * The version range format is described in the javadoc here: {@link VersionRange#createFromVersionSpec(java.lang.String)} + * Then a version range should be specified for the mod by adding "@" and the version range. + * The version range format is described in the javadoc here: + * {@link VersionRange#createFromVersionSpec(java.lang.String)} + * Then a ";". * * If a "required" mod is missing, or a mod exists with a version outside the specified range, * the game will not start and an error screen will tell the player which versions are required. * * Example: - * Our example mod is a dedicated addon to mod1 and has optional integration with mod2. - * It uses new features that were introduced in forge version 14.21.1.2395 and mod2 version 4.7.0 + * Our example mod: + * * depends on Forge and uses new features that were introduced in Forge version 14.21.1.2395 + * "required:forge@[14.21.1.2395,);" + * * is a dedicated addon to mod1 and has to have its event handlers run after mod1's are run, + * "required-after:mod1;" + * * has optional integration with mod2 which depends on features introduced in mod2 version 4.7.0, + * "after:mod2@[4.7.0,);" + * * depends on a client-side-only rendering library called rendermod + * "required-client:rendermod;" * - * the dependencies string = "after:forge@[14.21.1.2395,);required-after:mod1;after:mod2@[4.7.0,);" + * The full dependencies string is all of those combined: + * "required:forge@[14.21.1.2395,);required-after:mod1;after:mod2@[4.7.0,);required-client:rendermod;" * * This will stop the game and display an error message if any of these is true: - * The installed forge is too old, mod1 is missing, or an old version of mod2 is present. + * The installed forge is too old, + * mod1 is missing, + * an old version of mod2 is present, + * rendermod is missing on the client. */ String dependencies() default ""; diff --git a/src/main/java/net/minecraftforge/fml/common/versioning/DependencyParser.java b/src/main/java/net/minecraftforge/fml/common/versioning/DependencyParser.java new file mode 100644 index 000000000..ac34202ed --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/common/versioning/DependencyParser.java @@ -0,0 +1,221 @@ +/* + * Minecraft Forge + * Copyright (c) 2016. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation version 2.1 + * of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package net.minecraftforge.fml.common.versioning; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import com.google.common.base.Splitter; +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import net.minecraftforge.fml.common.LoaderException; +import net.minecraftforge.fml.relauncher.Side; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +public final class DependencyParser +{ + public static class DependencyInfo + { + public final Set requirements = new HashSet<>(); + public final List dependencies = new ArrayList<>(); + public final List dependants = new ArrayList<>(); + } + + private static final Logger LOGGER = LogManager.getLogger("FML"); + private static final ImmutableList DEPENDENCY_INSTRUCTIONS = ImmutableList.of("client", "server", "required", "before", "after"); + private static final Splitter DEPENDENCY_INSTRUCTIONS_SPLITTER = Splitter.on("-").omitEmptyStrings().trimResults(); + private static final Splitter DEPENDENCY_PART_SPLITTER = Splitter.on(":").omitEmptyStrings().trimResults(); + private static final Splitter DEPENDENCY_SPLITTER = Splitter.on(";").omitEmptyStrings().trimResults(); + + private final String modId; + private final Side side; + + public DependencyParser(String modId, Side side) + { + this.modId = modId; + this.side = side; + } + + public DependencyInfo parseDependencies(String dependencyString) + { + DependencyInfo info = new DependencyInfo(); + if (dependencyString == null || dependencyString.length() == 0) + { + return info; + } + + for (String dep : DEPENDENCY_SPLITTER.split(dependencyString)) + { + final List depParts = DEPENDENCY_PART_SPLITTER.splitToList(dep); + if (depParts.size() != 2) + { + throw new DependencyParserException(modId, dep, "Dependency string needs 2 parts."); + } + + final List instructions = DEPENDENCY_INSTRUCTIONS_SPLITTER.splitToList(depParts.get(0)); + final String target = depParts.get(1); + parseDependency(dep, instructions, target, info); + } + return info; + } + + private void parseDependency(String dep, List instructions, String target, DependencyInfo info) + { + final boolean targetIsAll = target.startsWith("*"); + final boolean targetIsBounded = target.contains("@"); + if (targetIsAll) + { + if (target.length() > 1) + { + throw new DependencyParserException(modId, dep, "Cannot have an \"all\" (*) relationship with anything except pure *"); + } + else if (targetIsBounded) + { + throw new DependencyParserException(modId, dep, "You cannot have a versioned dependency on everything (*)"); + } + } + + Side depSide = null; + String depOrder = null; + boolean depRequired = false; + + for (String instruction : instructions) + { + if ("client".equals(instruction)) + { + if (depSide != null) + { + throw new DependencyParserException(modId, dep, "Up to one side (client or server) can be specified."); + } + depSide = Side.CLIENT; + } + else if ("server".equals(instruction)) + { + if (depSide != null) + { + throw new DependencyParserException(modId, dep, "Up to one side (client or server) can be specified."); + } + depSide = Side.SERVER; + } + else if ("required".equals(instruction)) + { + if (depRequired) + { + throw new DependencyParserException(modId, dep, "'required' can only be specified once."); + } + if (targetIsAll) + { + throw new DependencyParserException(modId, dep, "You can't 'require' everything (*)"); + } + depRequired = true; + } + else if ("before".equals(instruction) || "after".equals(instruction)) + { + if (depOrder != null) + { + throw new DependencyParserException(modId, dep, "'before' or 'after' can only be specified once."); + } + depOrder = instruction; + } + else + { + throw new DependencyParserException(modId, dep, String.format("Found invalid instruction '%s'. Only %s are allowed.", instruction, DEPENDENCY_INSTRUCTIONS)); + } + } + + ArtifactVersion artifactVersion; + try + { + artifactVersion = VersionParser.parseVersionReference(target); + } + catch (RuntimeException e) + { + throw new DependencyParserException(modId, dep, "Could not parse version string.", e); + } + + if (!targetIsAll) + { + String depModId = artifactVersion.getLabel(); + sanityCheckModId(modId, dep, depModId); + } + + if (!depRequired && depOrder == null) + { + throw new DependencyParserException(modId, dep, "'required', 'client', or 'server' must be specified."); + } + + if (depSide == null || depSide == this.side) + { + if (depRequired) + { + info.requirements.add(artifactVersion); + } + + if ("before".equals(depOrder)) + { + info.dependants.add(artifactVersion); + } + else if ("after".equals(depOrder)) + { + info.dependencies.add(artifactVersion); + } + } + } + + // TODO 1.13: throw these exceptions instead of logging them + /** Based on {@link net.minecraftforge.fml.common.FMLModContainer#sanityCheckModId()} */ + private static void sanityCheckModId(String modId, String dep, String depModId) + { + if (Strings.isNullOrEmpty(depModId)) + { + LOGGER.error(new DependencyParserException(modId, dep, "The modId is null or empty").getMessage()); + } + else if (depModId.length() > 64) + { + LOGGER.error(new DependencyParserException(modId, dep, String.format("The modId '%s' is longer than the maximum of 64 characters.", depModId)).getMessage()); + } + else if (!depModId.equals(depModId.toLowerCase(Locale.ENGLISH))) + { + LOGGER.error(new DependencyParserException(modId, dep, String.format("The modId '%s' must be all lowercase.", depModId)).getMessage()); + } + } + + private static class DependencyParserException extends LoaderException + { + public DependencyParserException(String modId, String dependencyString, String explanation) + { + super(formatMessage(modId, dependencyString, explanation)); + } + + public DependencyParserException(String modId, String dependencyString, String explanation, Throwable cause) + { + super(formatMessage(modId, dependencyString, explanation), cause); + } + + public static String formatMessage(String modId, String dependencyString, String explanation) + { + return String.format("Unable to parse dependency for mod '%s' with dependency string '%s'. %s", modId, dependencyString, explanation); + } + } +} diff --git a/src/main/java/net/minecraftforge/fml/common/versioning/VersionParser.java b/src/main/java/net/minecraftforge/fml/common/versioning/VersionParser.java index 95a85bb75..3a6d10993 100644 --- a/src/main/java/net/minecraftforge/fml/common/versioning/VersionParser.java +++ b/src/main/java/net/minecraftforge/fml/common/versioning/VersionParser.java @@ -21,11 +21,8 @@ package net.minecraftforge.fml.common.versioning; import java.util.List; -import net.minecraftforge.fml.common.FMLLog; import net.minecraftforge.fml.common.LoaderException; -import org.apache.logging.log4j.Level; - import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.Lists; @@ -76,8 +73,7 @@ public class VersionParser } catch (InvalidVersionSpecificationException e) { - FMLLog.log.error("Unable to parse a version range specification successfully {}", range, e); - throw new LoaderException(e); + throw new LoaderException("Unable to parse a version range specification successfully " + range, e); } } } diff --git a/src/test/java/net/minecraftforge/fml/test/DependencyParserTest.java b/src/test/java/net/minecraftforge/fml/test/DependencyParserTest.java new file mode 100644 index 000000000..2beb94c3b --- /dev/null +++ b/src/test/java/net/minecraftforge/fml/test/DependencyParserTest.java @@ -0,0 +1,304 @@ +package net.minecraftforge.fml.test; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.google.common.collect.ImmutableList; +import net.minecraftforge.fml.common.versioning.DependencyParser; +import net.minecraftforge.fml.relauncher.Side; + +import com.google.common.base.Functions; +import com.google.common.collect.Collections2; +import com.google.common.collect.Sets; + +import net.minecraftforge.fml.common.LoaderException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +public class DependencyParserTest +{ + private static DependencyParser clientDependencyParser; + private static DependencyParser serverDependencyParser; + private static List parsers; + + @BeforeAll + public static void beforeAll() + { + clientDependencyParser = new DependencyParser("test", Side.CLIENT); + serverDependencyParser = new DependencyParser("test", Side.SERVER); + parsers = ImmutableList.of(clientDependencyParser, serverDependencyParser); + } + + @Test + public void testParsingNothing() + { + List strings = new ArrayList<>(); + strings.add(null); + strings.add(""); + strings.add(";;;;;;;"); + strings.add("; ; ; ; ; ; ; "); + for (String string : strings) + { + for (DependencyParser parser : parsers) + { + DependencyParser.DependencyInfo info = parser.parseDependencies(string); + assertTrue(info.requirements.isEmpty()); + assertTrue(info.dependants.isEmpty()); + assertTrue(info.dependencies.isEmpty()); + } + } + } + + @Test + public void testParsingRequired() + { + String mod = "supermod3000@[1.2,)"; + for (DependencyParser parser : parsers) + { + DependencyParser.DependencyInfo info = parser.parseDependencies("required:" + mod); + assertContainsSameToString(info.requirements, Sets.newHashSet(mod)); + assertTrue(info.dependants.isEmpty()); + assertTrue(info.dependencies.isEmpty()); + } + } + + @Test + public void testParsingBefore() + { + String mod = "supermod3000@[1.2,)"; + for (DependencyParser parser : parsers) + { + DependencyParser.DependencyInfo info = parser.parseDependencies("before:" + mod); + assertTrue(info.requirements.isEmpty()); + assertContainsSameToString(info.dependants, Sets.newHashSet(mod)); + assertTrue(info.dependencies.isEmpty()); + } + } + + @Test + public void testParsingAfter() + { + String mod = "supermod3000@[1.2,)"; + for (DependencyParser parser : parsers) + { + DependencyParser.DependencyInfo info = parser.parseDependencies("after:" + mod); + assertTrue(info.requirements.isEmpty()); + assertTrue(info.dependants.isEmpty()); + assertContainsSameToString(info.dependencies, Sets.newHashSet(mod)); + } + } + + @Test + public void testParsingNoPrefix() + { + String mod = "supermod3000@[1.2,)"; + for (DependencyParser parser : parsers) + { + assertThrows(LoaderException.class, () -> { + parser.parseDependencies(mod); + }); + } + } + + @Test + public void testParsingCombined() + { + for (DependencyParser parser : parsers) + { + String dependencyString = "after:supermod2000@[1.3,);required-before:yetanothermod;required:modw"; + DependencyParser.DependencyInfo info = parser.parseDependencies(dependencyString); + assertContainsSameToString(info.requirements, Sets.newHashSet("yetanothermod", "modw")); + assertContainsSameToString(info.dependencies, Sets.newHashSet("supermod2000@[1.3,)")); + assertContainsSameToString(info.dependants, Sets.newHashSet("yetanothermod")); + } + } + + @Test + public void testParsingSided() + { + String mod = "testmod@[1.0,2.0)"; + { + DependencyParser.DependencyInfo info = clientDependencyParser.parseDependencies("client-after:" + mod); + assertTrue(info.requirements.isEmpty()); + assertTrue(info.dependants.isEmpty()); + assertContainsSameToString(info.dependencies, Sets.newHashSet(mod)); + } + + { + DependencyParser.DependencyInfo info = clientDependencyParser.parseDependencies("server-after:testmod@[1.0,2.0);server-required:testmod2;server-after:testmod3;server-before:testmod4"); + assertTrue(info.requirements.isEmpty()); + assertTrue(info.dependants.isEmpty()); + assertTrue(info.dependencies.isEmpty()); + } + + { + DependencyParser.DependencyInfo info = clientDependencyParser.parseDependencies("client-before:testmod@[1.0,2.0);server-required:testmod2;server-after:testmod3;server-before:testmod4"); + assertTrue(info.requirements.isEmpty()); + assertContainsSameToString(info.dependants, Sets.newHashSet(mod)); + assertTrue(info.dependencies.isEmpty()); + } + + { + DependencyParser.DependencyInfo info = serverDependencyParser.parseDependencies("server-before:" + mod); + assertTrue(info.requirements.isEmpty()); + assertContainsSameToString(info.dependants, Sets.newHashSet(mod)); + assertTrue(info.dependencies.isEmpty()); + } + + { + DependencyParser.DependencyInfo info = serverDependencyParser.parseDependencies("client-before:testmod@[1.0,2.0);client-required:testmod2;client-after:testmod3;client-before:testmod4"); + assertTrue(info.requirements.isEmpty()); + assertTrue(info.dependants.isEmpty()); + assertTrue(info.dependencies.isEmpty()); + } + + { + DependencyParser.DependencyInfo info = serverDependencyParser.parseDependencies("server-before:testmod@[1.0,2.0);client-required:testmod2;client-after:testmod3;client-before:testmod4"); + assertTrue(info.requirements.isEmpty()); + assertContainsSameToString(info.dependants, Sets.newHashSet(mod)); + assertTrue(info.dependencies.isEmpty()); + } + } + + @Test + public void testParsingInvalidDependencyInstructions() + { + for (DependencyParser parser : parsers) + { + assertThrows(LoaderException.class, () -> { + parser.parseDependencies("gibberishtext:amod"); + }); + } + } + + @Test + public void testParsingInvalidDependencyVersionClient() + { + for (DependencyParser parser : parsers) + { + assertThrows(LoaderException.class, () -> { + parser.parseDependencies("amod@[10"); + }); + assertThrows(LoaderException.class, () -> { + parser.parseDependencies("client:amod@[10"); + }); + assertThrows(LoaderException.class, () -> { + parser.parseDependencies("server:amod@[10"); + }); + } + } + + // TODO: enable this in 1.13 +// @Test(expected = LoaderException.class) +// public void testParsingUppercaseModId() +// { +// for (DependencyParser parser : parsers) +// { +// assertThrows(LoaderException.class, () -> { +// parser.parseDependencies("Forge@[1.0]"); +// }); +// assertThrows(LoaderException.class, () -> { +// parser.parseDependencies("client:Forge@[1.0]"); +// }); +// assertThrows(LoaderException.class, () -> { +// parser.parseDependencies("server:Forge@[1.0]"); +// }); +// } +// } + + @Test + public void testParsingSoftDepWithNoVersion() + { + for (DependencyParser parser : parsers) + { + assertThrows(LoaderException.class, () -> { + parser.parseDependencies("amod"); + }); + assertThrows(LoaderException.class, () -> { + parser.parseDependencies("client:amod"); + }); + assertThrows(LoaderException.class, () -> { + parser.parseDependencies("server:amod"); + }); + } + } + + @Test + public void testParsingDepAfterAll() + { + for (DependencyParser parser : parsers) + { + parser.parseDependencies("after:*"); + } + } + + @Test + public void testParsingDepBeforeAll() + { + for (DependencyParser parser : parsers) + { + parser.parseDependencies("before:*"); + } + } + + @Test + public void testParsingDepOnAll() + { + for (DependencyParser parser : parsers) + { + assertThrows(LoaderException.class, () -> { + parser.parseDependencies("*"); + }); + } + } + + @Test + public void testParsingSidedDepOnAll() + { + for (DependencyParser parser : parsers) + { + assertThrows(LoaderException.class, () -> { + parser.parseDependencies("client:*"); + }); + } + } + + @Test + public void testParsingRequireAll() + { + for (DependencyParser parser : parsers) + { + assertThrows(LoaderException.class, () -> { + parser.parseDependencies("required:*"); + }); + } + } + + @Test + public void testParsingVersionedAll() + { + for (DependencyParser parser : parsers) + { + assertThrows(LoaderException.class, () -> { + parser.parseDependencies("*@[1.0]"); + }); + } + } + + public static void assertContainsSameToString(Collection c1, Collection expected) + { + Collection transformedToString = Collections2.transform(c1, Functions.toStringFunction()); + assertContainsSame(transformedToString, expected); + } + + public static void assertContainsSame(Collection c1, Collection c2) + { + if (!c1.containsAll(c2) || !c2.containsAll(c1)) + { + fail(c1 + " does not contain the same as " + c2); + } + } +}