[SUREFIRE-1234] Allow to configure JVM for tests by referencing a toolchain entry
diff --git a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
index b26e981..112de82 100644
--- a/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
+++ b/maven-surefire-common/src/main/java/org/apache/maven/plugin/surefire/AbstractSurefireMojo.java
@@ -97,6 +97,7 @@
import javax.annotation.Nonnull;
import java.io.File;
import java.io.IOException;
+import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.nio.file.Files;
import java.nio.file.Paths;
@@ -141,6 +142,8 @@
import static org.apache.maven.surefire.booter.SystemUtils.toJdkVersionFromReleaseFile;
import static org.apache.maven.surefire.suite.RunResult.failure;
import static org.apache.maven.surefire.suite.RunResult.noTestsRun;
+import static org.apache.maven.surefire.util.ReflectionUtils.invokeMethodWithArray;
+import static org.apache.maven.surefire.util.ReflectionUtils.tryGetMethod;
/**
* Abstract base class for running tests using Surefire.
@@ -765,6 +768,42 @@
private String[] dependenciesToScan;
/**
+ * <p>
+ * Allow for configuration of the test jvm via maven toolchains.
+ * This permits a configuration where the project is built with one jvm and tested with another.
+ * This is similar to {@link #jvm}, but avoids hardcoding paths.
+ * The two parameters are mutually exclusive (jvm wins)
+ * </p>
+ *
+ * <p>Examples:</p>
+ * (see <a href="https://maven.apache.org/guides/mini/guide-using-toolchains.html">
+ * Guide to Toolchains</a> for more info)
+ *
+ * <pre>
+ * {@code
+ * <configuration>
+ * ...
+ * <jdkToolchain>
+ * <version>1.11</version>
+ * </jdkToolchain>
+ * </configuration>
+ *
+ * <configuration>
+ * ...
+ * <jdkToolchain>
+ * <version>1.8</version>
+ * <vendor>zulu</vendor>
+ * </jdkToolchain>
+ * </configuration>
+ * }
+ * </pre>
+ *
+ * @since 3.0.0-M5 and Maven 3.3.x
+ */
+ @Parameter
+ private Map<String, String> jdkToolchain;
+
+ /**
*
*/
@Component
@@ -909,7 +948,49 @@
return consoleLogger;
}
- private void setupStuff()
+ private static <T extends ToolchainManager> Toolchain getToolchainMaven33x( Class<T> toolchainManagerType,
+ T toolchainManager,
+ MavenSession session,
+ Map<String, String> toolchainArgs )
+ throws MojoFailureException
+ {
+ Method getToolchainsMethod =
+ tryGetMethod( toolchainManagerType, "getToolchains", MavenSession.class, String.class, Map.class );
+ if ( getToolchainsMethod != null )
+ {
+ //noinspection unchecked
+ List<Toolchain> tcs = (List<Toolchain>) invokeMethodWithArray( toolchainManager,
+ getToolchainsMethod, session, "jdk", toolchainArgs );
+ if ( tcs.isEmpty() )
+ {
+ throw new MojoFailureException(
+ "Requested toolchain specification did not match any configured toolchain: " + toolchainArgs );
+ }
+ return tcs.get( 0 );
+ }
+ return null;
+ }
+
+ //TODO remove the part with ToolchainManager lookup once we depend on
+ //3.0.9 (have it as prerequisite). Define as regular component field then.
+ private Toolchain getToolchain() throws MojoFailureException
+ {
+ Toolchain tc = null;
+
+ if ( getJdkToolchain() != null )
+ {
+ tc = getToolchainMaven33x( ToolchainManager.class, getToolchainManager(), getSession(), getJdkToolchain() );
+ }
+
+ if ( tc == null )
+ {
+ tc = getToolchainManager().getToolchainFromBuildContext( "jdk", getSession() );
+ }
+
+ return tc;
+ }
+
+ private void setupStuff() throws MojoFailureException
{
surefireDependencyResolver = new SurefireDependencyResolver( getRepositorySystem(),
getConsoleLogger(), getLocalRepository(),
@@ -925,7 +1006,7 @@
if ( getToolchainManager() != null )
{
- toolchain = getToolchainManager().getToolchainFromBuildContext( "jdk", getSession() );
+ toolchain = getToolchain();
}
}
@@ -3865,6 +3946,16 @@
SurefireHelper.logDebugOrCliShowErrors( s, getConsoleLogger(), cli );
}
+ public Map<String, String> getJdkToolchain()
+ {
+ return jdkToolchain;
+ }
+
+ public void setJdkToolchain( Map<String, String> jdkToolchain )
+ {
+ this.jdkToolchain = jdkToolchain;
+ }
+
public String getTempDir()
{
return tempDir;
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoToolchainsTest.java b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoToolchainsTest.java
new file mode 100644
index 0000000..7589718
--- /dev/null
+++ b/maven-surefire-common/src/test/java/org/apache/maven/plugin/surefire/AbstractSurefireMojoToolchainsTest.java
@@ -0,0 +1,176 @@
+package org.apache.maven.plugin.surefire;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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.
+ */
+
+import org.apache.maven.execution.MavenSession;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.toolchain.Toolchain;
+import org.apache.maven.toolchain.ToolchainManager;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.powermock.core.classloader.annotations.PowerMockIgnore;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static java.util.Collections.emptyMap;
+import static java.util.Collections.singletonList;
+import static java.util.Collections.singletonMap;
+import static junit.framework.TestCase.assertNull;
+import static org.fest.assertions.Assertions.assertThat;
+import static org.powermock.api.mockito.PowerMockito.mock;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+import static org.powermock.api.mockito.PowerMockito.when;
+import static org.powermock.reflect.Whitebox.invokeMethod;
+
+/**
+ * Test for {@link AbstractSurefireMojo}. jdkToolchain parameter
+ */
+@RunWith( PowerMockRunner.class )
+@PrepareForTest( {AbstractSurefireMojo.class} )
+@PowerMockIgnore( {"org.jacoco.agent.rt.*", "com.vladium.emma.rt.*"} )
+public class AbstractSurefireMojoToolchainsTest
+{
+
+ /**
+ * Ensure that we use the toolchain found by getToolchainMaven33x()
+ * when the jdkToolchain parameter is set.
+ */
+ @Test
+ public void shouldCallMaven33xMethodWhenSpecSet() throws Exception
+ {
+ AbstractSurefireMojoTest.Mojo mojo = new AbstractSurefireMojoTest.Mojo();
+ Toolchain expectedFromMaven33Method = mock( Toolchain.class );
+ MockToolchainManager toolchainManager = new MockToolchainManager( null, null );
+ mojo.setToolchainManager( toolchainManager );
+ mojo.setJdkToolchain( singletonMap( "version", "1.8" ) );
+
+ mockStatic( AbstractSurefireMojo.class );
+ when(
+ AbstractSurefireMojo.class,
+ "getToolchainMaven33x",
+ ToolchainManager.class,
+ toolchainManager,
+ mojo.getSession(), mojo.getJdkToolchain() ).thenReturn( expectedFromMaven33Method );
+ Toolchain actual = invokeMethod( mojo, "getToolchain" );
+ assertThat( actual )
+ .isSameAs( expectedFromMaven33Method );
+ }
+
+ /**
+ * Ensure that we use the toolchain from build context when
+ * no jdkToolchain map is configured in mojo parameters.
+ * getToolchain() returns the main maven toolchain from the build context
+ */
+ @Test
+ public void shouldFallthroughToBuildContextWhenNoSpecSet() throws Exception
+ {
+ AbstractSurefireMojoTest.Mojo mojo = new AbstractSurefireMojoTest.Mojo();
+ Toolchain expectedFromContext = mock( Toolchain.class );
+ Toolchain expectedFromSpec = mock( Toolchain.class ); //ensure it still behaves correctly even if not null
+ mojo.setToolchainManager( new MockToolchainManager( expectedFromSpec, expectedFromContext ) );
+ Toolchain actual = invokeMethod( mojo, "getToolchain" );
+ assertThat( actual )
+ .isSameAs( expectedFromContext );
+ }
+
+ @Test
+ public void shouldReturnNoToolchainInMaven32() throws Exception
+ {
+ Toolchain toolchain = invokeMethod( AbstractSurefireMojo.class,
+ "getToolchainMaven33x",
+ MockToolchainManagerMaven32.class,
+ new MockToolchainManagerMaven32( null ),
+ mock( MavenSession.class ),
+ emptyMap() );
+ assertNull( toolchain );
+ }
+
+ @Test( expected = MojoFailureException.class )
+ public void shouldThrowMaven33xToolchain() throws Exception
+ {
+ invokeMethod(
+ AbstractSurefireMojo.class,
+ "getToolchainMaven33x",
+ MockToolchainManager.class,
+ new MockToolchainManager( null, null ),
+ mock( MavenSession.class ),
+ emptyMap() );
+ }
+
+ @Test
+ public void shouldGetMaven33xToolchain() throws Exception
+ {
+ Toolchain expected = mock( Toolchain.class );
+ Toolchain actual = invokeMethod(
+ AbstractSurefireMojo.class,
+ "getToolchainMaven33x",
+ MockToolchainManager.class,
+ new MockToolchainManager( expected, null ),
+ mock( MavenSession.class ),
+ emptyMap() );
+
+ assertThat( actual )
+ .isSameAs( expected );
+ }
+
+ /**
+ * Mocks a ToolchainManager
+ */
+ public static final class MockToolchainManager extends MockToolchainManagerMaven32
+ {
+ private final Toolchain specToolchain;
+
+ public MockToolchainManager( Toolchain specToolchain, Toolchain buildContextToolchain )
+ {
+ super( buildContextToolchain );
+ this.specToolchain = specToolchain;
+ }
+
+ public List<Toolchain> getToolchains( MavenSession session, String type, Map<String, String> requirements )
+ {
+ return specToolchain == null ? Collections.<Toolchain>emptyList() : singletonList( specToolchain );
+ }
+ }
+
+ /**
+ * Mocks an older version that does not implement getToolchains()
+ * returns provided toolchain
+ */
+ public static class MockToolchainManagerMaven32 implements ToolchainManager
+ {
+
+ private final Toolchain buildContextToolchain;
+
+ public MockToolchainManagerMaven32( Toolchain buildContextToolchain )
+ {
+ this.buildContextToolchain = buildContextToolchain;
+ }
+
+ @Override
+ public Toolchain getToolchainFromBuildContext( String type, MavenSession context )
+ {
+ return buildContextToolchain;
+ }
+ }
+}
diff --git a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
index 22bf702..36425ed 100644
--- a/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
+++ b/maven-surefire-common/src/test/java/org/apache/maven/surefire/JUnit4SuiteTest.java
@@ -25,6 +25,7 @@
import junit.framework.TestSuite;
import org.apache.maven.plugin.surefire.AbstractSurefireMojoJava7PlusTest;
import org.apache.maven.plugin.surefire.AbstractSurefireMojoTest;
+import org.apache.maven.plugin.surefire.AbstractSurefireMojoToolchainsTest;
import org.apache.maven.plugin.surefire.CommonReflectorTest;
import org.apache.maven.plugin.surefire.MojoMocklessTest;
import org.apache.maven.plugin.surefire.SurefireHelperTest;
@@ -97,6 +98,7 @@
suite.addTest( new JUnit4TestAdapter( JarManifestForkConfigurationTest.class ) );
suite.addTest( new JUnit4TestAdapter( ModularClasspathForkConfigurationTest.class ) );
suite.addTest( new JUnit4TestAdapter( AbstractSurefireMojoJava7PlusTest.class ) );
+ suite.addTest( new JUnit4TestAdapter( AbstractSurefireMojoToolchainsTest.class ) );
suite.addTest( new JUnit4TestAdapter( ScannerUtilTest.class ) );
suite.addTest( new JUnit4TestAdapter( MojoMocklessTest.class ) );
suite.addTest( new JUnit4TestAdapter( ForkClientTest.class ) );
diff --git a/maven-surefire-plugin/src/site/apt/examples/toolchains.apt.vm b/maven-surefire-plugin/src/site/apt/examples/toolchains.apt.vm
new file mode 100644
index 0000000..891f8b1
--- /dev/null
+++ b/maven-surefire-plugin/src/site/apt/examples/toolchains.apt.vm
@@ -0,0 +1,56 @@
+ ------
+ Using Maven Toolchains
+ ------
+ Akom <akom>
+ ------
+ 2020-04-17
+ ------
+
+~~ Licensed to the Apache Software Foundation (ASF) under one
+~~ or more contributor license agreements. See the NOTICE file
+~~ distributed with this work for additional information
+~~ regarding copyright ownership. The ASF licenses this file
+~~ to you 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.
+
+~~ NOTE: For help with the syntax of this file, see:
+~~ http://maven.apache.org/doxia/references/apt-format.html
+
+ For general information about Maven Toolchains, see
+ {{{https://maven.apache.org/guides/mini/guide-using-toolchains.html}Guide to Using Toolchains}}
+
+
+Using Maven Toolchains with ${thisPlugin}.
+
+ By default, if the pom configures the toolchains plugin as specified in the aforementioned
+ guide, ${thisPlugin} will launch the test jvm using the main toolchain
+ configured in Maven.
+
+ In some cases, it may be desirable to compile and test using different jvms.
+ While the <<<jvm>>> option can achieve this, it requires hardcoding system-specific paths.
+ Configuration option <<<jdkToolchain>>> can be used to supply an alternate toolchain specification.
+
+* Configuring a different jvm for running tests using toolchains
+
++---+
+<configuration>
+ [...]
+ <jdkToolchain>
+ <version>1.11</version>
+ <vendor>zulu</vendor>
+ </jdkToolchain>
+ [...]
+</configuration>
++---+
+
+ The above example assumes that your toolchains.xml contains a valid entry with these values.
diff --git a/maven-surefire-plugin/src/site/markdown/java9.md b/maven-surefire-plugin/src/site/markdown/java9.md
index 4ba2567..17157ab 100644
--- a/maven-surefire-plugin/src/site/markdown/java9.md
+++ b/maven-surefire-plugin/src/site/markdown/java9.md
@@ -125,3 +125,5 @@
</plugin>
Now you can run the build with tests on the top of Java 9.
+
+Also see the [full documentation for surefire toolchains](examples/toolchains.html) configuration options.
diff --git a/maven-surefire-plugin/src/site/site.xml b/maven-surefire-plugin/src/site/site.xml
index 2cec467..cbee436 100644
--- a/maven-surefire-plugin/src/site/site.xml
+++ b/maven-surefire-plugin/src/site/site.xml
@@ -62,6 +62,7 @@
<item name="Shutdown of Forked JVM" href="examples/shutdown.html"/>
<item name="Run tests with Java 9" href="java9.html"/>
<item name="Run tests in Docker" href="docker.html"/>
+ <item name="Run tests in a different JVM using toolchains" href="examples/toolchains.html"/>
</menu>
</body>
</project>