diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index f738659511..34f5977c4d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -8,6 +8,17 @@ Updated the embedded Maven from version 3.9.7 to 3.9.9; [Maven 3.9.9 Release Notes](https://maven.apache.org/docs/3.9.9/release-notes.html). +### Surefire/Failsafe plugin configuration propagated to Junit/TestNG launch configuration + +The following arguments are supported:
+``,
+``,
+``,
+``,
+``,
+ +Configuration is propagated on unit test launch configuration creation and also when executing `maven > update project` + ## 2.6.1 * 📅 Release Date: 04th June 2024 diff --git a/org.eclipse.m2e.jdt.tests/projects/surefireFailsafeToTestLaunchSettings/argumentsAreSet/pom.xml b/org.eclipse.m2e.jdt.tests/projects/surefireFailsafeToTestLaunchSettings/argumentsAreSet/pom.xml new file mode 100644 index 0000000000..de4208e439 --- /dev/null +++ b/org.eclipse.m2e.jdt.tests/projects/surefireFailsafeToTestLaunchSettings/argumentsAreSet/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + foo.bar + demo-test-config + 0.0.1-SNAPSHOT + + + org.codehaus.mojo:properties-maven-plugin:read-project-properties + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + + integration-test + + + + + + + + diff --git a/org.eclipse.m2e.jdt.tests/src/org/eclipse/m2e/jdt/tests/UnitTestLaunchConfigConfigurationTest.java b/org.eclipse.m2e.jdt.tests/src/org/eclipse/m2e/jdt/tests/UnitTestLaunchConfigConfigurationTest.java new file mode 100644 index 0000000000..e3f24fcc82 --- /dev/null +++ b/org.eclipse.m2e.jdt.tests/src/org/eclipse/m2e/jdt/tests/UnitTestLaunchConfigConfigurationTest.java @@ -0,0 +1,319 @@ +/******************************************************************************* + * Copyright (c) 2024 Pascal Treilhes + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.m2e.jdt.tests; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; + +import java.io.ByteArrayInputStream; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.FileLocator; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationType; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.m2e.core.MavenPlugin; +import org.eclipse.m2e.core.internal.preferences.MavenConfigurationImpl; +import org.eclipse.m2e.jdt.internal.UnitTestSupport; +import org.eclipse.m2e.jdt.internal.launch.MavenRuntimeClasspathProvider; +import org.eclipse.m2e.tests.common.AbstractMavenProjectTestCase; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +@SuppressWarnings("restriction") +@RunWith(Parameterized.class) +public class UnitTestLaunchConfigConfigurationTest extends AbstractMavenProjectTestCase { + + private static final String REPLACED_SUREFIRE_POM_STRING = ""; + private static final String REPLACED_FAILSAFE_POM_STRING = ""; + private static final String ROOT_PATH = "/projects/surefireFailsafeToTestLaunchSettings"; + private static ILaunchManager LAUNCH_MANAGER = DebugPlugin.getDefault().getLaunchManager(); + + private static final String SUREFIRE_ARGS_SET = """ + + + --argLineItem=surefireArgLineValue + + + surefireProp1Value + + + surefireEnvironmentVariables1Value + + + """; + private static final String FAILSAFE_ARGS_SET = """ + + + --argLineItem=failsafeArgLineValue + + + failsafeProp1Value + + + failsafeEnvironmentVariables1Value + + + """; + + // Define the parameters to be used in the test + @Parameters + public static Collection data() { + return List.of(MavenRuntimeClasspathProvider.JDT_TESTNG_TEST, MavenRuntimeClasspathProvider.JDT_JUNIT_TEST); + } + + @Parameter(0) + public String testType; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + ((MavenConfigurationImpl) MavenPlugin.getMavenConfiguration()).setAutomaticallyUpdateConfiguration(true); + setAutoBuilding(true); + } + + @Test + public void test_configuration_must_be_updated_with_surefire_config() + throws CoreException, IOException, InterruptedException { + // Get launch type + ILaunchConfigurationType type = LAUNCH_MANAGER.getLaunchConfigurationType(testType); + + assumeTrue(testType + " support not available", type != null); + + File pomFile = getTestFile("argumentsAreSet/pom.xml"); + String surefireConf = SUREFIRE_ARGS_SET; + String failsafeConf = null; + + IProject project = importProject(pomFile.getAbsolutePath()); + + // create basic unit test + createDefaultTest(project, type, "test.SomeTest"); + + updateProject(project); + waitForJobsToComplete(); + + ILaunchConfiguration[] updatedConfigurations = LAUNCH_MANAGER.getLaunchConfigurations(type); + assertTrue(updatedConfigurations.length == 1); + + mergePomAndPluginConfigIntoProject(project, pomFile, surefireConf, failsafeConf); + updateProject(project); + waitForJobsToComplete(); + + updatedConfigurations = LAUNCH_MANAGER.getLaunchConfigurations(type); + assertTrue(updatedConfigurations.length == 1); + + ILaunchConfiguration config = updatedConfigurations[0]; + + // check argLine + String argLine = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_VM_ARGUMENTS, ""); + assertTrue(argLine.contains("--argLineItem=surefireArgLineValue")); + + // check environmentVariables + Map envVars = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_ENVIRONMENT_VARIABLES, + (Map) null); + + assertNotNull(envVars); + assertTrue(envVars.size() == 1); + assertTrue(envVars.containsKey("surefireEnvironmentVariables1")); + assertEquals("surefireEnvironmentVariables1Value", envVars.get("surefireEnvironmentVariables1")); + + // check systemPropertyVariables + assertTrue(argLine.contains("-DsurefireProp1=surefireProp1Value")); + } + + @Test + public void test_configuration_must_be_updated_with_failsafe_config() + throws CoreException, IOException, InterruptedException { + // Get launch type + ILaunchConfigurationType type = LAUNCH_MANAGER.getLaunchConfigurationType(testType); + + assumeTrue(testType + " support not available", type != null); + + File pomFile = getTestFile("argumentsAreSet/pom.xml"); + String surefireConf = null; + String failsafeConf = FAILSAFE_ARGS_SET; + + IProject project = importProject(pomFile.getAbsolutePath()); + // waitForJobsToComplete(); + + // create basic unit test + createDefaultTest(project, type, "test.SomeTestIT"); + + updateProject(project); + waitForJobsToComplete(); + + ILaunchConfiguration[] updatedConfigurations = LAUNCH_MANAGER.getLaunchConfigurations(type); + assertTrue(updatedConfigurations.length == 1); + + mergePomAndPluginConfigIntoProject(project, pomFile, surefireConf, failsafeConf); + updateProject(project); + + updatedConfigurations = LAUNCH_MANAGER.getLaunchConfigurations(type); + assertTrue(updatedConfigurations.length == 1); + + ILaunchConfiguration config = updatedConfigurations[0]; + + // check argLine + String argLine = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_VM_ARGUMENTS, ""); + assertTrue(argLine.contains("--argLineItem=failsafeArgLineValue")); + + // check environmentVariables + Map envVars = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_ENVIRONMENT_VARIABLES, + (Map) null); + + assertNotNull(envVars); + assertTrue(envVars.size() == 1); + assertTrue(envVars.containsKey("failsafeEnvironmentVariables1")); + assertEquals("failsafeEnvironmentVariables1Value", envVars.get("failsafeEnvironmentVariables1")); + + // check systemPropertyVariables + assertTrue(argLine.contains("-DfailsafeProp1=failsafeProp1Value")); + } + + @Test + public void test_configuration_must_be_updated_with_surefire_config_when_created() + throws CoreException, IOException, InterruptedException { + // Get launch type + ILaunchConfigurationType type = LAUNCH_MANAGER.getLaunchConfigurationType(testType); + + assumeTrue(testType + " support not available", type != null); + + File pomFile = getTestFile("argumentsAreSet/pom.xml"); + String surefireConf = SUREFIRE_ARGS_SET; + String failsafeConf = null; + + IProject project = importProject(pomFile.getAbsolutePath()); + mergePomAndPluginConfigIntoProject(project, pomFile, surefireConf, failsafeConf); + updateProject(project); + waitForJobsToComplete(); + + // create basic unit test + createDefaultTest(project, type, "test.SomeTest"); + + ILaunchConfiguration[] updatedConfigurations = LAUNCH_MANAGER.getLaunchConfigurations(type); + assertTrue(updatedConfigurations.length == 1); + + ILaunchConfiguration config = updatedConfigurations[0]; + + // check argLine + String argLine = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_VM_ARGUMENTS, ""); + assertTrue(argLine.contains("--argLineItem=surefireArgLineValue")); + + // check environmentVariables + Map envVars = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_ENVIRONMENT_VARIABLES, + (Map) null); + + assertNotNull(envVars); + assertTrue(envVars.size() == 1); + assertTrue(envVars.containsKey("surefireEnvironmentVariables1")); + assertEquals("surefireEnvironmentVariables1Value", envVars.get("surefireEnvironmentVariables1")); + + // check systemPropertyVariables + assertTrue(argLine.contains("-DsurefireProp1=surefireProp1Value")); + } + + @Test + public void test_configuration_must_be_updated_with_failSafe_config_when_created() + throws CoreException, IOException, InterruptedException { + // Get launch type + ILaunchConfigurationType type = LAUNCH_MANAGER.getLaunchConfigurationType(testType); + + assumeTrue(testType + " support not available", type != null); + + File pomFile = getTestFile("argumentsAreSet/pom.xml"); + String surefireConf = null; + String failsafeConf = FAILSAFE_ARGS_SET; + + IProject project = importProject(pomFile.getAbsolutePath()); + mergePomAndPluginConfigIntoProject(project, pomFile, surefireConf, failsafeConf); + updateProject(project); + waitForJobsToComplete(); + + // create basic unit test + createDefaultTest(project, type, "test.SomeTestIT"); + + ILaunchConfiguration[] updatedConfigurations = LAUNCH_MANAGER.getLaunchConfigurations(type); + assertTrue(updatedConfigurations.length == 1); + + ILaunchConfiguration config = updatedConfigurations[0]; + + // check argLine + String argLine = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_VM_ARGUMENTS, ""); + assertTrue(argLine.contains("--argLineItem=failsafeArgLineValue")); + + // check environmentVariables + Map envVars = config.getAttribute(UnitTestSupport.LAUNCH_CONFIG_ENVIRONMENT_VARIABLES, + (Map) null); + + assertNotNull(envVars); + assertTrue(envVars.size() == 1); + assertTrue(envVars.containsKey("failsafeEnvironmentVariables1")); + assertEquals("failsafeEnvironmentVariables1Value", envVars.get("failsafeEnvironmentVariables1")); + + // check systemPropertyVariables + assertTrue(argLine.contains("-DfailsafeProp1=failsafeProp1Value")); + } + + private void updateProject(IProject project) throws CoreException, InterruptedException { + MavenPlugin.getProjectConfigurationManager().updateProjectConfiguration(project, monitor); + waitForJobsToComplete(); + } + + // Create a default test + private void createDefaultTest(IProject project, ILaunchConfigurationType type, String testClassName) + throws CoreException { + // create basic unit test + ILaunchConfigurationWorkingCopy launchConfig = type.newInstance(project, "sampleTest"); + launchConfig.setAttribute(IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, project.getName()); + launchConfig.setAttribute(IJavaLaunchConfigurationConstants.ATTR_MAIN_TYPE_NAME, testClassName); + launchConfig.setAttribute(DebugPlugin.ATTR_WORKING_DIRECTORY, project.getLocation().toString()); + launchConfig.doSave(); + } + + // Merge the pom and plugins configuration into the project + private void mergePomAndPluginConfigIntoProject(IProject project, File pomTemplate, String surefireConfiguration, + String failsafeConfiguration) throws IOException, CoreException { + String pom = Utils.read(project, pomTemplate); + IFile pomFileWS = project.getFile(pomTemplate.getName()); + String newContent = pom; + + if (surefireConfiguration != null) { + newContent = newContent.replace(REPLACED_SUREFIRE_POM_STRING, surefireConfiguration); + } + + if (failsafeConfiguration != null) { + newContent = newContent.replace(REPLACED_FAILSAFE_POM_STRING, failsafeConfiguration); + } + + pomFileWS.setContents(new ByteArrayInputStream(newContent.getBytes()), true, false, null); + } + + private File getTestFile(String filename) throws IOException { + return new File(FileLocator.toFileURL(getClass().getResource(ROOT_PATH + "/" + filename)).getFile()); + } +} diff --git a/org.eclipse.m2e.jdt/META-INF/MANIFEST.MF b/org.eclipse.m2e.jdt/META-INF/MANIFEST.MF index 9bc237e970..2cc4f19700 100644 --- a/org.eclipse.m2e.jdt/META-INF/MANIFEST.MF +++ b/org.eclipse.m2e.jdt/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: %Bundle-Name Bundle-SymbolicName: org.eclipse.m2e.jdt;singleton:=true -Bundle-Version: 2.3.500.qualifier +Bundle-Version: 2.3.600.qualifier Bundle-Localization: plugin Export-Package: org.eclipse.m2e.jdt, org.eclipse.m2e.jdt.internal;x-friends:="org.eclipse.m2e.jdt.ui", diff --git a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java index 132dd48c9d..0356efdd14 100644 --- a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java +++ b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/AbstractJavaProjectConfigurator.java @@ -192,6 +192,8 @@ public void configure(ProjectConfigurationRequest request, IProgressMonitor moni javaProject.setRawClasspath(classpath.getEntries(), classesFolder.getFullPath(), monitor); MavenJdtPlugin.getDefault().getBuildpathManager().updateClasspath(project, monitor); + + UnitTestSupport.resetLaunchConfigurations(project); } @SuppressWarnings("unused") diff --git a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/UnitTestSupport.java b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/UnitTestSupport.java new file mode 100644 index 0000000000..6051c2dc41 --- /dev/null +++ b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/UnitTestSupport.java @@ -0,0 +1,435 @@ +/******************************************************************************** + * Copyright (c) 2024, 2024 Pascal Treilhes and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Pascal Treilhes - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.jdt.internal; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.StringJoiner; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.debug.core.ILaunchConfigurationType; +import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy; +import org.eclipse.debug.core.ILaunchManager; +import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants; +import org.eclipse.jdt.launching.JavaRuntime; + +import org.apache.maven.plugin.Mojo; +import org.apache.maven.plugin.MojoExecution; +import org.apache.maven.project.MavenProject; + +import org.eclipse.m2e.core.MavenPlugin; +import org.eclipse.m2e.core.embedder.IMaven; +import org.eclipse.m2e.core.internal.IMavenConstants; +import org.eclipse.m2e.core.project.IMavenProjectFacade; +import org.eclipse.m2e.jdt.internal.launch.MavenRuntimeClasspathProvider; + + +/** + * This class is used to support the launch configuration of JUnit and TestNG tests + */ +public class UnitTestSupport { + + /** + * Feature flag to enable or disable the support + */ + private static final boolean FEATURE_ENABLED = Boolean + .parseBoolean(System.getProperty("m2e.process.test.configuration", "true")); + + private static final Logger LOG = LoggerFactory.getLogger(UnitTestSupport.class); + + /** + * org.apache.maven.surefire.api.testset.TestListResolver method name to check if a test should run + */ + private static final String SHOULD_RUN_METHOD = "shouldRun"; + + /** + * org.apache.maven.plugin.surefire.AbstractSurefireMojo method name to get the included and excluded tests + */ + private static final String GET_INCLUDED_AND_EXCLUDED_TESTS_METHOD = "getIncludedAndExcludedTests"; + + /** + * Launch configuration attribute for the main class + */ + private static final String LAUNCH_CONFIG_MAIN_CLASS = "org.eclipse.jdt.launching.MAIN_TYPE"; + + /** + * Launch configuration attribute for the project + */ + private static final String LAUNCH_CONFIG_PROJECT = "org.eclipse.jdt.launching.PROJECT_ATTR"; + + /** + * Launch configuration attribute for the VM arguments + */ + public static final String LAUNCH_CONFIG_VM_ARGUMENTS = "org.eclipse.jdt.launching.VM_ARGUMENTS"; + + /** + * Launch configuration attribute for the environment variables + */ + public static final String LAUNCH_CONFIG_ENVIRONMENT_VARIABLES = "org.eclipse.debug.core.environmentVariables"; + + /** + * Launch configuration attribute for the system properties + */ + private static final String LAUNCH_CONFIG_WORKING_DIRECTORY = "org.eclipse.jdt.launching.WORKING_DIRECTORY"; + + /** + * Launch configuration attribute for the working directory + */ + private static final String PLUGIN_ARGLINE = "argLine"; + + /** + * surefire/failsafe mojo configuration element for the environment variables + */ + private static final String PLUGIN_ENVIRONMENT_VARIABLES = "environmentVariables"; + + /** + * surefire/failsafe mojo configuration element for the system properties + */ + private static final String PLUGIN_SYSPROP_VARIABLES = "systemPropertyVariables"; + + /** + * surefire/failsafe mojo configuration element for the working directory + */ + private static final String PLUGIN_WORKING_DIRECTORY = "workingDirectory"; + + /** + * surefire/failsafe mojo configuration element for the enable assertions + */ + private static final String PLUGIN_ENABLE_ASSERTIONS = "enableAssertions"; + + /** + * maven goal for the test execution + */ + private static final String GOAL_TEST = "test"; + + /** + * maven goal for the integration test execution + */ + private static final String GOAL_INTEGRATION_TEST = "integration-test"; + + /** + * maven artifact id for the surefire plugin + */ + private static final String SUREFIRE_PLUGIN_ARTIFACT_ID = "maven-surefire-plugin"; + + /** + * maven artifact id for the failsafe plugin + */ + private static final String FAILSAFE_PLUGIN_ARTIFACT_ID = "maven-failsafe-plugin"; + + /** + * maven group id for the maven plugins + */ + private static final String MAVEN_PLUGIN_GROUP_ID = "org.apache.maven.plugins"; + + private static final List TEST_EXECUTIONS = List.of( + new ExecutionId(MAVEN_PLUGIN_GROUP_ID, SUREFIRE_PLUGIN_ARTIFACT_ID, GOAL_TEST), + new ExecutionId(MAVEN_PLUGIN_GROUP_ID, FAILSAFE_PLUGIN_ARTIFACT_ID, GOAL_INTEGRATION_TEST)); + + private static final Set CONSIDERED_LAUNCH_TYPES = Set.of(MavenRuntimeClasspathProvider.JDT_JUNIT_TEST, + MavenRuntimeClasspathProvider.JDT_TESTNG_TEST); + + /** + * Reset all launch configurations for the project + */ + public static void resetLaunchConfigurations(IProject project) { + if(FEATURE_ENABLED) { + new ConfigurationManager().setupLaunchConfigurations(project); + } + } + + /** + * Reset the launch configuration + * + * @param configuration the configuration + */ + public static void setupLaunchConfigurationFromMavenConfiguration(ILaunchConfiguration configuration) { + if(FEATURE_ENABLED) { + new ConfigurationManager().setupLaunchConfiguration(configuration); + } + } + + /** + * Check if the type is supported + * + * @param id the type id + * @return true if supported + */ + private static boolean isSupportedType(String id) { + return id != null && CONSIDERED_LAUNCH_TYPES.contains(id); + } + + private static class ConfigurationManager { + + /** + * Reset all launch configurations for the project + */ + public void setupLaunchConfigurations(IProject project) { + + if(project != null && project.exists()) { + ILaunchManager launchManager = DebugPlugin.getDefault().getLaunchManager(); + for(String launchTypeId : CONSIDERED_LAUNCH_TYPES) { + try { + // Get launch type + ILaunchConfigurationType type = launchManager.getLaunchConfigurationType(launchTypeId); + if(type == null) { + // unknown type, probably support not installed + continue; + } + // Get all launch configurations for the type + ILaunchConfiguration[] configurations = launchManager.getLaunchConfigurations(type); + for(ILaunchConfiguration configuration : configurations) { + // Check if the configuration is associated with the desired project and type + String configurationProjectName = configuration.getAttribute(LAUNCH_CONFIG_PROJECT, ""); + if(project.getName().equals(configurationProjectName)) { + LOG.info("Reset launch configuration name: {}", configuration.getName()); + setupLaunchConfiguration(configuration); + } + } + } catch(Exception e) { + LOG.error(e.getMessage(), e); + } + } + + } + } + + /** + * Reset the launch configuration + */ + public void setupLaunchConfiguration(ILaunchConfiguration configuration) { + try { + if(!isSupportedType(configuration.getType().getIdentifier())) { + return; + } + IProject project = JavaRuntime.getJavaProject(configuration).getProject(); + + // maven project if project has a maven classpath + boolean isMavenProject = configuration + .getAttribute(IJavaLaunchConfigurationConstants.ATTR_CLASSPATH_PROVIDER, "") + .equals(MavenRuntimeClasspathProvider.MAVEN_CLASSPATH_PROVIDER); + + if(project != null && project.hasNature(IMavenConstants.NATURE_ID) && isMavenProject) { + + switch(configuration.getType().getIdentifier()) { + case MavenRuntimeClasspathProvider.JDT_TESTNG_TEST: + case MavenRuntimeClasspathProvider.JDT_JUNIT_TEST: { + + LOG.info("Updating {} from maven configuration", configuration.getName()); + + IMavenProjectFacade facade = MavenPlugin.getMavenProjectRegistry().getProject(project); + + TestLaunchArguments args = getTestLaunchArguments(configuration, facade, null); + + // update the configuration only if arg extraction was successfull + if(args != null) { + defineConfigurationValues(project, configuration, args); + } + + } + } + } + } catch(CoreException ex) { + LOG.error(ex.getMessage(), ex); + } + } + + /** + * Define the configuration values + */ + private void defineConfigurationValues(IProject project, ILaunchConfiguration configuration, + TestLaunchArguments args) throws CoreException { + + ILaunchConfigurationWorkingCopy copy = configuration.getWorkingCopy(); + + StringJoiner launchArguments = new StringJoiner("\n"); + if(args.enableAssertions()) { + launchArguments.add("-ea"); + } + if(args.argLine() != null) { + launchArguments.add(args.argLine()); + } + if(args.systemPropertyVariables() != null) { + args.systemPropertyVariables().forEach((key, value) -> launchArguments.add("-D" + key + "=" + value)); + } + copy.setAttribute(LAUNCH_CONFIG_VM_ARGUMENTS, launchArguments.toString()); + + try { + if(args.workingDirectory() != null + && !Files.isSameFile(project.getLocation().toPath().toAbsolutePath(), args.workingDirectory().toPath())) { + copy.setAttribute(LAUNCH_CONFIG_WORKING_DIRECTORY, args.workingDirectory().getAbsolutePath()); + } else { + copy.setAttribute(LAUNCH_CONFIG_WORKING_DIRECTORY, (String) null); + } + } catch(IOException ex) { + LOG.error(ex.getMessage(), ex); + } + + if(args.environmentVariables() != null) { + copy.setAttribute(LAUNCH_CONFIG_ENVIRONMENT_VARIABLES, args.environmentVariables()); + } + + copy.doSave(); + } + + private TestLaunchArguments getTestLaunchArguments(ILaunchConfiguration configuration, IMavenProjectFacade facade, + IProgressMonitor monitor) throws CoreException { + + String testClass = configuration.getAttribute(LAUNCH_CONFIG_MAIN_CLASS, ""); + MavenProject mavenProject = facade.getMavenProject(); + + // find test executions + List executions = new ArrayList<>(); + for(ExecutionId id : TEST_EXECUTIONS) { + executions.addAll(facade.getMojoExecutions(id.groupId(), id.artifactId(), monitor, id.goal())); + } + + // find which plugin executions will launch the test + List handlers = executions.stream() + .filter(e -> isTestHandledByPlugin(facade, monitor, e, testClass)).toList(); + + // only one execution is expected here but if more only the first one is used + for(MojoExecution execution : handlers) { + return getTestLaunchArguments(mavenProject, execution, monitor); + } + return null; + } + + /** + * Check if the test is handled by the plugin + * + * @return true if the test is handled by the plugin + */ + private boolean isTestHandledByPlugin(IMavenProjectFacade facade, IProgressMonitor monitor, MojoExecution execution, + String testClass) { + + String testFile = testClass.replace(".", "/") + ".class"; + // get a configured mojo instance + Optional mojo = getMojoInstance(facade, execution, monitor); + // get an instance of org.apache.maven.surefire.api.testset.TestListResolver directly from the plugin instance + Optional testResolverInstance = mojo.map(o -> uncheckedInvoke(o, GET_INCLUDED_AND_EXCLUDED_TESTS_METHOD)); + // check if the test is handled by the plugin + Boolean isTestHandled = testResolverInstance.map(o -> uncheckedInvoke(o, SHOULD_RUN_METHOD, testFile, "")) + .map(Boolean.class::cast).orElse(false); + + return isTestHandled; + } + + /** + * Invoke a method without throwing checked exceptions + * + * @param instance the instance on which to invoke the method + * @param methodName the name of the method to invoke + * @param arguments the arguments + * @return the result of the invocation + */ + private static Object uncheckedInvoke(Object instance, String methodName, Object... arguments) { + Class searchClass = instance.getClass(); + Class[] parameters = Arrays.stream(arguments).map(p -> p.getClass()).toArray(Class[]::new); + while(searchClass != null) { + try { + Method method = searchClass.getDeclaredMethod(methodName, parameters); + if(!method.trySetAccessible()) { + LOG.error("Cannot make accessible {}", method); + } else { + return method.invoke(instance, arguments); + } + } catch(NoSuchMethodException | SecurityException ex) { + LOG.debug(ex.getMessage(), ex); + } catch(IllegalAccessException | InvocationTargetException ex) { + LOG.error(ex.getMessage(), ex); + } + searchClass = searchClass.getSuperclass(); + } + return null; + } + + /** Execution cache */ + private final Map mojoCache = new HashMap<>(); + + /** + * Get a configured mojo instance + */ + public Optional getMojoInstance(IMavenProjectFacade facade, MojoExecution execution, + IProgressMonitor monitor) { + return Optional.ofNullable(mojoCache.computeIfAbsent(execution, exe -> { + try { + return facade.createExecutionContext().execute(facade.getMavenProject(), + (context, pm) -> MavenPlugin.getMaven().getConfiguredMojo(context.getSession(), exe, Mojo.class), + monitor); + } catch(CoreException ex) { + LOG.error("Unable to instanciate mojo instance", ex); + return null; + } + })); + } + + /** + * Get all the arguments provided to the plugin for the provided {@link MojoExecution}. + * + * @param mavenProject the current maven project + * @param execution the plugin execution + * @param monitor the progress monitor + * @return the arguments + */ + @SuppressWarnings("unchecked") + private TestLaunchArguments getTestLaunchArguments(MavenProject mavenProject, MojoExecution execution, + IProgressMonitor monitor) { + try { + IMaven maven = MavenPlugin.getMaven(); + return new TestLaunchArguments( + maven.getMojoParameterValue(mavenProject, execution, PLUGIN_ARGLINE, String.class, monitor), + maven.getMojoParameterValue(mavenProject, execution, PLUGIN_SYSPROP_VARIABLES, Map.class, monitor), + maven.getMojoParameterValue(mavenProject, execution, PLUGIN_ENVIRONMENT_VARIABLES, Map.class, monitor), + maven.getMojoParameterValue(mavenProject, execution, PLUGIN_WORKING_DIRECTORY, File.class, monitor), + maven.getMojoParameterValue(mavenProject, execution, PLUGIN_ENABLE_ASSERTIONS, Boolean.class, monitor)); + } catch(Exception e) { + LOG.error(e.getMessage(), e); + } + return null; + } + + } + + /** + * Holder for the surefire/failsafe launch arguments + */ + private static record TestLaunchArguments(String argLine, Map systemPropertyVariables, + Map environmentVariables, File workingDirectory, boolean enableAssertions) { + } + + /** + * Holder for the execution id + */ + private static record ExecutionId(String groupId, String artifactId, String goal) { + } + +} diff --git a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/launch/MavenLaunchConfigurationListener.java b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/launch/MavenLaunchConfigurationListener.java index be35fd7f5d..d770742ee1 100644 --- a/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/launch/MavenLaunchConfigurationListener.java +++ b/org.eclipse.m2e.jdt/src/org/eclipse/m2e/jdt/internal/launch/MavenLaunchConfigurationListener.java @@ -35,6 +35,7 @@ import org.eclipse.m2e.core.internal.IMavenConstants; import org.eclipse.m2e.core.project.IMavenProjectChangedListener; import org.eclipse.m2e.core.project.MavenProjectChangedEvent; +import org.eclipse.m2e.jdt.internal.UnitTestSupport; public class MavenLaunchConfigurationListener implements ILaunchConfigurationListener, IMavenProjectChangedListener { @@ -43,6 +44,7 @@ public class MavenLaunchConfigurationListener implements ILaunchConfigurationLis @Override public void launchConfigurationAdded(ILaunchConfiguration configuration) { updateLaunchConfiguration(configuration); + UnitTestSupport.setupLaunchConfigurationFromMavenConfiguration(configuration); } @Override