Skip to content

Commit

Permalink
Trace test execution directories in the remote maven execution
Browse files Browse the repository at this point in the history
Currently running tests in maven via m2e is possible but not very
convenient as one still needs to navigate to the results then open the
correct file. Another pitfall is that even if one has opened the file
once, the "classic" JUnit view not update the view even when the file
changes afterwards.

This now adds a new process tracking of test executions directories that
then can be watched on the m2e side and display the new advanced JUnit
view when the run has finished.
  • Loading branch information
laeubi committed Feb 27, 2024
1 parent 8789e4c commit 8e7e21a
Show file tree
Hide file tree
Showing 18 changed files with 895 additions and 130 deletions.
4 changes: 3 additions & 1 deletion org.eclipse.m2e.launching/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions org.eclipse.m2e.launching/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -243,5 +243,12 @@
class="org.eclipse.m2e.internal.launch.MavenConsoleLineTracker"
processType="java"/>
</extension>
<extension
point="org.eclipse.unittest.ui.unittestViewSupport">
<viewSupport
class="org.eclipse.m2e.internal.launch.testing.MavenTestViewSupport"
id="org.eclipse.m2e.launching.testViewSupport">
</viewSupport>
</extension>

</plugin>
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
/********************************************************************************
* 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 org.eclipse.core.runtime.Status;
import org.eclipse.debug.core.DebugException;
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<String, String> attributes = new HashMap<>();

private Map<ArtifactKey, MavenProjectBuildData> projects = new ConcurrentHashMap<>();

private MavenBuildConnection connection;

private List<MavenBuildListener> buildListeners = new CopyOnWriteArrayList<>();

public MavenBuildConnectionProcess(ILaunch launch) {
this.launch = launch;
launch.addProcess(this);
attributes.put(IProcess.ATTR_PROCESS_TYPE, "m2e-build-endpoint");
}

public <T> T getAdapter(Class<T> adapter) {
return null;
}

public boolean canTerminate() {
return true;
}

public boolean isTerminated() {
return connection == null || connection.isReadCompleted();
}

public void terminate() throws DebugException {
if(connection != null) {
try {
connection.close();
} catch(IOException ex) {
throw new DebugException(Status.error("Terminate failed", ex));
}
connection = null;
}
}

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);
}

@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<ArtifactKey, MavenProjectBuildData> 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() {
for(MavenBuildListener mavenBuildListener : buildListeners) {
mavenBuildListener.close();
}
buildListeners.clear();
launch.removeProcess(MavenBuildConnectionProcess.this);
}
});
}

String getMavenVMArguments() throws IOException {
return connection.getMavenVMArguments();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,50 +16,39 @@
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.DebugException;
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<ArtifactKey, MavenProjectBuildData> projects,
MavenBuildConnection connection) {
}

private static final Map<ILaunch, MavenBuildConnectionData> 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));
}

public void launchesTerminated(ILaunch[] launches) {
closeServers(Arrays.stream(launches).map(LAUNCH_PROJECT_DATA::get));
}

private static void closeServers(Stream<MavenBuildConnectionData> connectionData) {
connectionData.filter(Objects::nonNull).forEach(c -> {
Arrays.stream(launches).flatMap(launch -> getConnection(launch).stream()).forEach(con -> {
try {
c.connection().close();
} catch(IOException ex) { // ignore
con.terminate();
} catch(DebugException ex) {
//ignore
}
});
}

public void launchesTerminated(ILaunch[] launches) {
launchesRemoved(launches);
}

public void launchesAdded(ILaunch[] launches) { // ignore
}

Expand All @@ -71,33 +60,42 @@ public void launchesChanged(ILaunch[] launches) { // ignore
static void openListenerConnection(ILaunch launch, VMArguments arguments) {
try {
if(MavenLaunchUtils.getMavenRuntime(launch.getLaunchConfiguration()) instanceof MavenEmbeddedRuntime) {

Map<ArtifactKey, MavenProjectBuildData> 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 -> {
try {
existing.terminate();
} catch(DebugException ex) {
}
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<MavenBuildConnectionProcess> 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<MavenBuildConnectionProcess> connection = getConnection(launch);
if(connection.isEmpty()) {
return null;
}
MavenBuildConnectionProcess process = connection.get();
Map<ArtifactKey, MavenProjectBuildData> 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;


/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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();
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 8e7e21a

Please sign in to comment.