Skip to content

Commit 8e7e21a

Browse files
committed
Trace test execution directories in the remote maven execution
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.
1 parent 8789e4c commit 8e7e21a

18 files changed

Lines changed: 895 additions & 130 deletions

org.eclipse.m2e.launching/META-INF/MANIFEST.MF

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="[3.27.0,4.0.0)",
1818
org.eclipse.m2e.maven.runtime;bundle-version="[3.8.6,4.0.0)",
1919
org.eclipse.m2e.core;bundle-version="[2.0.0,3.0.0)",
2020
org.eclipse.m2e.core.ui;bundle-version="[2.0.0,3.0.0)",
21-
org.eclipse.m2e.workspace.cli;bundle-version="0.1.0"
21+
org.eclipse.m2e.workspace.cli;bundle-version="0.1.0",
22+
org.eclipse.jdt.junit.core,
23+
org.eclipse.unittest.ui;bundle-version="1.1.200"
2224
Bundle-ActivationPolicy: lazy
2325
Bundle-RequiredExecutionEnvironment: JavaSE-17
2426
Bundle-Vendor: %Bundle-Vendor

org.eclipse.m2e.launching/plugin.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,5 +243,12 @@
243243
class="org.eclipse.m2e.internal.launch.MavenConsoleLineTracker"
244244
processType="java"/>
245245
</extension>
246+
<extension
247+
point="org.eclipse.unittest.ui.unittestViewSupport">
248+
<viewSupport
249+
class="org.eclipse.m2e.internal.launch.testing.MavenTestViewSupport"
250+
id="org.eclipse.m2e.launching.testViewSupport">
251+
</viewSupport>
252+
</extension>
246253

247254
</plugin>

org.eclipse.m2e.launching/src/org/eclipse/m2e/actions/ExecutePomAction.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
import org.eclipse.ui.IFileEditorInput;
5757
import org.eclipse.ui.PlatformUI;
5858
import org.eclipse.ui.dialogs.ElementListSelectionDialog;
59+
import org.eclipse.unittest.ui.ConfigureViewerSupport;
5960

6061
import org.eclipse.m2e.core.MavenPlugin;
6162
import org.eclipse.m2e.core.embedder.IMavenExecutableLocation;
@@ -75,6 +76,10 @@
7576
* @author Eugene Kuleshov
7677
*/
7778
public class ExecutePomAction implements ILaunchShortcut, IExecutableExtension, ILaunchShortcut2 {
79+
80+
public static final ConfigureViewerSupport TEST_RESULT_LISTENER_CONFIGURER = new ConfigureViewerSupport(
81+
"org.eclipse.m2e.launching.testViewSupport");
82+
7883
private static final Logger log = LoggerFactory.getLogger(ExecutePomAction.class);
7984

8085
private boolean showDialog = false;
@@ -212,7 +217,6 @@ private ILaunchConfiguration createLaunchConfiguration(IContainer basedir, Strin
212217
workingCopy.setAttribute(IDebugUIConstants.ATTR_PRIVATE, true);
213218
workingCopy.setAttribute(RefreshTab.ATTR_REFRESH_SCOPE, "${project}"); //$NON-NLS-1$
214219
workingCopy.setAttribute(RefreshTab.ATTR_REFRESH_RECURSIVE, true);
215-
216220
setProjectConfiguration(workingCopy, basedir);
217221

218222
// TODO when launching Maven with debugger consider to add the following property
@@ -237,6 +241,7 @@ private void setProjectConfiguration(ILaunchConfigurationWorkingCopy workingCopy
237241
workingCopy.setAttribute(MavenLaunchConstants.ATTR_PROFILES, selectedProfiles);
238242
}
239243
}
244+
TEST_RESULT_LISTENER_CONFIGURER.apply(workingCopy);
240245
}
241246

242247
private ILaunchConfiguration getLaunchConfiguration(IContainer basedir, String mode) {
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
/********************************************************************************
2+
* Copyright (c) 2024 Christoph Läubrich and others
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0.
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Christoph Läubrich - initial API and implementation
12+
********************************************************************************/
13+
14+
package org.eclipse.m2e.internal.launch;
15+
16+
import java.io.IOException;
17+
import java.util.HashMap;
18+
import java.util.List;
19+
import java.util.Map;
20+
import java.util.concurrent.ConcurrentHashMap;
21+
import java.util.concurrent.CopyOnWriteArrayList;
22+
23+
import org.eclipse.core.runtime.Status;
24+
import org.eclipse.debug.core.DebugException;
25+
import org.eclipse.debug.core.ILaunch;
26+
import org.eclipse.debug.core.model.IProcess;
27+
import org.eclipse.debug.core.model.IStreamsProxy;
28+
29+
import org.eclipse.m2e.core.embedder.ArtifactKey;
30+
import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge;
31+
import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenBuildConnection;
32+
import org.eclipse.m2e.internal.maven.listener.MavenBuildListener;
33+
import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData;
34+
import org.eclipse.m2e.internal.maven.listener.MavenTestEvent;
35+
36+
37+
/**
38+
* A process that represents the remote connection to the maven process
39+
*/
40+
public class MavenBuildConnectionProcess implements IProcess {
41+
42+
private ILaunch launch;
43+
44+
private Map<String, String> attributes = new HashMap<>();
45+
46+
private Map<ArtifactKey, MavenProjectBuildData> projects = new ConcurrentHashMap<>();
47+
48+
private MavenBuildConnection connection;
49+
50+
private List<MavenBuildListener> buildListeners = new CopyOnWriteArrayList<>();
51+
52+
public MavenBuildConnectionProcess(ILaunch launch) {
53+
this.launch = launch;
54+
launch.addProcess(this);
55+
attributes.put(IProcess.ATTR_PROCESS_TYPE, "m2e-build-endpoint");
56+
}
57+
58+
public <T> T getAdapter(Class<T> adapter) {
59+
return null;
60+
}
61+
62+
public boolean canTerminate() {
63+
return true;
64+
}
65+
66+
public boolean isTerminated() {
67+
return connection == null || connection.isReadCompleted();
68+
}
69+
70+
public void terminate() throws DebugException {
71+
if(connection != null) {
72+
try {
73+
connection.close();
74+
} catch(IOException ex) {
75+
throw new DebugException(Status.error("Terminate failed", ex));
76+
}
77+
connection = null;
78+
}
79+
}
80+
81+
public String getLabel() {
82+
return "M2E Build Listener";
83+
}
84+
85+
public ILaunch getLaunch() {
86+
return launch;
87+
}
88+
89+
public IStreamsProxy getStreamsProxy() {
90+
return null;
91+
}
92+
93+
@Override
94+
public void setAttribute(String key, String value) {
95+
attributes.put(key, value);
96+
}
97+
98+
@Override
99+
public String getAttribute(String key) {
100+
return attributes.get(key);
101+
}
102+
103+
public void addMavenBuildListener(MavenBuildListener listener) {
104+
buildListeners.add(listener);
105+
}
106+
107+
/**
108+
* @param mavenTestRunnerClient
109+
* @return
110+
*/
111+
public void removeMavenBuildListener(MavenBuildListener listener) {
112+
buildListeners.remove(listener);
113+
}
114+
115+
@Override
116+
public int getExitValue() {
117+
return 0;
118+
}
119+
120+
/**
121+
* @return the projects
122+
*/
123+
public Map<ArtifactKey, MavenProjectBuildData> getProjects() {
124+
return this.projects;
125+
}
126+
127+
void connect() throws IOException {
128+
connection = M2EMavenBuildDataBridge.prepareConnection(launch.getLaunchConfiguration().getName(),
129+
new MavenBuildListener() {
130+
131+
@Override
132+
public void projectStarted(MavenProjectBuildData data) {
133+
projects.put(new ArtifactKey(data.groupId, data.artifactId, data.version, null), data);
134+
for(MavenBuildListener mavenBuildListener : buildListeners) {
135+
mavenBuildListener.projectStarted(data);
136+
}
137+
}
138+
139+
@Override
140+
public void onTestEvent(MavenTestEvent mavenTestEvent) {
141+
for(MavenBuildListener mavenBuildListener : buildListeners) {
142+
mavenBuildListener.onTestEvent(mavenTestEvent);
143+
}
144+
}
145+
146+
public void close() {
147+
for(MavenBuildListener mavenBuildListener : buildListeners) {
148+
mavenBuildListener.close();
149+
}
150+
buildListeners.clear();
151+
launch.removeProcess(MavenBuildConnectionProcess.this);
152+
}
153+
});
154+
}
155+
156+
String getMavenVMArguments() throws IOException {
157+
return connection.getMavenVMArguments();
158+
}
159+
160+
}

org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenBuildProjectDataConnection.java

Lines changed: 36 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -16,50 +16,39 @@
1616
import java.io.IOException;
1717
import java.util.Arrays;
1818
import java.util.Map;
19-
import java.util.Objects;
20-
import java.util.concurrent.ConcurrentHashMap;
21-
import java.util.stream.Stream;
19+
import java.util.Optional;
2220

2321
import org.eclipse.core.runtime.CoreException;
22+
import org.eclipse.debug.core.DebugException;
2423
import org.eclipse.debug.core.DebugPlugin;
2524
import org.eclipse.debug.core.ILaunch;
2625
import org.eclipse.debug.core.ILaunchesListener2;
26+
import org.eclipse.debug.core.model.IProcess;
2727

2828
import org.eclipse.m2e.core.embedder.ArtifactKey;
2929
import org.eclipse.m2e.core.internal.launch.MavenEmbeddedRuntime;
3030
import org.eclipse.m2e.internal.launch.MavenRuntimeLaunchSupport.VMArguments;
31-
import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge;
32-
import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenBuildConnection;
33-
import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenProjectBuildData;
31+
import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData;
3432

3533

3634
public class MavenBuildProjectDataConnection {
3735

38-
private static record MavenBuildConnectionData(Map<ArtifactKey, MavenProjectBuildData> projects,
39-
MavenBuildConnection connection) {
40-
}
41-
42-
private static final Map<ILaunch, MavenBuildConnectionData> LAUNCH_PROJECT_DATA = new ConcurrentHashMap<>();
43-
4436
static {
4537
DebugPlugin.getDefault().getLaunchManager().addLaunchListener(new ILaunchesListener2() {
4638
public void launchesRemoved(ILaunch[] launches) {
47-
closeServers(Arrays.stream(launches).map(LAUNCH_PROJECT_DATA::remove));
48-
}
49-
50-
public void launchesTerminated(ILaunch[] launches) {
51-
closeServers(Arrays.stream(launches).map(LAUNCH_PROJECT_DATA::get));
52-
}
53-
54-
private static void closeServers(Stream<MavenBuildConnectionData> connectionData) {
55-
connectionData.filter(Objects::nonNull).forEach(c -> {
39+
Arrays.stream(launches).flatMap(launch -> getConnection(launch).stream()).forEach(con -> {
5640
try {
57-
c.connection().close();
58-
} catch(IOException ex) { // ignore
41+
con.terminate();
42+
} catch(DebugException ex) {
43+
//ignore
5944
}
6045
});
6146
}
6247

48+
public void launchesTerminated(ILaunch[] launches) {
49+
launchesRemoved(launches);
50+
}
51+
6352
public void launchesAdded(ILaunch[] launches) { // ignore
6453
}
6554

@@ -71,33 +60,42 @@ public void launchesChanged(ILaunch[] launches) { // ignore
7160
static void openListenerConnection(ILaunch launch, VMArguments arguments) {
7261
try {
7362
if(MavenLaunchUtils.getMavenRuntime(launch.getLaunchConfiguration()) instanceof MavenEmbeddedRuntime) {
74-
75-
Map<ArtifactKey, MavenProjectBuildData> projects = new ConcurrentHashMap<>();
76-
77-
MavenBuildConnection connection = M2EMavenBuildDataBridge.prepareConnection(
78-
launch.getLaunchConfiguration().getName(),
79-
d -> projects.put(new ArtifactKey(d.groupId, d.artifactId, d.version, null), d));
80-
81-
if(LAUNCH_PROJECT_DATA.putIfAbsent(launch, new MavenBuildConnectionData(projects, connection)) != null) {
82-
connection.close();
63+
getConnection(launch).ifPresent(existing -> {
64+
try {
65+
existing.terminate();
66+
} catch(DebugException ex) {
67+
}
8368
throw new IllegalStateException(
8469
"Maven bridge already created for launch of" + launch.getLaunchConfiguration().getName());
85-
}
86-
arguments.append(connection.getMavenVMArguments());
70+
});
71+
MavenBuildConnectionProcess process = new MavenBuildConnectionProcess(launch);
72+
process.connect();
73+
arguments.append(process.getMavenVMArguments());
8774
}
8875
} catch(CoreException | IOException ex) { // ignore
8976
}
9077
}
9178

79+
public static Optional<MavenBuildConnectionProcess> getConnection(ILaunch launch) {
80+
for(IProcess process : launch.getProcesses()) {
81+
if(process instanceof MavenBuildConnectionProcess connection) {
82+
return Optional.of(connection);
83+
}
84+
}
85+
return Optional.empty();
86+
}
87+
9288
static MavenProjectBuildData getBuildProject(ILaunch launch, String groupId, String artifactId, String version) {
93-
MavenBuildConnectionData build = LAUNCH_PROJECT_DATA.get(launch);
94-
if(build == null) {
89+
Optional<MavenBuildConnectionProcess> connection = getConnection(launch);
90+
if(connection.isEmpty()) {
9591
return null;
9692
}
93+
MavenBuildConnectionProcess process = connection.get();
94+
Map<ArtifactKey, MavenProjectBuildData> projects = process.getProjects();
9795
ArtifactKey key = new ArtifactKey(groupId, artifactId, version, null);
9896
while(true) {
99-
MavenProjectBuildData buildProject = build.projects().get(key);
100-
if(buildProject != null || build.connection().isReadCompleted()) {
97+
MavenProjectBuildData buildProject = projects.get(key);
98+
if(buildProject != null || process.isTerminated()) {
10199
return buildProject;
102100
}
103101
Thread.onSpinWait(); // Await completion of project data read. It has to become available soon, since its GAV was printed on the console

org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenConsoleLineTracker.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565

6666
import org.eclipse.m2e.core.internal.IMavenConstants;
6767
import org.eclipse.m2e.core.project.IBuildProjectFileResolver;
68-
import org.eclipse.m2e.internal.maven.listener.M2EMavenBuildDataBridge.MavenProjectBuildData;
68+
import org.eclipse.m2e.internal.maven.listener.MavenProjectBuildData;
6969

7070

7171
/**

org.eclipse.m2e.launching/src/org/eclipse/m2e/internal/launch/MavenLaunchDelegate.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import java.util.SortedMap;
2727
import java.util.TreeMap;
2828

29+
import org.osgi.framework.Bundle;
30+
import org.osgi.framework.BundleException;
2931
import org.slf4j.Logger;
3032
import org.slf4j.LoggerFactory;
3133

@@ -121,6 +123,15 @@ public void launch(ILaunchConfiguration configuration, String mode, ILaunch laun
121123
this.programArguments = null;
122124

123125
try {
126+
Bundle bundle = Platform.getBundle("org.eclipse.unittest.ui");
127+
if(bundle != null) {
128+
try {
129+
bundle.start();
130+
} catch(BundleException ex) {
131+
//we tried our best...
132+
}
133+
}
134+
System.out.println(bundle);
124135
this.launchSupport = MavenRuntimeLaunchSupport.create(configuration, monitor);
125136
this.extensionsSupport = MavenLaunchExtensionsSupport.create(configuration, launch);
126137
this.preferencesService = Platform.getPreferencesService();
@@ -129,7 +140,6 @@ public void launch(ILaunchConfiguration configuration, String mode, ILaunch laun
129140
log.info(" mvn {}", getProgramArguments(configuration)); //$NON-NLS-1$
130141

131142
extensionsSupport.configureSourceLookup(configuration, launch, monitor);
132-
133143
super.launch(configuration, mode, launch, monitor);
134144
} finally {
135145
this.launch = null;

0 commit comments

Comments
 (0)