Added ProjectActionContext that can pass action-like environment to project queries
diff --git a/ide/projectapi/manifest.mf b/ide/projectapi/manifest.mf
index 7fefbde717a1..bf89633c6a95 100644
--- a/ide/projectapi/manifest.mf
+++ b/ide/projectapi/manifest.mf
@@ -1,6 +1,6 @@
Manifest-Version: 1.0
OpenIDE-Module: org.netbeans.modules.projectapi/1
-OpenIDE-Module-Specification-Version: 1.98
+OpenIDE-Module-Specification-Version: 1.99
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/projectapi/Bundle.properties
OpenIDE-Module-Layer: org/netbeans/modules/projectapi/layer.xml
OpenIDE-Module-Needs: org.netbeans.spi.project.ProjectManagerImplementation
diff --git a/ide/projectapi/src/org/netbeans/api/project/ContainedProjectFilter.java b/ide/projectapi/src/org/netbeans/api/project/ContainedProjectFilter.java
new file mode 100644
index 000000000000..2680ec2c3efa
--- /dev/null
+++ b/ide/projectapi/src/org/netbeans/api/project/ContainedProjectFilter.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.api.project;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Provides list of projects the project action should apply to
+ *
+ *
+ * An action that processes multiple projects might use ContainedProjectFilter
+ * to operate only on a specific subset of projects.
+ * The use of ContainedProjectFilter is optional and determined
+ * by the requirements of individual actions.
+ * Actions employing this class must document their specific filtering logic
+ * and behavior.
+ *
+ *
+ * @author Dusan Petrovic
+ *
+ * @since 1.99
+ */
+public final class ContainedProjectFilter {
+
+ private final List projectsToProcess;
+
+ private ContainedProjectFilter(List projectsToProcess) {
+ this.projectsToProcess = projectsToProcess;
+ }
+
+ /**
+ * Static factory method to create an instance of ContainedProjectFilter.
+ *
+ * @param projectsToProcess the list of projects to include in the filter
+ * @return an Optional containing ContainedProjectFilter, or Optional.empty() if the list is null or empty
+ */
+ public static Optional of(List projectsToProcess) {
+ if (projectsToProcess == null || projectsToProcess.isEmpty()) {
+ return Optional.empty();
+ }
+ return Optional.of(new ContainedProjectFilter(projectsToProcess));
+ }
+
+ public List getProjectsToProcess() {
+ return Collections.unmodifiableList(projectsToProcess);
+ }
+}
diff --git a/ide/projectapi/src/org/netbeans/spi/project/ActionProvider.java b/ide/projectapi/src/org/netbeans/spi/project/ActionProvider.java
index 65098b8435c7..106a3e1f9783 100644
--- a/ide/projectapi/src/org/netbeans/spi/project/ActionProvider.java
+++ b/ide/projectapi/src/org/netbeans/spi/project/ActionProvider.java
@@ -83,6 +83,13 @@ public interface ActionProvider {
*/
String COMMAND_TEST_SINGLE = "test.single"; // NOI18N
+ /**
+ * Standard command for running tests in parallel on given projects sub-modules
+ *
+ * @since 1.99
+ */
+ String COMMAND_TEST_PARALLEL = "test.parallel"; // NOI18N
+
/**
* Standard command for running the project in debugger
*/
diff --git a/ide/projectapi/src/org/netbeans/spi/project/NestedClass.java b/ide/projectapi/src/org/netbeans/spi/project/NestedClass.java
new file mode 100644
index 000000000000..5df90f2a7104
--- /dev/null
+++ b/ide/projectapi/src/org/netbeans/spi/project/NestedClass.java
@@ -0,0 +1,147 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.spi.project;
+
+import java.util.Objects;
+import org.openide.filesystems.FileObject;
+
+/**
+ * Structure representing an identification of a nested class in a file.
+ *
+ *
+ * NestedClass can be used to represent nested classes within parent class
+ * Example:
+ * If we have following structure: ParentClass (parent-of) ChildClass1 (parent-of) ChildClass2,
+ * for ChildClass1 className field would contain "ChildClass1" and topLevelClassName would contain "ParentClass",
+ * for ChildClass2 className field would contain "ChildClass1.ChildClass2" and topLevelClassName would contain "ParentClass"
+ *
+ *
+ * @author Dusan Petrovic
+ *
+ * @since 1.99
+ */
+public final class NestedClass {
+
+ private final FileObject file;
+ private final String className;
+ private final String topLevelClassName;
+
+ /**
+ * Creates a new instance holding the specified identification
+ * of a nested class.
+ *
+ * @param className name of a class inside the file
+ * @param topLevelClassName top level name of a class inside the file
+ * @param file file to be kept in the object
+ * @exception java.lang.IllegalArgumentException
+ * if the file or class name is {@code null}
+ * @since 1.99
+ */
+ public NestedClass(String className, String topLevelClassName, FileObject file) {
+ super();
+ if (className == null) {
+ throw new IllegalArgumentException("className is ");
+ }
+ if (topLevelClassName == null) {
+ throw new IllegalArgumentException("topLevelClassName is ");
+ }
+ if (file == null) {
+ throw new IllegalArgumentException("file is ");
+ }
+ this.className = className;
+ this.topLevelClassName = topLevelClassName;
+ this.file = file;
+ }
+
+ /**
+ * Returns the file identification.
+ *
+ * @return file held by this object
+ * @since 1.99
+ */
+ public FileObject getFile() {
+ return file;
+ }
+
+ /**
+ * Returns name of a nested class within a file.
+ *
+ * @return class name held by this object
+ * @since 1.99
+ */
+ public String getClassName() {
+ return className;
+ }
+
+ /**
+ * Returns name of a top level class within a file.
+ *
+ * @return top level class name held by this object
+ * @since 1.99
+ */
+ public String getTopLevelClassName() {
+ return topLevelClassName;
+ }
+
+ /**
+ * Returns fully qualified name.
+ *
+ * @param packageName name of the package where the class is
+ *
+ * @return fully qualified name held by this object
+ * @since 1.99
+ */
+ public String getFQN(String packageName) {
+ return String.join(".", packageName, topLevelClassName, className);
+ }
+
+ /**
+ * Returns fully qualified name.
+ *
+ * @param packageName name of the package where the class is
+ * @param nestedClassSeparator separator for the nested classes
+ *
+ * @return fully qualified name held by this object
+ * @since 1.99
+ */
+ public String getFQN(String packageName, String nestedClassSeparator) {
+ return String.join(".", packageName, String.join(nestedClassSeparator, topLevelClassName, className.replace(".", nestedClassSeparator)));
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 3;
+ hash = 41 * hash + Objects.hashCode(this.className);
+ hash = 41 * hash + Objects.hashCode(this.topLevelClassName);
+ hash = 41 * hash + Objects.hashCode(this.file);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if ((obj == null) || (obj.getClass() != NestedClass.class)) {
+ return false;
+ }
+ if (this == obj) {
+ return true;
+ }
+ final NestedClass other = (NestedClass) obj;
+ return other.file.equals(file) && other.className.equals(className) && other.topLevelClassName.equals(topLevelClassName);
+ }
+}
diff --git a/ide/projectapi/src/org/netbeans/spi/project/SingleMethod.java b/ide/projectapi/src/org/netbeans/spi/project/SingleMethod.java
index e459a28ca9eb..ca2874deb02e 100644
--- a/ide/projectapi/src/org/netbeans/spi/project/SingleMethod.java
+++ b/ide/projectapi/src/org/netbeans/spi/project/SingleMethod.java
@@ -19,6 +19,7 @@
package org.netbeans.spi.project;
+import java.util.Objects;
import org.openide.filesystems.FileObject;
/**
@@ -29,9 +30,26 @@
*/
public final class SingleMethod {
- private FileObject file;
- private String methodName;
+ private final FileObject file;
+ private final String methodName;
+ private final NestedClass nestedClass;
+ /**
+ * Creates a new instance holding the specified identification
+ * of a method/function in a file.
+ *
+ * @param nestedClass nested class containing the method
+ * @param file file to be kept in the object
+ * @param methodName name of a method inside the file
+ *
+ * @since 1.99
+ */
+ private SingleMethod(NestedClass nestedClass, FileObject file, String methodName) {
+ this.methodName = methodName;
+ this.file = file;
+ this.nestedClass = nestedClass;
+ }
+
/**
* Creates a new instance holding the specified identification
* of a method/function in a file.
@@ -43,15 +61,39 @@ public final class SingleMethod {
* @since 1.19
*/
public SingleMethod(FileObject file, String methodName) {
- super();
- if (file == null) {
- throw new IllegalArgumentException("file is ");
- }
- if (methodName == null) {
- throw new IllegalArgumentException("methodName is ");
+ this(null, nonNull(file, "file"), nonNull(methodName, "methodName"));
+ }
+
+ /**
+ * Creates a new instance holding the specified identification
+ * of a method/function in nested class in a file.
+ *
+ * @param methodName name of a method inside the file
+ * @param nestedClass nested class containing the method
+ *
+ * @exception java.lang.IllegalArgumentException
+ * if the nested class name is {@code null}
+ * @since 1.99
+ */
+ public SingleMethod(String methodName, NestedClass nestedClass) {
+ this(nonNull(nestedClass, "nestedClass"), nonNull(nestedClass.getFile(), "file"), nonNull(methodName, "methodName"));
+ }
+
+ private static T nonNull(T value, String paramName) {
+ if (value == null) {
+ throw new IllegalArgumentException(paramName + " is ");
}
- this.file = file;
- this.methodName = methodName;
+ return value;
+ }
+
+ /**
+ * Returns the nested class containing the method.
+ *
+ * @return nested class containing the method
+ * @since 1.99
+ */
+ public NestedClass getNestedClass() {
+ return nestedClass;
}
/**
@@ -94,7 +136,7 @@ public boolean equals(Object obj) {
return false;
}
SingleMethod other = (SingleMethod) obj;
- return other.file.equals(file) && other.methodName.equals(methodName);
+ return other.file.equals(file) && other.methodName.equals(methodName) && Objects.equals(other.nestedClass, nestedClass);
}
@Override
@@ -102,6 +144,7 @@ public int hashCode() {
int hash = 7;
hash = 29 * hash + this.file.hashCode();
hash = 29 * hash + this.methodName.hashCode();
+ hash = 29 * hash + Objects.hashCode(this.nestedClass);
return hash;
}
}
diff --git a/java/gradle.java/apichanges.xml b/java/gradle.java/apichanges.xml
index 55a1019a9685..fc7e3f311fdd 100644
--- a/java/gradle.java/apichanges.xml
+++ b/java/gradle.java/apichanges.xml
@@ -83,6 +83,18 @@ is the proper place.
+
+
+ Added action for running tests in parallel
+
+
+
+
+
+ Added action for running tests in parallel with ability to specify the projects on which
+ the action will be applied.
+
+
Support for per-language output directories
diff --git a/java/gradle.java/nbproject/project.properties b/java/gradle.java/nbproject/project.properties
index fb6f49f0b16b..fd605b84f415 100644
--- a/java/gradle.java/nbproject/project.properties
+++ b/java/gradle.java/nbproject/project.properties
@@ -25,5 +25,5 @@ javadoc.apichanges=${basedir}/apichanges.xml
test-unit-sys-prop.test.netbeans.dest.dir=${netbeans.dest.dir}
test-unit-sys-prop.java.awt.headless=true
test.use.jdk.javac=true
-spec.version.base=1.29.0
+spec.version.base=1.30.0
diff --git a/java/gradle.java/nbproject/project.xml b/java/gradle.java/nbproject/project.xml
index 0c9f70e8e166..e02a778936ad 100644
--- a/java/gradle.java/nbproject/project.xml
+++ b/java/gradle.java/nbproject/project.xml
@@ -171,7 +171,7 @@
- 2.73
+ 2.74
@@ -188,7 +188,7 @@
1
- 1.57.1
+ 1.99
diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/GradleJavaTokenProvider.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/GradleJavaTokenProvider.java
index 3e2723d802fd..84435b7fbc65 100644
--- a/java/gradle.java/src/org/netbeans/modules/gradle/java/GradleJavaTokenProvider.java
+++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/GradleJavaTokenProvider.java
@@ -34,6 +34,7 @@
import org.netbeans.api.java.source.ClasspathInfo;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.project.Project;
+import org.netbeans.spi.project.NestedClass;
import org.netbeans.spi.project.SingleMethod;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
@@ -84,8 +85,9 @@ public Map createReplacements(String action, Lookup context) {
private void processSelectedPackageAndClass(final Map map, Lookup context) {
FileObject fo = RunUtils.extractFileObjectfromLookup(context);
+ NestedClass nestedClass = context.lookup(NestedClass.class);
GradleJavaProject gjp = GradleJavaProject.get(project);
- String className = evaluateClassName(gjp, fo);
+ String className = evaluateClassName(gjp, fo, nestedClass);
if (className != null) {
map.put(SELECTED_CLASS, className);
int dot = className.lastIndexOf('.');
@@ -104,7 +106,8 @@ private void processSelectedMethod(final Map map, Lookup context
FileObject fo = method != null ? method.getFile() : RunUtils.extractFileObjectfromLookup(context);
if ((fo != null) && fo.isData()) {
GradleJavaProject gjp = GradleJavaProject.get(project);
- String className = evaluateClassName(gjp, fo);
+ NestedClass nestedClass = method != null ? method.getNestedClass() : context.lookup(NestedClass.class);
+ String className = evaluateClassName(gjp, fo, nestedClass);
String selectedMethod = method != null ? className + '.' + method.getMethodName() : className;
map.put(SELECTED_METHOD, selectedMethod);
}
@@ -133,7 +136,7 @@ private void processSourceSets(final Map map, Lookup context) {
}
}
- private String evaluateClassName(GradleJavaProject gjp, FileObject fo) {
+ private String evaluateClassName(GradleJavaProject gjp, FileObject fo, NestedClass nestedClass) {
String ret = null;
if ((gjp != null) && (fo != null)) {
File f = FileUtil.toFile(fo);
@@ -145,7 +148,7 @@ private String evaluateClassName(GradleJavaProject gjp, FileObject fo) {
ret = relPath.replace('/', '.');
ret = ret + '*';
} else {
- ret = SourceUtils.classNameFor(ClasspathInfo.create(fo), relPath);
+ ret = SourceUtils.classNameFor(ClasspathInfo.create(fo), relPath, nestedClass);
}
}
}
diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/JavaActionProvider.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/JavaActionProvider.java
index 2d4045b3d55b..d3dda0ff6d39 100644
--- a/java/gradle.java/src/org/netbeans/modules/gradle/java/JavaActionProvider.java
+++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/JavaActionProvider.java
@@ -72,6 +72,7 @@ public class JavaActionProvider extends DefaultGradleActionsProvider {
COMMAND_RUN,
COMMAND_DEBUG,
COMMAND_TEST,
+ COMMAND_TEST_PARALLEL,
COMMAND_TEST_SINGLE,
COMMAND_DEBUG_TEST_SINGLE,
COMMAND_RUN_SINGLE_METHOD,
diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/ProjectsTokenProvider.java b/java/gradle.java/src/org/netbeans/modules/gradle/java/ProjectsTokenProvider.java
new file mode 100644
index 000000000000..612db9ad03da
--- /dev/null
+++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/ProjectsTokenProvider.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.modules.gradle.java;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.netbeans.api.project.Project;
+import org.netbeans.modules.gradle.api.NbGradleProject;
+import org.netbeans.modules.gradle.spi.actions.ReplaceTokenProvider;
+import org.netbeans.spi.project.ActionProvider;
+import org.netbeans.api.project.ContainedProjectFilter;
+import org.netbeans.spi.project.ProjectServiceProvider;
+import org.openide.util.Lookup;
+
+/**
+ *
+ * @author Dusan Petrovic
+ */
+@ProjectServiceProvider(
+ service = ReplaceTokenProvider.class,
+ projectType = NbGradleProject.GRADLE_PROJECT_TYPE
+)
+public class ProjectsTokenProvider implements ReplaceTokenProvider {
+
+ private static final String TASK_WITH_PROJECTS = "taskWithProjects"; //NOI18N
+ private static final Set SUPPORTED = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
+ TASK_WITH_PROJECTS
+ )));
+
+ @Override
+ public Set getSupportedTokens() {
+ return SUPPORTED;
+ }
+
+ @Override
+ public Map createReplacements(String action, Lookup context) {
+ String taskName = getTaskForAction(action);
+ if (taskName == null) {
+ return new HashMap<>();
+ }
+ return getProjectsWithTaskReplacement(taskName, context);
+ }
+
+ private String getTaskForAction(String action) {
+ return switch (action) {
+ case ActionProvider.COMMAND_TEST_PARALLEL -> "test"; //NOI18N
+ default -> null;
+ };
+ }
+
+ private Map getProjectsWithTaskReplacement(String taskName, Lookup context) {
+ ContainedProjectFilter parameters = context.lookup(ContainedProjectFilter.class);
+ List projects = parameters == null ? null : parameters.getProjectsToProcess();
+ if (projects == null || projects.isEmpty()) {
+ return Map.of(TASK_WITH_PROJECTS, taskName);
+ }
+ StringBuilder resultTask = new StringBuilder();
+ List projectReplacements = createProjectsReplacement(projects);
+ for (String project : projectReplacements) {
+ resultTask.append(project)
+ .append(":") //NOI18N
+ .append(taskName)
+ .append(" ");//NOI18N
+ }
+ return Map.of(TASK_WITH_PROJECTS, resultTask.toString().trim());
+ }
+
+ private List createProjectsReplacement(List projects) {
+ return projects
+ .stream()
+ .map(prj -> prj.getProjectDirectory().getName())
+ .toList();
+ }
+}
diff --git a/java/gradle.java/src/org/netbeans/modules/gradle/java/action-mapping.xml b/java/gradle.java/src/org/netbeans/modules/gradle/java/action-mapping.xml
index af720a333f82..97fadb9fee99 100644
--- a/java/gradle.java/src/org/netbeans/modules/gradle/java/action-mapping.xml
+++ b/java/gradle.java/src/org/netbeans/modules/gradle/java/action-mapping.xml
@@ -21,6 +21,9 @@
-->
+
+ --parallel --rerun-tasks ${taskWithProjects}
+
"${cleanTestTaskName}" "${testTaskName}" --tests "${selectedClass}"
diff --git a/java/gradle.test/manifest.mf b/java/gradle.test/manifest.mf
index 1dafb59394bd..2e5175701464 100644
--- a/java/gradle.test/manifest.mf
+++ b/java/gradle.test/manifest.mf
@@ -2,4 +2,4 @@ Manifest-Version: 1.0
AutoUpdate-Show-In-Client: false
OpenIDE-Module: org.netbeans.modules.gradle.test
OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/gradle/test/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.23
+OpenIDE-Module-Specification-Version: 1.24
diff --git a/java/gradle.test/nbproject/project.xml b/java/gradle.test/nbproject/project.xml
index 6bbce2d0cc20..efe3ef690ebb 100644
--- a/java/gradle.test/nbproject/project.xml
+++ b/java/gradle.test/nbproject/project.xml
@@ -90,7 +90,7 @@
- 2.4.1.2.25.8.1
+ 2.74
diff --git a/java/gradle.test/src/org/netbeans/modules/gradle/test/GradleTestProgressListener.java b/java/gradle.test/src/org/netbeans/modules/gradle/test/GradleTestProgressListener.java
index a3e70dc76c52..a45f9d20176d 100644
--- a/java/gradle.test/src/org/netbeans/modules/gradle/test/GradleTestProgressListener.java
+++ b/java/gradle.test/src/org/netbeans/modules/gradle/test/GradleTestProgressListener.java
@@ -47,6 +47,7 @@
import org.gradle.tooling.events.test.TestStartEvent;
import org.gradle.tooling.events.test.TestSuccessResult;
import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectUtils;
import org.netbeans.modules.gsf.testrunner.api.CommonUtils;
import org.netbeans.modules.gsf.testrunner.api.CoreManager;
import org.netbeans.modules.gsf.testrunner.api.Report;
@@ -62,13 +63,13 @@
*
* @author Laszlo Kishalmi
*/
-@ProjectServiceProvider(service = GradleProgressListenerProvider.class, projectType = NbGradleProject.GRADLE_PLUGIN_TYPE + "/java")
+@ProjectServiceProvider(service = GradleProgressListenerProvider.class, projectType = NbGradleProject.GRADLE_PROJECT_TYPE )
public final class GradleTestProgressListener implements ProgressListener, GradleProgressListenerProvider {
private final Project project;
- TestSession session;
+ private final Map sessions = new ConcurrentHashMap<>();
- Map runningTests = new ConcurrentHashMap<>();
+ private Map> runningTests = new ConcurrentHashMap<>();
public GradleTestProgressListener(Project project) {
this.project = project;
@@ -131,17 +132,22 @@ private void processTestProgress(TestProgressEvent evt) {
}
private void processTestOutput(TestOutputEvent evt) {
+ TestSession session = sessions.get(getSessionKey(evt.getDescriptor()));
+ assert session != null;
+ if (session == null) {
+ throw new IllegalArgumentException("TestSession is null");
+ }
TestOutputDescriptor desc = evt.getDescriptor();
OperationDescriptor parent = desc.getParent();
CoreManager manager = getManager();
String msg = desc.getMessage();
if (msg != null && msg.endsWith("\n")) {
msg = msg.substring(0, msg.length() - 1);
- if (manager != null) {
+ if (manager != null && session != null) {
manager.displayOutput(session, msg, desc.getDestination().equals(Destination.StdErr));
}
if (parent instanceof JvmTestOperationDescriptor) {
- Testcase tc = runningTests.get(getTestOpKey((JvmTestOperationDescriptor) parent));
+ Testcase tc = runningTests.get(session).get(getTestOpKey((JvmTestOperationDescriptor) parent));
if (tc != null) {
tc.addOutputLines(Arrays.asList(msg.split("\\R")));
}
@@ -151,8 +157,12 @@ private void processTestOutput(TestOutputEvent evt) {
private void sessionStart(TestStartEvent evt) {
- session = new TestSession(evt.getDisplayName(), project, TestSession.SessionType.TEST);
- runningTests.clear();
+ String key = getSessionKey(evt.getDescriptor());
+ TestSession session;
+ synchronized (this) {
+ session = sessions.computeIfAbsent(key, name -> new TestSession(name, getProject(key), TestSession.SessionType.TEST));
+ runningTests.put(session, new ConcurrentHashMap<>());
+ }
CoreManager manager = getManager();
if (manager != null) {
manager.registerNodeFactory();
@@ -161,7 +171,12 @@ private void sessionStart(TestStartEvent evt) {
}
private void sessionFinish(TestFinishEvent evt) {
- runningTests.clear();
+ TestSession session;
+ synchronized (this) {
+ session = sessions.remove(getSessionKey(evt.getDescriptor()));
+ assert session != null;
+ runningTests.remove(session);
+ }
CoreManager manager = getManager();
if (manager != null) {
manager.sessionFinished(session);
@@ -172,6 +187,8 @@ private void suiteStart(TestStartEvent evt, JvmTestOperationDescriptor op) {
}
private void suiteFinish(TestFinishEvent evt, JvmTestOperationDescriptor op) {
+ TestSession session = sessions.get(getSessionKey(evt.getDescriptor()));
+ assert session != null;
TestOperationResult result = evt.getResult();
TestSuite currentSuite = session.getCurrentSuite();
String suiteName = GradleTestSuite.suiteName(op);
@@ -186,6 +203,7 @@ private void suiteFinish(TestFinishEvent evt, JvmTestOperationDescriptor op) {
}
private void caseStart(TestStartEvent evt, JvmTestOperationDescriptor op) {
+ TestSession session = sessions.get(getSessionKey(evt.getDescriptor()));
assert session != null;
assert op.getParent() != null;
TestSuite currentSuite = session.getCurrentSuite();
@@ -198,12 +216,19 @@ private void caseStart(TestStartEvent evt, JvmTestOperationDescriptor op) {
}
}
Testcase tc = new GradleTestcase(op, session);
- runningTests.put(getTestOpKey(op), tc);
- session.addTestCase(tc);
+ synchronized (this) {
+ runningTests.get(session).put(getTestOpKey(op), tc);
+ session.addTestCase(tc);
+ }
}
- private void caseFinish(TestFinishEvent evt, JvmTestOperationDescriptor op) {
- Testcase tc = runningTests.get(getTestOpKey(op));
+ private void caseFinish(TestFinishEvent evt, JvmTestOperationDescriptor op) {
+ Testcase tc;
+ synchronized (this) {
+ TestSession session = sessions.get(getSessionKey(evt.getDescriptor()));
+ assert session != null;
+ tc = runningTests.get(session).remove(getTestOpKey(op));
+ }
if (tc != null) {
TestOperationResult result = evt.getResult();
long time = result.getEndTime() - result.getStartTime();
@@ -241,11 +266,38 @@ private void caseFinish(TestFinishEvent evt, JvmTestOperationDescriptor op) {
}
}
- runningTests.remove(getTestOpKey(op));
}
}
+ private static final String GRADLE_TEST_RUN = "Gradle Test Run :"; // NOI18N
+ private static String TEST = ":test";
+
+ private Project getProject(String key) {
+ if (key != null && key.startsWith(GRADLE_TEST_RUN)) {
+ key = key.substring(GRADLE_TEST_RUN.length());
+ if (key.endsWith(TEST)) {
+ key = key.substring(0, key.length() - TEST.length()).trim();
+ if (!key.isEmpty()) {
+ for (Project containedPrj : ProjectUtils.getContainedProjects(project, true)) {
+ if (key.equals(containedPrj.getProjectDirectory().getName())) {
+ return containedPrj;
+ }
+ }
+ }
+ }
+ }
+ return project;
+ }
+
+ private static String getSessionKey(OperationDescriptor op) {
+ String id = "";
+ for (OperationDescriptor descriptor = op; descriptor != null; descriptor = descriptor.getParent()) {
+ id = descriptor.getName();
+ }
+ return id;
+ }
+
private static JvmTestOperationDescriptor getSuiteOpDesc(JvmTestOperationDescriptor op, String className) {
for (JvmTestOperationDescriptor descriptor = op; descriptor != null; descriptor = (JvmTestOperationDescriptor) descriptor.getParent()) {
if (className == null || className.equals(descriptor.getClassName())) {
diff --git a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/maven-actions-override.xml b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/maven-actions-override.xml
index 3ba325ab656d..eacae56a62c9 100644
--- a/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/maven-actions-override.xml
+++ b/java/java.lsp.server/nbcode/integration/src/org/netbeans/modules/nbcode/integration/maven-actions-override.xml
@@ -55,7 +55,6 @@
${packageClassName}