diff --git a/org.eclipse.m2e.launching/META-INF/MANIFEST.MF b/org.eclipse.m2e.launching/META-INF/MANIFEST.MF index 523d712280..b8ee041a76 100644 --- a/org.eclipse.m2e.launching/META-INF/MANIFEST.MF +++ b/org.eclipse.m2e.launching/META-INF/MANIFEST.MF @@ -18,7 +18,9 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.27.0,4.0.0)", org.eclipse.m2e.maven.runtime;bundle-version="[3.8.6,4.0.0)", org.eclipse.m2e.core;bundle-version="[2.0.0,3.0.0)", org.eclipse.m2e.core.ui;bundle-version="[2.0.0,3.0.0)", - org.eclipse.m2e.workspace.cli;bundle-version="0.1.0" + org.eclipse.m2e.workspace.cli;bundle-version="0.1.0", + org.eclipse.jdt.junit.core, + org.eclipse.unittest.ui;bundle-version="1.1.200" Bundle-ActivationPolicy: lazy Bundle-RequiredExecutionEnvironment: JavaSE-17 Bundle-Vendor: %Bundle-Vendor diff --git a/org.eclipse.m2e.launching/plugin.xml b/org.eclipse.m2e.launching/plugin.xml index fec1e03370..661e235d4b 100644 --- a/org.eclipse.m2e.launching/plugin.xml +++ b/org.eclipse.m2e.launching/plugin.xml @@ -243,5 +243,12 @@ class="org.eclipse.m2e.internal.launch.MavenConsoleLineTracker" processType="java"/> + + + + diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/actions/ExecutePomAction.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/actions/ExecutePomAction.java index 69c2ef633f..b5819df36e 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/actions/ExecutePomAction.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/actions/ExecutePomAction.java @@ -56,6 +56,7 @@ import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.ElementListSelectionDialog; +import org.eclipse.unittest.ui.ConfigureViewerSupport; import org.eclipse.m2e.core.MavenPlugin; import org.eclipse.m2e.core.embedder.IMavenExecutableLocation; @@ -75,6 +76,10 @@ * @author Eugene Kuleshov */ public class ExecutePomAction implements ILaunchShortcut, IExecutableExtension, ILaunchShortcut2 { + + public static final ConfigureViewerSupport TEST_RESULT_LISTENER_CONFIGURER = new ConfigureViewerSupport( + "org.eclipse.m2e.launching.testViewSupport"); + private static final Logger log = LoggerFactory.getLogger(ExecutePomAction.class); private boolean showDialog = false; @@ -212,7 +217,6 @@ private ILaunchConfiguration createLaunchConfiguration(IContainer basedir, Strin workingCopy.setAttribute(IDebugUIConstants.ATTR_PRIVATE, true); workingCopy.setAttribute(RefreshTab.ATTR_REFRESH_SCOPE, "${project}"); //$NON-NLS-1$ workingCopy.setAttribute(RefreshTab.ATTR_REFRESH_RECURSIVE, true); - setProjectConfiguration(workingCopy, basedir); // TODO when launching Maven with debugger consider to add the following property @@ -237,6 +241,7 @@ private void setProjectConfiguration(ILaunchConfigurationWorkingCopy workingCopy workingCopy.setAttribute(MavenLaunchConstants.ATTR_PROFILES, selectedProfiles); } } + TEST_RESULT_LISTENER_CONFIGURER.apply(workingCopy); } private ILaunchConfiguration getLaunchConfiguration(IContainer basedir, String mode) { diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java new file mode 100644 index 0000000000..37010abeba --- /dev/null +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildConnectionProcess.java @@ -0,0 +1,174 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.internal.launch; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.atomic.AtomicBoolean; + +import org.eclipse.debug.core.DebugEvent; +import org.eclipse.debug.core.DebugPlugin; +import org.eclipse.debug.core.ILaunch; +import org.eclipse.debug.core.model.IProcess; +import org.eclipse.debug.core.model.IStreamsProxy; + +import org.eclipse.m2e.core.embedder.ArtifactKey; +import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge; +import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenBuildConnection; +import org.eclipse.m2e.internal.maven.listener.MavenBuildListener; +import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData; +import org.eclipse.m2e.internal.maven.listener.MavenTestEvent; + + +/** + * A process that represents the remote connection to the maven process + */ +public class MavenBuildConnectionProcess implements IProcess { + + private ILaunch launch; + + private Map attributes = new HashMap<>(); + + private Map projects = new ConcurrentHashMap<>(); + + private MavenBuildConnection connection; + + private List buildListeners = new CopyOnWriteArrayList<>(); + + private AtomicBoolean terminated = new AtomicBoolean(); + + public MavenBuildConnectionProcess(ILaunch launch) { + this.launch = launch; + launch.addProcess(this); + attributes.put(IProcess.ATTR_PROCESS_TYPE, "m2e-build-endpoint"); + fireEvent(new DebugEvent(this, DebugEvent.CREATE)); + } + + public T getAdapter(Class adapter) { + return null; + } + + public boolean canTerminate() { + return !isTerminated(); + } + + public boolean isTerminated() { + return terminated.get() || connection == null; + } + + public void terminate() { + if(terminated.compareAndSet(false, true)) { + if(connection != null) { + try { + connection.close(); + } catch(IOException ex) { + } + connection = null; + } + for(MavenBuildListener mavenBuildListener : buildListeners) { + mavenBuildListener.close(); + } + buildListeners.clear(); + fireEvent(new DebugEvent(this, DebugEvent.TERMINATE)); + } + } + + public String getLabel() { + return "M2E Build Listener"; + } + + public ILaunch getLaunch() { + return launch; + } + + public IStreamsProxy getStreamsProxy() { + return null; + } + + @Override + public void setAttribute(String key, String value) { + attributes.put(key, value); + fireEvent(new DebugEvent(this, DebugEvent.CHANGE)); + } + + @Override + public String getAttribute(String key) { + return attributes.get(key); + } + + public void addMavenBuildListener(MavenBuildListener listener) { + buildListeners.add(listener); + } + + /** + * @param mavenTestRunnerClient + * @return + */ + public void removeMavenBuildListener(MavenBuildListener listener) { + buildListeners.remove(listener); + } + + @Override + public int getExitValue() { + return 0; + } + + /** + * @return the projects + */ + public Map getProjects() { + return this.projects; + } + + void connect() throws IOException { + connection = M2EMavenBuildDataBridge.prepareConnection(launch.getLaunchConfiguration().getName(), + new MavenBuildListener() { + + @Override + public void projectStarted(MavenProjectBuildData data) { + projects.put(new ArtifactKey(data.groupId, data.artifactId, data.version, null), data); + for(MavenBuildListener mavenBuildListener : buildListeners) { + mavenBuildListener.projectStarted(data); + } + } + + @Override + public void onTestEvent(MavenTestEvent mavenTestEvent) { + for(MavenBuildListener mavenBuildListener : buildListeners) { + mavenBuildListener.onTestEvent(mavenTestEvent); + } + } + + public void close() { + MavenBuildConnectionProcess.this.terminate(); + } + }); + } + + String getMavenVMArguments() throws IOException { + return connection.getMavenVMArguments(); + } + + private void fireEvent(DebugEvent event) { + DebugPlugin manager = DebugPlugin.getDefault(); + if(manager != null) { + manager.fireDebugEventSet(new DebugEvent[] {event}); + } + } + +} diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java index cd9abfbcd0..919caa77f1 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java @@ -16,48 +16,30 @@ import java.io.IOException; import java.util.Arrays; import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Stream; +import java.util.Optional; import org.eclipse.core.runtime.CoreException; import org.eclipse.debug.core.DebugPlugin; import org.eclipse.debug.core.ILaunch; import org.eclipse.debug.core.ILaunchesListener2; +import org.eclipse.debug.core.model.IProcess; import org.eclipse.m2e.core.embedder.ArtifactKey; import org.eclipse.m2e.core.internal.launch.MavenEmbeddedRuntime; import org.eclipse.m2e.internal.launch.MavenRuntimeLaunchSupport.VMArguments; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenBuildConnection; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenProjectBuildData; +import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData; public class MavenBuildProjectDataConnection { - private static record MavenBuildConnectionData(Map projects, - MavenBuildConnection connection) { - } - - private static final Map LAUNCH_PROJECT_DATA = new ConcurrentHashMap<>(); - static { DebugPlugin.getDefault().getLaunchManager().addLaunchListener(new ILaunchesListener2() { public void launchesRemoved(ILaunch[] launches) { - closeServers(Arrays.stream(launches).map(LAUNCH_PROJECT_DATA::remove)); + cleanupConnections(launches); } public void launchesTerminated(ILaunch[] launches) { - closeServers(Arrays.stream(launches).map(LAUNCH_PROJECT_DATA::get)); - } - - private static void closeServers(Stream connectionData) { - connectionData.filter(Objects::nonNull).forEach(c -> { - try { - c.connection().close(); - } catch(IOException ex) { // ignore - } - }); + cleanupConnections(launches); } public void launchesAdded(ILaunch[] launches) { // ignore @@ -65,39 +47,51 @@ public void launchesAdded(ILaunch[] launches) { // ignore public void launchesChanged(ILaunch[] launches) { // ignore } + + private void cleanupConnections(ILaunch[] launches) { + Arrays.stream(launches).flatMap(launch -> getConnection(launch).stream()).forEach(con -> { + con.terminate(); + }); + } }); } static void openListenerConnection(ILaunch launch, VMArguments arguments) { try { if(MavenLaunchUtils.getMavenRuntime(launch.getLaunchConfiguration()) instanceof MavenEmbeddedRuntime) { - - Map projects = new ConcurrentHashMap<>(); - - MavenBuildConnection connection = M2EMavenBuildDataBridge.prepareConnection( - launch.getLaunchConfiguration().getName(), - d -> projects.put(new ArtifactKey(d.groupId, d.artifactId, d.version, null), d)); - - if(LAUNCH_PROJECT_DATA.putIfAbsent(launch, new MavenBuildConnectionData(projects, connection)) != null) { - connection.close(); + getConnection(launch).ifPresent(existing -> { + existing.terminate(); throw new IllegalStateException( "Maven bridge already created for launch of" + launch.getLaunchConfiguration().getName()); - } - arguments.append(connection.getMavenVMArguments()); + }); + MavenBuildConnectionProcess process = new MavenBuildConnectionProcess(launch); + process.connect(); + arguments.append(process.getMavenVMArguments()); } } catch(CoreException | IOException ex) { // ignore } } + public static Optional getConnection(ILaunch launch) { + for(IProcess process : launch.getProcesses()) { + if(process instanceof MavenBuildConnectionProcess connection) { + return Optional.of(connection); + } + } + return Optional.empty(); + } + static MavenProjectBuildData getBuildProject(ILaunch launch, String groupId, String artifactId, String version) { - MavenBuildConnectionData build = LAUNCH_PROJECT_DATA.get(launch); - if(build == null) { + Optional connection = getConnection(launch); + if(connection.isEmpty()) { return null; } + MavenBuildConnectionProcess process = connection.get(); + Map projects = process.getProjects(); ArtifactKey key = new ArtifactKey(groupId, artifactId, version, null); while(true) { - MavenProjectBuildData buildProject = build.projects().get(key); - if(buildProject != null || build.connection().isReadCompleted()) { + MavenProjectBuildData buildProject = projects.get(key); + if(buildProject != null || process.isTerminated()) { return buildProject; } Thread.onSpinWait(); // Await completion of project data read. It has to become available soon, since its GAV was printed on the console diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java index 923df503c7..cca16682eb 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java @@ -65,7 +65,7 @@ import org.eclipse.m2e.core.internal.IMavenConstants; import org.eclipse.m2e.core.project.IBuildProjectFileResolver; -import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenProjectBuildData; +import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData; /** diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java index 3fad7104e1..1b81a02493 100644 --- a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java @@ -26,6 +26,8 @@ import java.util.SortedMap; import java.util.TreeMap; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -121,6 +123,15 @@ public void launch(ILaunchConfiguration configuration, String mode, ILaunch laun this.programArguments = null; try { + Bundle bundle = Platform.getBundle("org.eclipse.unittest.ui"); + if(bundle != null) { + try { + bundle.start(); + } catch(BundleException ex) { + //we tried our best... + } + } + System.out.println(bundle); this.launchSupport = MavenRuntimeLaunchSupport.create(configuration, monitor); this.extensionsSupport = MavenLaunchExtensionsSupport.create(configuration, launch); this.preferencesService = Platform.getPreferencesService(); @@ -129,7 +140,6 @@ public void launch(ILaunchConfiguration configuration, String mode, ILaunch laun log.info(" mvn {}", getProgramArguments(configuration)); //$NON-NLS-1$ extensionsSupport.configureSourceLookup(configuration, launch, monitor); - super.launch(configuration, mode, launch, monitor); } finally { this.launch = null; diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestRunnerClient.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestRunnerClient.java new file mode 100644 index 0000000000..e64b98d76f --- /dev/null +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestRunnerClient.java @@ -0,0 +1,170 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.internal.launch.testing; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; + +import org.xml.sax.SAXException; + +import org.eclipse.debug.core.ILaunch; +import org.eclipse.unittest.launcher.ITestRunnerClient; +import org.eclipse.unittest.model.ITestRunSession; +import org.eclipse.unittest.model.ITestSuiteElement; + +import org.apache.maven.execution.ExecutionEvent.Type; + +import org.eclipse.m2e.internal.launch.MavenBuildProjectDataConnection; +import org.eclipse.m2e.internal.launch.testing.copied.TestRunHandler; +import org.eclipse.m2e.internal.maven.listener.MavenBuildListener; +import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData; +import org.eclipse.m2e.internal.maven.listener.MavenTestEvent; + + +/** + * + */ +public class MavenTestRunnerClient implements ITestRunnerClient, MavenBuildListener { + + private ITestRunSession session; + + private ThreadLocal projectData = new ThreadLocal<>(); + + private Map projectElementMap = new ConcurrentHashMap<>(); + + /** + * @param session + */ + public MavenTestRunnerClient(ITestRunSession session) { + this.session = session; + } + + public void startMonitoring() { + System.out.println("---- start monitoring ---"); + MavenBuildProjectDataConnection.getConnection(session.getLaunch()) + .ifPresent(con -> con.addMavenBuildListener(this)); + + } + + public void stopTest() { + stopMonitoring(); + ILaunch launch = session.getLaunch(); + try { + launch.terminate(); + } catch(Exception ex) { + } + } + + public void stopMonitoring() { + System.out.println("---- stop Monitoring ----\r\n"); + MavenBuildProjectDataConnection.getConnection(session.getLaunch()) + .ifPresent(con -> con.removeMavenBuildListener(this)); + } + + /** + * @return the session + */ + public ITestRunSession getSession() { + return this.session; + } + + public void projectStarted(MavenProjectBuildData data) { + this.projectData.set(data); + } + + boolean started; + + public void onTestEvent(MavenTestEvent mavenTestEvent) { + System.out.println("MavenTestRunnerClient.onTestEvent()"); + if(mavenTestEvent.getType() == Type.MojoSucceeded || mavenTestEvent.getType() == Type.MojoFailed) { + MavenProjectBuildData buildData = projectData.get(); +// Display.getDefault().execute(() -> { + + //in any case look for the tests... + Path reportDirectory = mavenTestEvent.getReportDirectory(); + if(Files.isDirectory(reportDirectory)) { + SAXParser parser = getParser(); + if(parser == null) { + return; + } + ensureStarted(); + ITestSuiteElement projectSuite = getProjectSuite(buildData); + try (Stream list = Files.list(reportDirectory)) { + Iterator iterator = list.iterator(); + while(iterator.hasNext()) { + Path path = iterator.next(); + System.out.println("Scan result file " + path); + parseFile(path, parser, projectSuite); + } + } catch(IOException ex) { + } + } +// }); + } + } + + private synchronized void ensureStarted() { + if(!started) { + session.notifyTestSessionStarted(null); + started = true; + } + } + + /** + * @return + */ + private ITestSuiteElement getProjectSuite(MavenProjectBuildData buildData) { + return projectElementMap.computeIfAbsent(buildData, data -> { + Path basedir = data.projectBasedir; + ITestSuiteElement suite = session.newTestSuite(System.currentTimeMillis() + basedir.toString(), + basedir.getFileName().toString(), null, null, data.groupId + ":" + data.artifactId, null); + session.notifyTestStarted(suite); + return suite; + }); + } + + @SuppressWarnings("restriction") + private SAXParser getParser() { + try { + return org.eclipse.core.internal.runtime.XmlProcessorFactory.createSAXParserWithErrorOnDOCTYPE(); + } catch(ParserConfigurationException ex) { + } catch(SAXException ex) { + } + return null; + } + + private void parseFile(Path path, SAXParser parser, ITestSuiteElement parent) { + final TestRunHandler handler = new TestRunHandler(session, parent); + try { + parser.parse(Files.newInputStream(path), handler); + } catch(SAXException | IOException ex) { + //can't read then... + } + } + + public void close() { + if(started) { + session.notifyTestSessionCompleted(null); + } + } + +} diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestViewSupport.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestViewSupport.java new file mode 100644 index 0000000000..ae3c2c4928 --- /dev/null +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/MavenTestViewSupport.java @@ -0,0 +1,65 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ + +package org.eclipse.m2e.internal.launch.testing; + +import java.util.Collection; +import java.util.List; + +import org.eclipse.core.text.StringMatcher; +import org.eclipse.debug.core.ILaunchConfiguration; +import org.eclipse.jface.action.IAction; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.unittest.launcher.ITestRunnerClient; +import org.eclipse.unittest.model.ITestCaseElement; +import org.eclipse.unittest.model.ITestElement; +import org.eclipse.unittest.model.ITestRunSession; +import org.eclipse.unittest.model.ITestSuiteElement; +import org.eclipse.unittest.ui.ITestViewSupport; + + +public class MavenTestViewSupport implements ITestViewSupport { + + public ITestRunnerClient newTestRunnerClient(ITestRunSession session) { + return new MavenTestRunnerClient(session); + } + + public Collection getTraceExclusionFilterPatterns() { + return null; + } + + public IAction getOpenTestAction(Shell shell, ITestCaseElement testCase) { + return null; + } + + public IAction getOpenTestAction(Shell shell, ITestSuiteElement testSuite) { + return null; + } + + public IAction createOpenEditorAction(Shell shell, ITestElement failure, String traceLine) { + return null; + } + + public Runnable createShowStackTraceInConsoleViewActionDelegate(ITestElement failedTest) { + return null; + } + + public ILaunchConfiguration getRerunLaunchConfiguration(List testElements) { + return null; + } + + public String getDisplayName() { + return "Maven"; + } + +} diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/copied/IXMLTags.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/copied/IXMLTags.java new file mode 100644 index 0000000000..aaa636ee4d --- /dev/null +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/copied/IXMLTags.java @@ -0,0 +1,141 @@ +/******************************************************************************* + * Copyright (c) 2007, 2017 IBM Corporation and others. + * + * 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 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.m2e.internal.launch.testing.copied; + +interface IXMLTags { + + String NODE_TESTRUN = "testrun"; //$NON-NLS-1$ + + String NODE_TESTSUITES = "testsuites"; //$NON-NLS-1$ + + String NODE_TESTSUITE = "testsuite"; //$NON-NLS-1$ + + String NODE_PROPERTIES = "properties"; //$NON-NLS-1$ + + String NODE_PROPERTY = "property"; //$NON-NLS-1$ + + String NODE_TESTCASE = "testcase"; //$NON-NLS-1$ + + String NODE_ERROR = "error"; //$NON-NLS-1$ + + String NODE_FAILURE = "failure"; //$NON-NLS-1$ + + String NODE_EXPECTED = "expected"; //$NON-NLS-1$ + + String NODE_ACTUAL = "actual"; //$NON-NLS-1$ + + String NODE_SYSTEM_OUT = "system-out"; //$NON-NLS-1$ + + String NODE_SYSTEM_ERR = "system-err"; //$NON-NLS-1$ + + String NODE_SKIPPED = "skipped"; //$NON-NLS-1$ + + /** + * value: String + */ + String ATTR_NAME = "name"; //$NON-NLS-1$ + + /** + * value: String + */ + String ATTR_LAUNCH_CONFIG_NAME = "launch_config_name"; //$NON-NLS-1$ + + /** + * value: Integer + */ + String ATTR_TESTS = "tests"; //$NON-NLS-1$ + + /** + * value: Integer + */ + String ATTR_STARTED = "started"; //$NON-NLS-1$ + + /** + * value: Integer + */ + String ATTR_FAILURES = "failures"; //$NON-NLS-1$ + + /** + * value: Integer + */ + String ATTR_ERRORS = "errors"; //$NON-NLS-1$ + + /** + * value: Boolean + */ + String ATTR_IGNORED = "ignored"; //$NON-NLS-1$ + + /** + * value: String + */ + String ATTR_PACKAGE = "package"; //$NON-NLS-1$ + + /** + * value: String + */ + String ATTR_ID = "id"; //$NON-NLS-1$ + + /** + * value: String + */ + String ATTR_CLASSNAME = "classname"; //$NON-NLS-1$ + + /** + * value: Boolean + */ + String ATTR_INCOMPLETE = "incomplete"; //$NON-NLS-1$ + + /** + * value: Duration.toString() + */ + String ATTR_START_TIME = "startTime"; //$NON-NLS-1$ + + /** + * value: Double (duration in seconds) + */ + String ATTR_DURATION = "time"; //$NON-NLS-1$ + + /** + * value: String + */ + String ATTR_MESSAGE = "message"; //$NON-NLS-1$ + + /** + * value: String + */ + String ATTR_DISPLAY_NAME = "displayname"; //$NON-NLS-1$ + + /** + * value: Boolean + */ + String ATTR_DYNAMIC_TEST = "dynamicTest"; //$NON-NLS-1$ + + /** + * value: String + */ + String ATTR_DATA = "data"; //$NON-NLS-1$ + + /** + * value: String + */ + String ATTR_INCLUDE_TAGS = "include_tags"; //$NON-NLS-1$ + + /** + * value: String + */ + String ATTR_EXCLUDE_TAGS = "exclude_tags"; //$NON-NLS-1$ + +// public static final String ATTR_TYPE= "type"; //$NON-NLS-1$ +} diff --git a/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/copied/TestRunHandler.java b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/copied/TestRunHandler.java new file mode 100644 index 0000000000..71a961d378 --- /dev/null +++ b/org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/testing/copied/TestRunHandler.java @@ -0,0 +1,309 @@ +/******************************************************************************* + * Copyright (c) 2007, 2017 IBM Corporation and others. + * + * 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 + * + * Contributors: + * IBM Corporation - initial API and implementation + *******************************************************************************/ + +package org.eclipse.m2e.internal.launch.testing.copied; + +import java.util.Stack; +import java.util.concurrent.atomic.AtomicInteger; + +import org.xml.sax.Attributes; +import org.xml.sax.Locator; +import org.xml.sax.SAXException; +import org.xml.sax.SAXParseException; +import org.xml.sax.helpers.DefaultHandler; + +import org.eclipse.core.runtime.OperationCanceledException; +import org.eclipse.unittest.model.ITestCaseElement; +import org.eclipse.unittest.model.ITestElement; +import org.eclipse.unittest.model.ITestElement.FailureTrace; +import org.eclipse.unittest.model.ITestElement.Result; +import org.eclipse.unittest.model.ITestRunSession; +import org.eclipse.unittest.model.ITestSuiteElement; + + +public class TestRunHandler extends DefaultHandler { + + private static final AtomicInteger fid = new AtomicInteger(); + + private ITestRunSession fTestRunSession; + + private ITestSuiteElement fTestSuite; + + private ITestCaseElement fTestCase; + + private final Stack fNotRun = new Stack<>(); + + private StringBuilder fFailureBuffer; + + private boolean fInExpected; + + private boolean fInActual; + + private StringBuilder fExpectedBuffer; + + private StringBuilder fActualBuffer; + + private Locator fLocator; + + private Result fStatus; + + private int fLastReportedLine; + + /** + * Constructs a default {@link TestRunHandler} object instance + */ + public TestRunHandler(ITestRunSession session, ITestSuiteElement parent) { + fTestRunSession = session; + this.fTestSuite = parent; + } + + @Override + public void setDocumentLocator(Locator locator) { + fLocator = locator; + } + + @Override + public void startDocument() throws SAXException { + // Nothing to do + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + try { + if(fLocator != null) { + int line = fLocator.getLineNumber(); + if(line - 20 >= fLastReportedLine) { + line -= line % 20; + fLastReportedLine = line; + } + } + + if(Thread.interrupted()) + throw new OperationCanceledException(); + + switch(qName) { + case IXMLTags.NODE_TESTRUN: + break; + case IXMLTags.NODE_TESTSUITES: + break; + case IXMLTags.NODE_TESTSUITE: { + String name = attributes.getValue(IXMLTags.ATTR_NAME); + String pack = attributes.getValue(IXMLTags.ATTR_PACKAGE); + String suiteName = pack == null ? name : pack + "." + name; //$NON-NLS-1$ + String displayName = attributes.getValue(IXMLTags.ATTR_DISPLAY_NAME); + String data = attributes.getValue(IXMLTags.ATTR_DATA); + if(data != null && data.isBlank()) { + data = null; + } + fTestSuite = fTestRunSession.newTestSuite(getNextId(), suiteName, null, fTestSuite, displayName, data); + fNotRun.push(Boolean.valueOf(attributes.getValue(IXMLTags.ATTR_INCOMPLETE))); + fTestRunSession.notifyTestStarted(fTestSuite); + break; + } + // not interested + case IXMLTags.NODE_PROPERTIES: + case IXMLTags.NODE_PROPERTY: + break; + case IXMLTags.NODE_TESTCASE: { + String name = attributes.getValue(IXMLTags.ATTR_NAME); + String classname = attributes.getValue(IXMLTags.ATTR_CLASSNAME); + String testName = name + '(' + classname + ')'; + //TODO dynamic test not supported by API! +// boolean isDynamicTest = Boolean.valueOf(attributes.getValue(IXMLTags.ATTR_DYNAMIC_TEST)).booleanValue(); + String displayName = attributes.getValue(IXMLTags.ATTR_DISPLAY_NAME); + String data = attributes.getValue(IXMLTags.ATTR_DATA); + if(data != null && data.isBlank()) { + data = null; + } + fTestCase = fTestRunSession.newTestCase(getNextId(), testName, fTestSuite, displayName, data); + fNotRun.push(Boolean.valueOf(attributes.getValue(IXMLTags.ATTR_INCOMPLETE))); + //TODO incomplete versus ignored?!? +// fTestCase.setIgnored(Boolean.parseBoolean(attributes.getValue(IXMLTags.ATTR_IGNORED))); + fTestRunSession.notifyTestStarted(fTestCase); + break; + } + case IXMLTags.NODE_ERROR: + // TODO: multiple failures: https://bugs.eclipse.org/bugs/show_bug.cgi?id=125296 + fStatus = Result.ERROR; + fFailureBuffer = new StringBuilder(); + break; + case IXMLTags.NODE_FAILURE: + // TODO: multiple failures: https://bugs.eclipse.org/bugs/show_bug.cgi?id=125296 + fStatus = Result.FAILURE; + fFailureBuffer = new StringBuilder(); + break; + case IXMLTags.NODE_EXPECTED: + fInExpected = true; + fExpectedBuffer = new StringBuilder(); + break; + case IXMLTags.NODE_ACTUAL: + fInActual = true; + fActualBuffer = new StringBuilder(); + break; + // not interested + case IXMLTags.NODE_SYSTEM_OUT: + case IXMLTags.NODE_SYSTEM_ERR: + break; + case IXMLTags.NODE_SKIPPED: + // before Ant 1.9.0: not an Ant JUnit tag, see + // https://bugs.eclipse.org/bugs/show_bug.cgi?id=276068 + // later: child of or , see + // https://issues.apache.org/bugzilla/show_bug.cgi?id=43969 + fStatus = Result.OK; + fFailureBuffer = new StringBuilder(); + String message = attributes.getValue(IXMLTags.ATTR_MESSAGE); + if(message != null) { + fFailureBuffer.append(message).append('\n'); + } + break; + default: + throw new SAXParseException("unknown node '" + qName + "'", fLocator); //$NON-NLS-1$//$NON-NLS-2$ + } + } catch(RuntimeException e) { + e.printStackTrace(); + } + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { + if(fInExpected) { + fExpectedBuffer.append(ch, start, length); + + } else if(fInActual) { + fActualBuffer.append(ch, start, length); + + } else if(fFailureBuffer != null) { + fFailureBuffer.append(ch, start, length); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + try { + switch(qName) { + // OK + case IXMLTags.NODE_TESTRUN: + break; + // OK + case IXMLTags.NODE_TESTSUITES: + break; + case IXMLTags.NODE_TESTSUITE: + handleTestElementEnd(fTestSuite); + fTestSuite = fTestSuite.getParent(); + // TODO: end suite: compare counters? + break; + // OK + case IXMLTags.NODE_PROPERTIES: + case IXMLTags.NODE_PROPERTY: + break; + case IXMLTags.NODE_TESTCASE: + handleTestElementEnd(fTestCase); + fTestCase = null; + break; + case IXMLTags.NODE_FAILURE: + case IXMLTags.NODE_ERROR: { + ITestElement testElement = fTestCase; + if(testElement == null) + testElement = fTestSuite; + handleFailure(testElement, false); + break; + } + case IXMLTags.NODE_EXPECTED: + fInExpected = false; + if(fFailureBuffer != null) { + // skip whitespace from before and nodes + fFailureBuffer.setLength(0); + } + break; + case IXMLTags.NODE_ACTUAL: + fInActual = false; + if(fFailureBuffer != null) { + // skip whitespace from before and nodes + fFailureBuffer.setLength(0); + } + break; + // OK + case IXMLTags.NODE_SYSTEM_OUT: + case IXMLTags.NODE_SYSTEM_ERR: + break; + case IXMLTags.NODE_SKIPPED: { + ITestElement testElement = fTestCase; + if(testElement == null) { + testElement = fTestSuite; + } + if(fFailureBuffer != null && fFailureBuffer.length() > 0) { + handleFailure(testElement, true); + } else if(fTestCase != null) { + fStatus = Result.IGNORED; + } else { // not expected + fTestRunSession.notifyTestFailed(testElement, Result.FAILURE, false, null); + } + break; + } + default: + handleUnknownNode(qName); + break; + } + } catch(RuntimeException e) { + e.printStackTrace(); + } + } + + private void handleTestElementEnd(ITestElement testElement) { + boolean notrun = fNotRun.pop().booleanValue(); + if(notrun) { + return; + } + fTestRunSession.notifyTestEnded(testElement, fStatus == Result.IGNORED); + } + + private void handleFailure(ITestElement testElement, boolean assumptionFailed) { + if(fFailureBuffer != null) { + fTestRunSession.notifyTestFailed(testElement, fStatus, assumptionFailed, + new FailureTrace(fFailureBuffer.toString(), toString(fExpectedBuffer), toString(fActualBuffer))); + fFailureBuffer = null; + fExpectedBuffer = null; + fActualBuffer = null; + fStatus = null; + } + } + + private String toString(StringBuilder buffer) { + return buffer != null ? buffer.toString() : null; + } + + private void handleUnknownNode(String qName) throws SAXException { + // TODO: just log if debug option is enabled? + String msg = "unknown node '" + qName + "'"; //$NON-NLS-1$//$NON-NLS-2$ + if(fLocator != null) { + msg += " at line " + fLocator.getLineNumber() + ", column " + fLocator.getColumnNumber(); //$NON-NLS-1$//$NON-NLS-2$ + } + throw new SAXException(msg); + } + + @Override + public void error(SAXParseException e) throws SAXException { + throw e; + } + + @Override + public void warning(SAXParseException e) throws SAXException { + throw e; + } + + private String getNextId() { + return fTestSuite.getId() + "." + fid.incrementAndGet(); + } + +} diff --git a/org.eclipse.m2e.maven.runtime/pom.xml b/org.eclipse.m2e.maven.runtime/pom.xml index b1146a7ed1..15d8e92376 100644 --- a/org.eclipse.m2e.maven.runtime/pom.xml +++ b/org.eclipse.m2e.maven.runtime/pom.xml @@ -20,7 +20,7 @@ org.eclipse.m2e.maven.runtime - 3.9.600-SNAPSHOT + 3.9.601-SNAPSHOT jar M2E Embedded Maven Runtime (includes Incubating components) diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java index 72d113fe06..13e0b27ff3 100644 --- a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/M2EMavenBuildDataBridge.java @@ -21,36 +21,26 @@ import java.nio.ByteBuffer; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; -import java.nio.file.Path; import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.StringJoiner; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Consumer; import javax.inject.Named; import javax.inject.Singleton; -import org.apache.maven.eventspy.EventSpy; -import org.apache.maven.execution.ExecutionEvent; +import org.apache.maven.AbstractMavenLifecycleParticipant; +import org.apache.maven.MavenExecutionException; import org.apache.maven.execution.ExecutionEvent.Type; -import org.apache.maven.project.MavenProject; +import org.apache.maven.execution.MavenSession; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** - * This {@link EventSpy} listens to certain events within a Maven build JVM and - * sends certain data (e.g. about projects being built) to the JVM of the - * Eclipse IDE that launched the Maven build JVM. - * - * @author Hannes Wellmann - * + * Bridge between the remote running maven and the local m2e to exchange event + * messages and information. */ -@Named +@Named("m2e") @Singleton -public class M2EMavenBuildDataBridge implements EventSpy { +public class M2EMavenBuildDataBridge extends AbstractMavenLifecycleParticipant { private static final String SOCKET_FILE_PROPERTY_NAME = "m2e.build.project.data.socket.port"; private static final String DATA_SET_SEPARATOR = ";;"; @@ -60,7 +50,7 @@ public class M2EMavenBuildDataBridge implements EventSpy { private SocketChannel writeChannel; @Override - public void init(Context context) throws IOException { + public void afterSessionStart(MavenSession session) throws MavenExecutionException { String socketPort = System.getProperty(SOCKET_FILE_PROPERTY_NAME); if (socketPort != null) { try { @@ -76,94 +66,51 @@ public void init(Context context) throws IOException { } @Override - public void close() throws IOException { - writeChannel.close(); + public void afterSessionEnd(MavenSession session) throws MavenExecutionException { + try { + writeChannel.close(); + } catch (IOException e) { + // we can't do much here anyways... + } } - @Override - public void onEvent(Object event) throws Exception { - if (writeChannel != null && event instanceof ExecutionEvent - && ((ExecutionEvent) event).getType() == Type.ProjectStarted) { - - String message = serializeProjectData(((ExecutionEvent) event).getProject()); + boolean isActive() { + return writeChannel != null; + } - ByteBuffer buffer = ByteBuffer.wrap(message.getBytes()); - synchronized (writeChannel) { + void sendMessage(String msg) { + SocketChannel channel = writeChannel; + if (channel != null) { + ByteBuffer buffer = ByteBuffer.wrap((msg + DATA_SET_SEPARATOR).getBytes()); + synchronized (channel) { while (buffer.hasRemaining()) { - writeChannel.write(buffer); + try { + channel.write(buffer); + } catch (IOException e) { + LOGGER.warn("Can't forward message to m2e: " + e); + } } } } } - private static String serializeProjectData(MavenProject project) { - StringJoiner data = new StringJoiner(","); - add(data, "groupId", project.getGroupId()); - add(data, "artifactId", project.getArtifactId()); - add(data, "version", project.getVersion()); - add(data, "file", project.getFile()); - add(data, "basedir", project.getBasedir()); - add(data, "build.directory", project.getBuild().getDirectory()); - return data.toString() + DATA_SET_SEPARATOR; - } - private static void add(StringJoiner data, String key, Object value) { - data.add(key + "=" + value); - } - /** - *

- * This method is supposed to be called from M2E within the Eclipse-IDE JVM. - *

- * - * @param dataSet the data-set to parse - * @return the {@link MavenProjectBuildData} parsed from the given string - */ - private static MavenProjectBuildData parseMavenBuildProject(String dataSet) { - Map data = new HashMap<>(8); - for (String entry : dataSet.split(",")) { - String[] keyValue = entry.split("="); - if (keyValue.length != 2) { - throw new IllegalStateException("Invalid data-set format" + dataSet); - } - data.put(keyValue[0], keyValue[1]); - } - return new MavenProjectBuildData(data); - } - public static final class MavenProjectBuildData { - public final String groupId; - public final String artifactId; - public final String version; - public final Path projectBasedir; - public final Path projectFile; - public final Path projectBuildDirectory; - - MavenProjectBuildData(Map data) { - if (data.size() != 6) { - throw new IllegalArgumentException(); - } - this.groupId = Objects.requireNonNull(data.get("groupId")); - this.artifactId = Objects.requireNonNull(data.get("artifactId")); - this.version = Objects.requireNonNull(data.get("version")); - this.projectBasedir = Paths.get(data.get("basedir")); - this.projectFile = Paths.get(data.get("file")); - this.projectBuildDirectory = Paths.get(data.get("build.directory")); - } - } + /** * Prepares the connection to a {@code Maven build JVM} to be launched and is * intended to be called from the Eclipse IDE JVM. * * @param label the label of the listener thread - * @param datasetListener the listener, which is notified whenever a new + * @param buildListener the listener, which is notified whenever a new * {@link MavenProjectBuildData MavenProjectBuildDataSet} * has arived from the Maven VM in the Eclipse-IDE VM. * @return the preapre {@link MavenBuildConnection} * @throws IOException */ - public static MavenBuildConnection prepareConnection(String label, Consumer datasetListener) + public static MavenBuildConnection prepareConnection(String label, MavenBuildListener buildListener) throws IOException { // TODO: use UNIX domain socket once Java-17 is required by Maven @@ -187,9 +134,21 @@ public static MavenBuildConnection prepareConnection(String label, Consumer= 0;) { String dataSet = message.substring(0, terminatorIndex); message.delete(0, terminatorIndex + DATA_SET_SEPARATOR.length()); - - MavenProjectBuildData buildData = parseMavenBuildProject(dataSet); - datasetListener.accept(buildData); + if (dataSet.startsWith(M2eEventSpy.PROJECT_START_EVENT)) { + MavenProjectBuildData buildData = MavenProjectBuildData + .parseMavenBuildProject( + dataSet.substring(M2eEventSpy.PROJECT_START_EVENT.length())); + buildListener.projectStarted(buildData); + } else if (dataSet.startsWith(M2eMojoExecutionListener.TEST_START_EVENT)) { + String path = dataSet.substring(M2eMojoExecutionListener.TEST_START_EVENT.length()); + buildListener.onTestEvent(new MavenTestEvent(Type.MojoStarted, Paths.get(path))); + } else if (dataSet.startsWith(M2eMojoExecutionListener.TEST_END_EVENT)) { + String path = dataSet.substring(M2eMojoExecutionListener.TEST_END_EVENT.length()); + buildListener.onTestEvent(new MavenTestEvent(Type.MojoSucceeded, Paths.get(path))); + } else if (dataSet.startsWith(M2eMojoExecutionListener.TEST_END_FAILED_EVENT)) { + String path = dataSet.substring(M2eMojoExecutionListener.TEST_END_EVENT.length()); + buildListener.onTestEvent(new MavenTestEvent(Type.MojoFailed, Paths.get(path))); + } } // Explicit cast for compatibility with covariant return type on JDK 9's // ByteBuffer @@ -198,6 +157,7 @@ public static MavenBuildConnection prepareConnection(String label, Consumer testExecutionPathMap = new ConcurrentHashMap<>(); + private BuildPluginManager buildPluginManager; + + @Inject + public M2eMojoExecutionListener(M2EMavenBuildDataBridge bridge, BuildPluginManager buildPluginManager) { + this.bridge = bridge; + this.buildPluginManager = buildPluginManager; + } + + @Override + public void beforeMojoExecution(MojoExecutionEvent event) throws MojoExecutionException { + if (!bridge.isActive()) { + return; + } + MojoExecution execution = event.getExecution(); + // it would be better if we have a common interface in plexus-build api that + // mojos can implement instead then we just need to query for the interface + boolean bndTest = isBndTest(execution); + if (isSurefireTest(execution) || isFailsafeTest(execution) || bndTest || isTychoSurefire(execution)) { + File reportsDirectory = getMojoParameterValue(execution, bndTest ? "reportsDir" : "reportsDirectory", + File.class, event.getSession()); + if (reportsDirectory != null) { + testExecutionPathMap.put(execution, reportsDirectory.toPath()); + bridge.sendMessage(TEST_START_EVENT + reportsDirectory.getAbsolutePath()); + } + } + } + + private static boolean isTychoSurefire(MojoExecution execution) { + return execution.getPlugin().getId().startsWith("org.eclipse.tycho:tycho-surefire-plugin:") + && ("test".equals(execution.getGoal()) || "plugin-test".equals(execution.getGoal()) + || "bnd-test".equals(execution.getGoal())); + + } + + private static boolean isFailsafeTest(MojoExecution execution) { + return execution.getPlugin().getId().startsWith("org.apache.maven.plugins:maven-failsafe-plugin:") + && "integration-test".equals(execution.getGoal()); + } + + private static boolean isSurefireTest(MojoExecution execution) { + return execution.getPlugin().getId().startsWith("org.apache.maven.plugins:maven-surefire-plugin:") + && "test".equals(execution.getGoal()); + } + + private static boolean isBndTest(MojoExecution execution) { + return execution.getPlugin().getId().startsWith("biz.aQute.bnd:bnd-testing-maven-plugin:") + && "testing".equals(execution.getGoal()); + } + + private void afterMojoExecution(MojoExecutionEvent event, String type) { + if (!bridge.isActive()) { + return; + } + Path testPath = testExecutionPathMap.remove(event.getExecution()); + if (testPath != null) { + bridge.sendMessage(type + testPath.toAbsolutePath().toString()); + } + } + + private T getMojoParameterValue(MojoExecution mojoExecution, String parameter, Class asType, + MavenSession session) { + Xpp3Dom dom = mojoExecution.getConfiguration(); + if (dom == null) { + return null; + } + PlexusConfiguration configuration = new XmlPlexusConfiguration(dom).getChild(parameter); + if (configuration == null) { + return null; + } + MojoDescriptor mojoDescriptor = mojoExecution.getMojoDescriptor(); + ClassRealm pluginRealm; + try { + pluginRealm = buildPluginManager.getPluginRealm(session, mojoDescriptor.getPluginDescriptor()); + ExpressionEvaluator expressionEvaluator = new PluginParameterExpressionEvaluator(session, mojoExecution); + ConfigurationConverter typeConverter = converterLookup.lookupConverterForType(asType); + + Object value = typeConverter.fromConfiguration(converterLookup, configuration, asType, + mojoDescriptor.getImplementationClass(), pluginRealm, expressionEvaluator, null); + return asType.cast(value); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + @Override + public void afterMojoExecutionSuccess(MojoExecutionEvent event) throws MojoExecutionException { + afterMojoExecution(event, TEST_END_EVENT); + } + + @Override + public void afterExecutionFailure(MojoExecutionEvent event) { + afterMojoExecution(event, TEST_END_FAILED_EVENT); + + } + +} diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildListener.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildListener.java new file mode 100644 index 0000000000..dce5b75ae9 --- /dev/null +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenBuildListener.java @@ -0,0 +1,27 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ +package org.eclipse.m2e.internal.maven.listener; + +/** + * Interface to implement when wanted to be informed about remote maven events + */ +public interface MavenBuildListener extends AutoCloseable { + + void projectStarted(MavenProjectBuildData data); + + void onTestEvent(MavenTestEvent mavenTestEvent); + + @Override + void close(); + +} diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenProjectBuildData.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenProjectBuildData.java new file mode 100644 index 0000000000..02b2bddecd --- /dev/null +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenProjectBuildData.java @@ -0,0 +1,79 @@ +/******************************************************************************** + * Copyright (c) 2022, 2024 Hannes Wellmann 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: + * Hannes Wellmann - initial API and implementation + * Christoph Läubrich - factored out + ********************************************************************************/ +package org.eclipse.m2e.internal.maven.listener; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; + +import org.apache.maven.project.MavenProject; + +public final class MavenProjectBuildData { + public final String groupId; + public final String artifactId; + public final String version; + public final Path projectBasedir; + public final Path projectFile; + public final Path projectBuildDirectory; + + MavenProjectBuildData(Map data) { + if (data.size() != 6) { + throw new IllegalArgumentException(); + } + this.groupId = Objects.requireNonNull(data.get("groupId")); + this.artifactId = Objects.requireNonNull(data.get("artifactId")); + this.version = Objects.requireNonNull(data.get("version")); + this.projectBasedir = Paths.get(data.get("basedir")); + this.projectFile = Paths.get(data.get("file")); + this.projectBuildDirectory = Paths.get(data.get("build.directory")); + } + + static String serializeProjectData(MavenProject project) { + StringJoiner data = new StringJoiner(","); + add(data, "groupId", project.getGroupId()); + add(data, "artifactId", project.getArtifactId()); + add(data, "version", project.getVersion()); + add(data, "file", project.getFile()); + add(data, "basedir", project.getBasedir()); + add(data, "build.directory", project.getBuild().getDirectory()); + return data.toString(); + } + + /** + *

+ * This method is supposed to be called from M2E within the Eclipse-IDE JVM. + *

+ * + * @param dataSet the data-set to parse + * @return the {@link MavenProjectBuildData} parsed from the given string + */ + static MavenProjectBuildData parseMavenBuildProject(String dataSet) { + Map data = new HashMap<>(8); + for (String entry : dataSet.split(",")) { + String[] keyValue = entry.split("="); + if (keyValue.length != 2) { + throw new IllegalStateException("Invalid data-set format" + dataSet); + } + data.put(keyValue[0], keyValue[1]); + } + return new MavenProjectBuildData(data); + } + + private static void add(StringJoiner data, String key, Object value) { + data.add(key + "=" + value); + } +} \ No newline at end of file diff --git a/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenTestEvent.java b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenTestEvent.java new file mode 100644 index 0000000000..bf18b02295 --- /dev/null +++ b/org.eclipse.m2e.maven.runtime/src/main/java/org/eclipse/m2e/internal/maven/listener/MavenTestEvent.java @@ -0,0 +1,43 @@ +/******************************************************************************** + * Copyright (c) 2024 Christoph Läubrich 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: + * Christoph Läubrich - initial API and implementation + ********************************************************************************/ +package org.eclipse.m2e.internal.maven.listener; + +import java.nio.file.Path; + +import org.apache.maven.execution.ExecutionEvent; + +public class MavenTestEvent { + + + private Path reportDirectory; + private ExecutionEvent.Type type; + + public MavenTestEvent(ExecutionEvent.Type type, Path reportDirectory) { + this.type = type; + this.reportDirectory = reportDirectory; + } + + public Path getReportDirectory() { + return reportDirectory; + } + + public ExecutionEvent.Type getType() { + return type; + } + + @Override + public String toString() { + return "MavenTestEvent [type=" + type + ", reportDirectory=" + reportDirectory + "]"; + } + +} diff --git a/pom.xml b/pom.xml index 4950bcd1b8..c39883b38e 100644 --- a/pom.xml +++ b/pom.xml @@ -103,7 +103,7 @@ org.eclipse.m2e org.eclipse.m2e.maven.runtime - 3.9.600-SNAPSHOT + 3.9.601-SNAPSHOT diff --git a/target-platform/target-platform.target b/target-platform/target-platform.target index f2730674ef..03656ce2b3 100644 --- a/target-platform/target-platform.target +++ b/target-platform/target-platform.target @@ -12,6 +12,7 @@ +