diff --git a/src/main/java/org/spongepowered/lwts/runner/DelegateRunner.java b/src/main/java/org/spongepowered/lwts/runner/DelegateRunner.java new file mode 100644 index 0000000..2c2d5c5 --- /dev/null +++ b/src/main/java/org/spongepowered/lwts/runner/DelegateRunner.java @@ -0,0 +1,137 @@ +/* + * This file is part of LaunchWrapperTestSuite, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.lwts.runner; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.runner.Description; +import org.junit.runner.RunWith; +import org.junit.runner.Runner; +import org.junit.runner.notification.RunNotifier; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.RunnerBuilder; + +import com.google.common.collect.Lists; + +/** + * A runner that delegates to another runner. Note that this class does not do anything + * to run in a Launchwrapper context; use {@link LaunchWrapperDelegateRunner} for that. + * + * When using this directly with {@link RunWith}, also specify a {@link DelegatedRunWith} + * annotation to indicate the runner to delegate to. + */ +public class DelegateRunner extends Runner { + + /** + * Specifies the runner to delegate to. + */ + @Retention(RetentionPolicy.RUNTIME) + @Target(ElementType.TYPE) + @Inherited + public @interface DelegatedRunWith { + /** + * @return the runner to use + */ + public Class value(); + } + + /** + * Invoked by JUnit to initialize this runner. + * + * @param testClass The test class + * @param builder The RunnerBuilder + * @throws InitializationError If an error occurs during initialization + */ + public DelegateRunner(Class testClass, RunnerBuilder builder) throws InitializationError { + this(getDelegateClass(testClass), testClass, builder); + } + + /** + * Initialize this runner with another runner to delegate to. + * + * @param runnerClass The runner to delegate to + * @param testClass The test class + * @param builder The RunnerBuilder + * @throws InitializationError If an error occurs during initialization + */ + protected DelegateRunner(Class runnerClass, Class testClass, RunnerBuilder builder) throws InitializationError { + this.runner = constructRunner(runnerClass, testClass, builder); + } + + /** + * Finds the delegate runner class using the DelegatedRunWith annotation. + */ + private static Class getDelegateClass(Class testClass) throws InitializationError { + DelegatedRunWith annotation = testClass.getAnnotation(DelegatedRunWith.class); + if (annotation == null) { + throw new InitializationError("class '" + testClass + "' must have a DelegateRunWith annotation"); + } + return annotation.value(); + } + + /** + * Constructs the runner instance, trying both a constructor that takes only + * a class, and one that takes a class and a RunnerBuilder. (See + * https://git.io/fNeAw) + */ + @SuppressWarnings("serial") + private static Runner constructRunner(Class runnerClass, Class testClass, RunnerBuilder builder) throws InitializationError { + try { + return runnerClass.getConstructor(Class.class).newInstance(testClass); + } catch (Exception e) { + try { + return runnerClass.getConstructor(Class.class, RunnerBuilder.class).newInstance(testClass, builder); + } catch (Exception e2) { + throw new InitializationError(Lists.newArrayList( + new Throwable("Failed to construct runner '" + runnerClass + + "' for test '" + testClass + "'", null, false, false) { + @Override public String toString() { return getMessage(); } + }, + e, e2)); + } + } + } + + private final Runner runner; + + @Override + public Description getDescription() { + return runner.getDescription(); + } + + @Override + public void run(RunNotifier notifier) { + runner.run(notifier); + } + + @Override + public int testCount() { + return runner.testCount(); + } +} diff --git a/src/main/java/org/spongepowered/lwts/runner/LaunchWrapperDelegateRunner.java b/src/main/java/org/spongepowered/lwts/runner/LaunchWrapperDelegateRunner.java new file mode 100644 index 0000000..b962911 --- /dev/null +++ b/src/main/java/org/spongepowered/lwts/runner/LaunchWrapperDelegateRunner.java @@ -0,0 +1,109 @@ +/* + * This file is part of LaunchWrapperTestSuite, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.lwts.runner; + +import org.junit.runner.RunWith; +import org.junit.runner.Runner; +import org.junit.runners.model.InitializationError; +import org.junit.runners.model.RunnerBuilder; + +import com.google.common.base.Strings; + +import net.minecraft.launchwrapper.Launch; + +/** + * A runner that delegates to another runner, and runs in Launchwrapper context. + * + * When using this directly with {@link RunWith}, also specify a {@link DelegatedRunWith} + * annotation to indicate the runner to delegate to. + */ +public class LaunchWrapperDelegateRunner extends DelegateRunner { + + public static final String TWEAKER_PROPERTY = "lwts.tweaker"; + + private static boolean initialized; + + /** + * Invoked by JUnit to initialize this runner. + * + * @param klass The test class + * @param builder The RunnerBuilder + * @throws InitializationError If an error occurs during initialization + */ + public LaunchWrapperDelegateRunner(Class klass, RunnerBuilder builder) throws InitializationError { + super(loadTestClass(klass), builder); + } + + /** + * Initialize this runner with another runner to delegate to. + * + * @param runnerClass The runner to delegate to + * @param testClass The test class + * @param builder The RunnerBuilder + * @throws InitializationError If an error occurs during initialization + */ + protected LaunchWrapperDelegateRunner(Class runnerClass, Class testClass, RunnerBuilder builder) throws InitializationError { + super(runnerClass, loadTestClass(testClass), builder); + } + + /** + * Loads a test class within the Launchwrapper context. + * + *

The context will be initialized the first time this method is + * invoked.

+ * + * @param originalClass The original test class to load using Launchwrapper + * @return The loaded class + * @throws InitializationError If an errors occurs when loading the class + */ + public static Class loadTestClass(Class originalClass) throws InitializationError { + if (!initialized) { + initialized = true; + + String tweakClass = System.getProperty(TWEAKER_PROPERTY); + if (Strings.isNullOrEmpty(tweakClass)) { + throw new RuntimeException("Missing system property " + TWEAKER_PROPERTY); + } + + // Normally, LaunchWrapper sets the thread's context class loader + // to the LaunchClassLoader. However, that causes issue as soon as + // tests are run in the normal class loader in the same thread. + // Simply resetting it seems to fix various issues with Mockito. + Thread thread = Thread.currentThread(); + ClassLoader contextClassLoader = thread.getContextClassLoader(); + + Launch.main(new String[]{"--tweakClass", tweakClass}); + + thread.setContextClassLoader(contextClassLoader); + } + + try { + return Class.forName(originalClass.getName(), true, Launch.classLoader); + } catch (ClassNotFoundException e) { + throw new InitializationError(e); + } + } + +} diff --git a/src/main/java/org/spongepowered/lwts/runner/LaunchWrapperParameterized.java b/src/main/java/org/spongepowered/lwts/runner/LaunchWrapperParameterized.java index 1ccacae..01bb976 100644 --- a/src/main/java/org/spongepowered/lwts/runner/LaunchWrapperParameterized.java +++ b/src/main/java/org/spongepowered/lwts/runner/LaunchWrapperParameterized.java @@ -26,23 +26,25 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; +import org.junit.runners.model.RunnerBuilder; /** * {@link Parameterized} test runner for JUnit. To run a parameterized test * in the Launchwrapper context, declare this class as test runner using * {@link RunWith}. */ -public class LaunchWrapperParameterized extends Parameterized { +public class LaunchWrapperParameterized extends LaunchWrapperDelegateRunner { /** * Invoked by JUnit to initialize the {@link LaunchWrapperParameterized} * test runner. * * @param klass The test class + * @param builder The RunnerBuilder * @throws Throwable If an error occurs during initialization */ - public LaunchWrapperParameterized(Class klass) throws Throwable { - super(LaunchWrapperTestRunner.loadTestClass(klass)); + public LaunchWrapperParameterized(Class klass, RunnerBuilder builder) throws Throwable { + super(Parameterized.class, klass, builder); } } diff --git a/src/main/java/org/spongepowered/lwts/runner/LaunchWrapperTestRunner.java b/src/main/java/org/spongepowered/lwts/runner/LaunchWrapperTestRunner.java index 4323cba..3d158e3 100644 --- a/src/main/java/org/spongepowered/lwts/runner/LaunchWrapperTestRunner.java +++ b/src/main/java/org/spongepowered/lwts/runner/LaunchWrapperTestRunner.java @@ -24,68 +24,26 @@ */ package org.spongepowered.lwts.runner; -import com.google.common.base.Strings; -import net.minecraft.launchwrapper.Launch; import org.junit.runner.RunWith; -import org.junit.runners.BlockJUnit4ClassRunner; +import org.junit.runners.JUnit4; import org.junit.runners.model.InitializationError; +import org.junit.runners.model.RunnerBuilder; /** * Standard JUnit test runner. To run a test class in the Launchwrapper * context, declare this class as test runner using {@link RunWith}. */ -public class LaunchWrapperTestRunner extends BlockJUnit4ClassRunner { - - public static final String TWEAKER_PROPERTY = "lwts.tweaker"; - - private static boolean initialized; +public class LaunchWrapperTestRunner extends LaunchWrapperDelegateRunner { /** * Invoked by JUnit to initialize the {@link LaunchWrapperTestRunner}. * * @param klass The test class + * @param builder The RunnerBuilder * @throws InitializationError If an error occurs during initialization */ - public LaunchWrapperTestRunner(Class klass) throws InitializationError { - super(loadTestClass(klass)); - } - - /** - * Loads a test class within the Launchwrapper context. - * - *

The context will be initialized the first time this method is - * invoked.

- * - * @param originalClass The original test class to load using Launchwrapper - * @return The loaded class - * @throws InitializationError If an errors occurs when loading the class - */ - public static Class loadTestClass(Class originalClass) throws InitializationError { - if (!initialized) { - initialized = true; - - String tweakClass = System.getProperty(TWEAKER_PROPERTY); - if (Strings.isNullOrEmpty(tweakClass)) { - throw new RuntimeException("Missing system property " + TWEAKER_PROPERTY); - } - - // Normally, LaunchWrapper sets the thread's context class loader - // to the LaunchClassLoader. However, that causes issue as soon as - // tests are run in the normal class loader in the same thread. - // Simply resetting it seems to fix various issues with Mockito. - Thread thread = Thread.currentThread(); - ClassLoader contextClassLoader = thread.getContextClassLoader(); - - Launch.main(new String[]{"--tweakClass", tweakClass}); - - thread.setContextClassLoader(contextClassLoader); - } - - try { - return Class.forName(originalClass.getName(), true, Launch.classLoader); - } catch (ClassNotFoundException e) { - throw new InitializationError(e); - } + public LaunchWrapperTestRunner(Class klass, RunnerBuilder builder) throws InitializationError { + super(JUnit4.class, klass, builder); } }