Skip to content

Commit 59fb5e2

Browse files
Merge pull request #8664 from matthiasblaesing/junit-nested
Maven/Gradle-JUnit Integration: Support nested and toplevel non-public tests and stabiize result extraction
2 parents d39d5d2 + d21c961 commit 59fb5e2

File tree

15 files changed

+403
-136
lines changed

15 files changed

+403
-136
lines changed

ide/projectapi/nbproject/project.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
is.autoload=true
1919

2020
javac.compilerargs=-Xlint -Xlint:-serial
21-
javac.source=1.8
21+
javac.release=17
2222
javadoc.arch=${basedir}/arch.xml
2323
javadoc.apichanges=${basedir}/apichanges.xml
2424

ide/projectapi/src/org/netbeans/spi/project/NestedClass.java

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,16 @@ public final class NestedClass {
4646
* Creates a new instance holding the specified identification
4747
* of a nested class.
4848
*
49-
* @param className name of a class inside the file
50-
* @param topLevelClassName top level name of a class inside the file
49+
* @param className name of a class inside the file. Can be an empty string
50+
* if this NestedClass represents a top level element in the source file.
51+
* This is relevant for Java, which allows multiple top level class
52+
* declarations as long as they are not public. The class assumes, that the
53+
* {@code className} follows java convention (i.e. is separated by dots).
54+
* @param topLevelClassName top level name of a class inside the file. This
55+
* is the simple name without package qualification.
5156
* @param file file to be kept in the object
5257
* @exception java.lang.IllegalArgumentException
53-
* if the file or class name is {@code null}
58+
* if the file, topLevelClassName or class name is {@code null}
5459
* @since 1.99
5560
*/
5661
public NestedClass(String className, String topLevelClassName, FileObject file) {
@@ -90,7 +95,8 @@ public String getClassName() {
9095
}
9196

9297
/**
93-
* Returns name of a top level class within a file.
98+
* Returns name of a top level class within a file. This is the simple name
99+
* without package qualification.
94100
*
95101
* @return top level class name held by this object
96102
* @since 1.99
@@ -108,9 +114,19 @@ public String getTopLevelClassName() {
108114
* @since 1.99
109115
*/
110116
public String getFQN(String packageName) {
111-
return String.join(".", packageName, topLevelClassName, className);
117+
String classNameSuffix;
118+
if (className.isBlank()) {
119+
classNameSuffix = topLevelClassName;
120+
} else {
121+
classNameSuffix = topLevelClassName + "." + className;
122+
}
123+
if (packageName.isBlank()) {
124+
return classNameSuffix;
125+
} else {
126+
return String.join(".", packageName, classNameSuffix);
127+
}
112128
}
113-
129+
114130
/**
115131
* Returns fully qualified name.
116132
*
@@ -121,9 +137,19 @@ public String getFQN(String packageName) {
121137
* @since 1.99
122138
*/
123139
public String getFQN(String packageName, String nestedClassSeparator) {
124-
return String.join(".", packageName, String.join(nestedClassSeparator, topLevelClassName, className.replace(".", nestedClassSeparator)));
140+
String classNameSuffix;
141+
if (className.isBlank()) {
142+
classNameSuffix = topLevelClassName;
143+
} else {
144+
classNameSuffix = topLevelClassName + nestedClassSeparator + className.replace(".", nestedClassSeparator);
145+
}
146+
if (packageName.isBlank()) {
147+
return classNameSuffix;
148+
} else {
149+
return String.join(".", packageName, classNameSuffix);
150+
}
125151
}
126-
152+
127153
@Override
128154
public int hashCode() {
129155
int hash = 3;

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;

0 commit comments

Comments
 (0)