Skip to content

Commit 98d3071

Browse files
Gradle JUnit: Support nested and top-level non-public tests
1 parent 2caa10e commit 98d3071

File tree

4 files changed

+132
-46
lines changed

4 files changed

+132
-46
lines changed

java/gradle.test/nbproject/project.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,6 @@
1616
# under the License.
1717

1818
is.eager=true
19-
javac.source=1.8
19+
javac.release=17
2020
javac.compilerargs=-Xlint -Xlint:-serial
2121
nbm.module.author=Laszlo Kishalmi

java/gradle.test/nbproject/project.xml

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -26,46 +26,46 @@
2626
<code-name-base>org.netbeans.modules.gradle.test</code-name-base>
2727
<module-dependencies>
2828
<dependency>
29-
<code-name-base>org.netbeans.modules.libs.gradle</code-name-base>
29+
<code-name-base>org.netbeans.api.java.classpath</code-name-base>
30+
<build-prerequisite/>
3031
<compile-dependency/>
3132
<run-dependency>
32-
<release-version>8</release-version>
33-
<specification-version>8.0.1</specification-version>
33+
<release-version>1</release-version>
34+
<specification-version>1.41.1</specification-version>
3435
</run-dependency>
3536
</dependency>
3637
<dependency>
37-
<code-name-base>org.netbeans.modules.gradle</code-name-base>
38+
<code-name-base>org.netbeans.libs.javacapi</code-name-base>
3839
<build-prerequisite/>
3940
<compile-dependency/>
4041
<run-dependency>
41-
<release-version>2</release-version>
42-
<specification-version>2.0</specification-version>
42+
<specification-version>8.53</specification-version>
4343
</run-dependency>
4444
</dependency>
4545
<dependency>
46-
<code-name-base>org.netbeans.modules.gradle.java</code-name-base>
46+
<code-name-base>org.netbeans.modules.extexecution</code-name-base>
4747
<build-prerequisite/>
4848
<compile-dependency/>
4949
<run-dependency>
50-
<specification-version>1.17</specification-version>
50+
<release-version>2</release-version>
51+
<specification-version>1.45</specification-version>
5152
</run-dependency>
5253
</dependency>
5354
<dependency>
54-
<code-name-base>org.netbeans.api.java.classpath</code-name-base>
55+
<code-name-base>org.netbeans.modules.gradle</code-name-base>
5556
<build-prerequisite/>
5657
<compile-dependency/>
5758
<run-dependency>
58-
<release-version>1</release-version>
59-
<specification-version>1.41.1</specification-version>
59+
<release-version>2</release-version>
60+
<specification-version>2.0</specification-version>
6061
</run-dependency>
6162
</dependency>
6263
<dependency>
63-
<code-name-base>org.netbeans.modules.extexecution</code-name-base>
64+
<code-name-base>org.netbeans.modules.gradle.java</code-name-base>
6465
<build-prerequisite/>
6566
<compile-dependency/>
6667
<run-dependency>
67-
<release-version>2</release-version>
68-
<specification-version>1.45</specification-version>
68+
<specification-version>1.17</specification-version>
6969
</run-dependency>
7070
</dependency>
7171
<dependency>
@@ -118,6 +118,14 @@
118118
<specification-version>1.3.1</specification-version>
119119
</run-dependency>
120120
</dependency>
121+
<dependency>
122+
<code-name-base>org.netbeans.modules.libs.gradle</code-name-base>
123+
<compile-dependency/>
124+
<run-dependency>
125+
<release-version>8</release-version>
126+
<specification-version>8.0.1</specification-version>
127+
</run-dependency>
128+
</dependency>
121129
<dependency>
122130
<code-name-base>org.netbeans.modules.projectapi</code-name-base>
123131
<build-prerequisite/>

java/gradle.test/src/org/netbeans/modules/gradle/test/GradleTestProgressListener.java

Lines changed: 72 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -19,16 +19,17 @@
1919

2020
package org.netbeans.modules.gradle.test;
2121

22+
import java.nio.file.Path;
2223
import java.util.Arrays;
23-
import org.netbeans.modules.gradle.api.NbGradleProject;
2424
import java.util.Collection;
25-
import org.netbeans.modules.gradle.spi.GradleProgressListenerProvider;
2625
import java.util.EnumSet;
2726
import java.util.Map;
2827
import java.util.Set;
2928
import java.util.concurrent.ConcurrentHashMap;
3029
import java.util.regex.Matcher;
3130
import java.util.regex.Pattern;
31+
import java.util.stream.Collectors;
32+
import javax.lang.model.element.ElementKind;
3233
import org.gradle.tooling.Failure;
3334
import org.gradle.tooling.events.OperationDescriptor;
3435
import org.gradle.tooling.events.OperationType;
@@ -46,8 +47,15 @@
4647
import org.gradle.tooling.events.test.TestSkippedResult;
4748
import org.gradle.tooling.events.test.TestStartEvent;
4849
import org.gradle.tooling.events.test.TestSuccessResult;
50+
import org.netbeans.api.java.source.ClasspathInfo;
51+
import org.netbeans.api.java.source.ElementHandle;
52+
import org.netbeans.api.java.source.SourceUtils;
4953
import org.netbeans.api.project.Project;
5054
import org.netbeans.api.project.ProjectUtils;
55+
import org.netbeans.modules.gradle.api.NbGradleProject;
56+
import org.netbeans.modules.gradle.java.api.GradleJavaProject;
57+
import org.netbeans.modules.gradle.java.api.GradleJavaSourceSet.SourceType;
58+
import org.netbeans.modules.gradle.spi.GradleProgressListenerProvider;
5159
import org.netbeans.modules.gsf.testrunner.api.CommonUtils;
5260
import org.netbeans.modules.gsf.testrunner.api.CoreManager;
5361
import org.netbeans.modules.gsf.testrunner.api.Report;
@@ -57,6 +65,8 @@
5765
import org.netbeans.modules.gsf.testrunner.api.Testcase;
5866
import org.netbeans.modules.gsf.testrunner.api.Trouble;
5967
import org.netbeans.spi.project.ProjectServiceProvider;
68+
import org.openide.filesystems.FileObject;
69+
import org.openide.filesystems.FileUtil;
6070
import org.openide.util.Lookup;
6171

6272
/**
@@ -68,8 +78,8 @@ public final class GradleTestProgressListener implements ProgressListener, Gradl
6878

6979
private final Project project;
7080
private final Map<String, TestSession> sessions = new ConcurrentHashMap<>();
71-
72-
private Map<TestSession, Map<String, Testcase>> runningTests = new ConcurrentHashMap<>();
81+
private final Map<TestSession, Map<String, TestSuite>> runningSuites = new ConcurrentHashMap<>();
82+
private final Map<TestSession, Map<String, Testcase>> runningTests = new ConcurrentHashMap<>();
7383

7484
public GradleTestProgressListener(Project project) {
7585
this.project = project;
@@ -190,13 +200,21 @@ private void suiteFinish(TestFinishEvent evt, JvmTestOperationDescriptor op) {
190200
TestSession session = sessions.get(getSessionKey(evt.getDescriptor()));
191201
assert session != null;
192202
TestOperationResult result = evt.getResult();
193-
TestSuite currentSuite = session.getCurrentSuite();
194203
String suiteName = GradleTestSuite.suiteName(op);
195-
if (suiteName.equals(currentSuite.getName())) {
204+
// In the NetBeans wording a testsuite is the class grouping multiple
205+
// methods (testcase). In the gradle wording a suite can be nested, for
206+
// example the hieararchy can be:
207+
// - Gradle Test Executor <Number> started
208+
// - Test class <Class> started
209+
// => We flatten the list (suites are registered base on executed
210+
// cases (see caseStart)
211+
TestSuite testSuite = runningSuites.get(session).remove(suiteName);
212+
if (testSuite != null) {
196213
Report report = session.getReport(result.getEndTime() - result.getStartTime());
197-
session.finishSuite(currentSuite);
214+
session.finishSuite(testSuite);
198215
CoreManager manager = getManager();
199216
if (manager != null) {
217+
manager.displaySuiteRunning(session, testSuite);
200218
manager.displayReport(session, report, true);
201219
}
202220
}
@@ -206,19 +224,21 @@ private void caseStart(TestStartEvent evt, JvmTestOperationDescriptor op) {
206224
TestSession session = sessions.get(getSessionKey(evt.getDescriptor()));
207225
assert session != null;
208226
assert op.getParent() != null;
209-
TestSuite currentSuite = session.getCurrentSuite();
210-
TestSuite newSuite = new GradleTestSuite(getSuiteOpDesc((JvmTestOperationDescriptor) op.getParent(), op.getClassName()));
211-
if ((currentSuite == null) || !currentSuite.equals(newSuite)) {
212-
session.addSuite(newSuite);
213-
CoreManager manager = getManager();
214-
if (manager != null) {
215-
manager.displaySuiteRunning(session, newSuite);
216-
}
227+
String suiteName = GradleTestSuite.suiteName(op.getParent());
228+
Map<String, TestSuite> sessionSuites = runningSuites.computeIfAbsent(session, s -> new ConcurrentHashMap<>());
229+
TestSuite ts = sessionSuites.computeIfAbsent(suiteName, s -> {
230+
TestSuite suite = new GradleTestSuite(getSuiteOpDesc((JvmTestOperationDescriptor) op.getParent(), op.getClassName()));
231+
session.addSuite(suite);
232+
return suite;
233+
});
234+
CoreManager manager = getManager();
235+
if (manager != null && sessionSuites.size() == 1) {
236+
manager.displaySuiteRunning(session, ts);
217237
}
218238
Testcase tc = new GradleTestcase(op, session);
219-
synchronized (this) {
239+
synchronized (this) {
220240
runningTests.get(session).put(getTestOpKey(op), tc);
221-
session.addTestCase(tc);
241+
session.addTestCase(tc);
222242
}
223243
}
224244

@@ -233,7 +253,7 @@ private void caseFinish(TestFinishEvent evt, JvmTestOperationDescriptor op) {
233253
TestOperationResult result = evt.getResult();
234254
long time = result.getEndTime() - result.getStartTime();
235255
tc.setTimeMillis(time);
236-
tc.setLocation(searchLocation(op.getClassName(), op.getMethodName(), null));
256+
tc.setLocation(searchLocation(tc, op.getClassName(), op.getMethodName(), null));
237257
if (result instanceof TestSuccessResult) {
238258
tc.setStatus(Status.PASSED);
239259
}
@@ -261,7 +281,7 @@ private void caseFinish(TestFinishEvent evt, JvmTestOperationDescriptor op) {
261281
stackTrace = desc.split("\\n");
262282
trouble.setStackTrace(stackTrace);
263283
}
264-
tc.setLocation(searchLocation(op.getClassName(), op.getMethodName(), stackTrace));
284+
tc.setLocation(searchLocation(tc, op.getClassName(), op.getMethodName(), stackTrace));
265285
tc.setTrouble(trouble);
266286
}
267287

@@ -322,7 +342,39 @@ private static CoreManager getManager() {
322342

323343
}
324344

325-
private String searchLocation(String className, String methodName, String[] stackTrace) {
345+
private String searchLocation(Testcase tc, String className, String methodName, String[] stackTrace) {
346+
Map<ClasspathInfo, Path> classpathInfo = Map.of();
347+
NbGradleProject nbGradleProject = tc.getSession()
348+
.getProject()
349+
.getLookup()
350+
.lookup(NbGradleProject.class);
351+
GradleJavaProject gradleJavaProject = nbGradleProject != null ? nbGradleProject.projectLookup(GradleJavaProject.class) : null;
352+
if (gradleJavaProject != null) {
353+
classpathInfo = gradleJavaProject
354+
.getSourceSets()
355+
.values()
356+
.stream()
357+
.flatMap(gradleJavaSourceSet -> gradleJavaSourceSet.getSourceDirs(SourceType.JAVA).stream())
358+
.collect(
359+
Collectors.toMap(
360+
f -> ClasspathInfo.create(f),
361+
f -> f.toPath()
362+
)
363+
);
364+
}
365+
366+
String relativePath = null;
367+
for (Map.Entry<ClasspathInfo, Path> ci : classpathInfo.entrySet()) {
368+
FileObject fo = SourceUtils.getFile(ElementHandle.createTypeElementHandle(ElementKind.CLASS, className), ci.getKey());
369+
if (fo != null) {
370+
relativePath = ci.getValue().relativize(FileUtil.toFile(fo).toPath()).toString();
371+
break;
372+
}
373+
}
374+
if (relativePath != null) {
375+
return relativePath;
376+
}
377+
326378
StringBuilder ret = new StringBuilder(className.length() + methodName.length() + 10);
327379
String fileName = null;
328380
String line = null;

java/gradle.test/src/org/netbeans/modules/gradle/test/ui/nodes/GradleTestMethodNode.java

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,27 +19,28 @@
1919

2020
package org.netbeans.modules.gradle.test.ui.nodes;
2121

22-
import org.netbeans.modules.gradle.api.execute.RunUtils;
23-
import org.netbeans.modules.gradle.java.api.output.Location;
24-
import org.netbeans.modules.gradle.test.ui.nodes.Bundle;
25-
import org.netbeans.modules.gradle.test.GradleTestcase;
2622
import java.util.ArrayList;
2723
import java.util.Arrays;
2824
import java.util.List;
2925
import javax.swing.Action;
3026
import org.gradle.tooling.events.test.JvmTestOperationDescriptor;
3127
import org.netbeans.api.extexecution.print.LineConvertors;
3228
import org.netbeans.api.project.Project;
29+
import org.netbeans.modules.gradle.java.api.output.Location;
30+
import org.netbeans.modules.gradle.test.GradleTestcase;
3331
import org.netbeans.modules.gsf.testrunner.api.Testcase;
3432
import org.netbeans.modules.junit.ui.api.JUnitTestMethodNode;
3533
import org.netbeans.spi.project.ActionProvider;
36-
import static org.netbeans.spi.project.SingleMethod.COMMAND_DEBUG_SINGLE_METHOD;
37-
import static org.netbeans.spi.project.SingleMethod.COMMAND_RUN_SINGLE_METHOD;
34+
import org.netbeans.spi.project.NestedClass;
35+
import org.netbeans.spi.project.SingleMethod;
3836
import org.openide.filesystems.FileObject;
3937
import org.openide.util.Lookup;
4038
import org.openide.util.NbBundle;
4139
import org.openide.util.lookup.Lookups;
4240

41+
import static org.netbeans.spi.project.SingleMethod.COMMAND_DEBUG_SINGLE_METHOD;
42+
import static org.netbeans.spi.project.SingleMethod.COMMAND_RUN_SINGLE_METHOD;
43+
4344
/**
4445
*
4546
* @author Laszlo Kishalmi
@@ -66,14 +67,39 @@ public Action[] getActions(boolean context) {
6667
actions.add(getPreferredAction());
6768
}
6869
ActionProvider actionProvider = getProject().getLookup().lookup(ActionProvider.class);
69-
if ((actionProvider != null) && (testcase instanceof GradleTestcase)) {
70+
if ((actionProvider != null) && testcase instanceof GradleTestcase gradleTestcase) {
7071
List<String> supportedActions = Arrays.asList(actionProvider.getSupportedActions());
7172
boolean runSupported = supportedActions.contains(COMMAND_RUN_SINGLE_METHOD);
7273
boolean debugSupported = supportedActions.contains(COMMAND_DEBUG_SINGLE_METHOD);
7374

74-
JvmTestOperationDescriptor op = ((GradleTestcase) testcase).getOperation();
75-
String tcName = op.getClassName() + '.' + op.getMethodName();
76-
Lookup nodeContext = Lookups.singleton(RunUtils.simpleReplaceTokenProvider("selectedMethod", tcName));
75+
FileObject testFO = findFileObject(getTestLocation());
76+
JvmTestOperationDescriptor op = gradleTestcase.getOperation();
77+
// reporting adds signature to method name, this needs to be stripped away
78+
String mName = op.getMethodName();
79+
if(mName != null) {
80+
mName = mName.replaceFirst("[^\\p{javaJavaIdentifierPart}].*", "");
81+
}
82+
String tcName = op.getClassName();
83+
84+
SingleMethod methodSpec;
85+
if (tcName != null && tcName.contains("$")) {
86+
String[] nestedSplit = tcName.split("\\$", 2);
87+
String[] topLevelSplit = nestedSplit[0].split("\\.");
88+
methodSpec = new SingleMethod(mName, new NestedClass(nestedSplit[1].replace("$", "."), topLevelSplit[topLevelSplit.length - 1], testFO));
89+
} else {
90+
if (tcName != null) {
91+
String[] topLevelSplit = tcName.split("\\.");
92+
if (!testFO.getName().equals(topLevelSplit[topLevelSplit.length - 1])) {
93+
methodSpec = new SingleMethod(mName, new NestedClass("", topLevelSplit[topLevelSplit.length - 1], testFO));
94+
} else {
95+
methodSpec = new SingleMethod(testFO, mName);
96+
}
97+
} else {
98+
methodSpec = new SingleMethod(testFO, mName);
99+
}
100+
}
101+
102+
Lookup nodeContext = Lookups.fixed(methodSpec);
77103

78104
if (runSupported) {
79105
actions.add(new ReRunTestAction(actionProvider, nodeContext, COMMAND_RUN_SINGLE_METHOD, Bundle.LBL_RerunTest()));
@@ -83,7 +109,7 @@ public Action[] getActions(boolean context) {
83109
actions.add(new ReRunTestAction(actionProvider, nodeContext, COMMAND_DEBUG_SINGLE_METHOD, Bundle.LBL_DebugTest()));
84110
}
85111
}
86-
return actions.toArray(new Action[0]);
112+
return actions.toArray(Action[]::new);
87113
}
88114

89115
@Override

0 commit comments

Comments
 (0)