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 @@
+