This is an automated email from the ASF dual-hosted git repository.
dbalek pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/netbeans.git
The following commit(s) were added to refs/heads/master by this push:
new cf5f2b9 Support for visualizing test results in VSCode extension over
LSP. (#2766)
cf5f2b9 is described below
commit cf5f2b93caad447a2a61927833cbf501bb5bba23
Author: Dusan Balek <[email protected]>
AuthorDate: Fri Feb 19 16:58:40 2021 +0100
Support for visualizing test results in VSCode extension over LSP. (#2766)
---
.../gradle/execute/GradleDaemonExecutor.java | 27 +-
ide/gsf.testrunner.ui/apichanges.xml | 14 +
ide/gsf.testrunner.ui/manifest.mf | 2 +-
.../gsf/testrunner/ui/ResultDisplayHandler.java | 22 +-
.../modules/gsf/testrunner/ui/api/Manager.java | 72 ++--
.../testrunner/ui/api/TestMethodController.java | 11 +-
.../ui/api/TestResultDisplayHandler.java | 220 ++++++++++
ide/gsf.testrunner/apichanges.xml | 13 +
ide/gsf.testrunner/manifest.mf | 2 +-
ide/gsf.testrunner/nbproject/project.xml | 45 +-
.../nbcode/nbproject/platform.properties | 3 -
java/java.lsp.server/nbproject/project.xml | 29 +-
.../server/debugging/launch/NbLaunchDelegate.java | 28 +-
.../debugging/launch/NbLaunchRequestHandler.java | 3 +-
.../lsp/server/progress/TestProgressHandler.java | 140 ++++++
.../server/protocol/NbCodeClientCapabilities.java | 24 +-
.../lsp/server/protocol/NbCodeClientWrapper.java | 5 +
.../lsp/server/protocol/NbCodeLanguageClient.java | 9 +
.../java/lsp/server/protocol/QuickPickItem.java | 2 +-
.../modules/java/lsp/server/protocol/Server.java | 12 +-
.../lsp/server/protocol/ShowInputBoxParams.java | 4 +-
.../lsp/server/protocol/ShowQuickPickParams.java | 4 +-
...InputBoxParams.java => TestProgressParams.java} | 63 +--
.../java/lsp/server/protocol/TestSuiteInfo.java | 477 +++++++++++++++++++++
.../server/protocol/TextDocumentServiceImpl.java | 80 ++--
.../lsp/server/protocol/WorkspaceServiceImpl.java | 129 +++++-
.../server/progress/TestProgressHandlerTest.java | 200 +++++++++
.../java/lsp/server/protocol/ServerTest.java | 41 +-
java/java.lsp.server/vscode/package-lock.json | 22 +-
java/java.lsp.server/vscode/package.json | 15 +-
java/java.lsp.server/vscode/src/extension.ts | 58 ++-
java/java.lsp.server/vscode/src/protocol.ts | 27 ++
java/java.lsp.server/vscode/src/test/runTest.ts | 14 +-
java/java.lsp.server/vscode/src/testAdapter.ts | 212 +++++++++
java/junit.ui/manifest.mf | 2 +-
java/junit.ui/nbproject/project.xml | 2 +-
.../junit/ui/actions/TestClassInfoTask.java | 19 +-
java/testng.ui/manifest.mf | 2 +-
java/testng.ui/nbproject/project.xml | 2 +-
.../testng/ui/actions/TestClassInfoTask.java | 30 +-
.../bundles/vscode-test-adapter-util-0.7.1-license | 22 +
41 files changed, 1888 insertions(+), 220 deletions(-)
diff --git
a/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java
b/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java
index 219be06..58b7350 100644
---
a/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java
+++
b/extide/gradle/src/org/netbeans/modules/gradle/execute/GradleDaemonExecutor.java
@@ -50,11 +50,10 @@ import org.gradle.tooling.GradleConnectionException;
import org.gradle.tooling.GradleConnector;
import org.gradle.tooling.ProgressEvent;
import org.gradle.tooling.ProjectConnection;
+import org.gradle.tooling.events.ProgressListener;
import org.netbeans.api.progress.ProgressHandle;
import org.netbeans.api.project.ProjectInformation;
import org.netbeans.api.project.ProjectUtils;
-import org.netbeans.modules.gradle.NbGradleProjectImpl;
-import org.netbeans.modules.gradle.api.NbGradleProject;
import
org.netbeans.modules.gradle.api.execute.GradleDistributionManager.GradleDistribution;
import org.netbeans.modules.gradle.spi.GradleFiles;
import org.netbeans.modules.gradle.spi.execute.GradleDistributionProvider;
@@ -63,10 +62,12 @@ import
org.netbeans.spi.project.ui.support.BuildExecutionSupport;
import org.openide.awt.StatusDisplayer;
import org.openide.execution.ExecutorTask;
import org.openide.filesystems.FileUtil;
+import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.NbBundle.Messages;
import org.openide.util.Utilities;
import org.openide.util.io.ReaderInputStream;
+import org.openide.util.lookup.Lookups;
import org.openide.windows.IOColorPrint;
import org.openide.windows.IOColors;
import org.openide.windows.InputOutput;
@@ -128,6 +129,23 @@ public final class GradleDaemonExecutor extends
AbstractGradleExecutor {
final InputOutput ioput = getInputOutput();
actionStatesAtStart();
handle.start();
+
+ // BuildLauncher creates its own threads, need to note the effective
Lookup and re-establish it in the listeners
+ final Lookup execLookup = Lookup.getDefault();
+
+ class ProgressLookupListener implements
org.gradle.tooling.events.ProgressListener {
+ private final org.gradle.tooling.events.ProgressListener delegate;
+
+ public ProgressLookupListener(ProgressListener delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void statusChanged(org.gradle.tooling.events.ProgressEvent
event) {
+ Lookups.executeWith(execLookup, () ->
delegate.statusChanged(event));
+ }
+ }
+
try {
BuildExecutionSupport.registerRunningItem(item);
@@ -187,14 +205,14 @@ public final class GradleDaemonExecutor extends
AbstractGradleExecutor {
buildLauncher.setStandardOutput(outStream);
buildLauncher.setStandardError(errStream);
buildLauncher.addProgressListener((ProgressEvent pe) -> {
- handle.progress(pe.getDescription());
+ Lookups.executeWith(execLookup, () ->
handle.progress(pe.getDescription()));
});
buildLauncher.withCancellationToken(cancelTokenSource.token());
if (config.getProject() != null) {
Collection<? extends GradleProgressListenerProvider> providers
=
config.getProject().getLookup().lookupAll(GradleProgressListenerProvider.class);
for (GradleProgressListenerProvider provider : providers) {
-
buildLauncher.addProgressListener(provider.getProgressListener(),
provider.getSupportedOperationTypes());
+ buildLauncher.addProgressListener(new
ProgressLookupListener(provider.getProgressListener()),
provider.getSupportedOperationTypes());
}
}
buildLauncher.run();
@@ -205,6 +223,7 @@ public final class GradleDaemonExecutor extends
AbstractGradleExecutor {
} catch (UncheckedException | BuildException ex) {
if (!cancelling) {
StatusDisplayer.getDefault().setStatusText(Bundle.BUILD_FAILED(getProjectName()));
+ gradleTask.finish(1);
} else {
// This can happen if cancelling a Gradle build which is
running
// an external aplication
diff --git a/ide/gsf.testrunner.ui/apichanges.xml
b/ide/gsf.testrunner.ui/apichanges.xml
index 6e62dbe..60258a7 100644
--- a/ide/gsf.testrunner.ui/apichanges.xml
+++ b/ide/gsf.testrunner.ui/apichanges.xml
@@ -52,6 +52,20 @@
<!-- ACTUAL CHANGES BEGIN HERE: -->
<changes>
+ <change id="TestResultDisplayHandler">
+ <api name="CommonTestrunnerUIAPI"/>
+ <summary>Added API class and SPI interface for handlers displaying
test results</summary>
+ <version major="1" minor="22"/>
+ <date day="19" month="2" year="2021"/>
+ <author login="dbalek"/>
+ <compatibility addition="yes"/>
+ <description>
+ Added API class and SPI interface for handlers displaying test
results.
+ Allows for alternative handling of tests results (e.g. sending over
+ LSP to clients for display).
+ </description>
+ <class package="org.netbeans.modules.gsf.testrunner.ui.spi"
name="TestResultDisplayHandler"/>
+ </change>
<change id="TestCreatorConfiguration_validity">
<api name="CommonTestrunnerUIAPI"/>
<summary>Added methods to determine the validity of the configuration
panel in the "Create Tests" dialog</summary>
diff --git a/ide/gsf.testrunner.ui/manifest.mf
b/ide/gsf.testrunner.ui/manifest.mf
index 083f334..898943b 100644
--- a/ide/gsf.testrunner.ui/manifest.mf
+++ b/ide/gsf.testrunner.ui/manifest.mf
@@ -2,5 +2,5 @@ Manifest-Version: 1.0
OpenIDE-Module: org.netbeans.modules.gsf.testrunner.ui
OpenIDE-Module-Layer: org/netbeans/modules/gsf/testrunner/ui/layer.xml
OpenIDE-Module-Localizing-Bundle:
org/netbeans/modules/gsf/testrunner/ui/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.21
+OpenIDE-Module-Specification-Version: 1.22
diff --git
a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/ResultDisplayHandler.java
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/ResultDisplayHandler.java
index c4f8727..dd2a5f3 100644
---
a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/ResultDisplayHandler.java
+++
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/ResultDisplayHandler.java
@@ -40,6 +40,7 @@ import org.netbeans.modules.gsf.testrunner.api.Report;
import org.netbeans.modules.gsf.testrunner.api.TestSession;
import org.netbeans.modules.gsf.testrunner.api.TestSuite;
import org.netbeans.modules.gsf.testrunner.ui.api.Manager;
+import org.netbeans.modules.gsf.testrunner.ui.api.TestResultDisplayHandler;
import org.openide.ErrorManager;
import org.openide.util.Lookup;
import org.openide.windows.IOContainer;
@@ -51,7 +52,7 @@ import org.openide.windows.OutputWriter;
*
* @author Marian Petras. Erno Mononen
*/
-public final class ResultDisplayHandler {
+public final class ResultDisplayHandler implements
TestResultDisplayHandler.Spi<ResultDisplayHandler> {
private static final Logger LOGGER =
Logger.getLogger(ResultDisplayHandler.class.getName());
@@ -118,7 +119,7 @@ public final class ResultDisplayHandler {
dividerSettings.getLocation());
}
- public int getTotalTests() {
+ public int getTotalTests(final ResultDisplayHandler token) {
return statisticsPanel.getTreePanel().getTotalTests();
}
@@ -178,7 +179,7 @@ public final class ResultDisplayHandler {
/**
*/
- public void displayOutput(final String text, final boolean error) {
+ public void displayOutput(final ResultDisplayHandler token, final String
text, final boolean error) {
/* Called from the AntLogger's thread */
@@ -220,7 +221,7 @@ public final class ResultDisplayHandler {
* @param suiteName name of the running suite; or {@code null} in the
case
* of anonymous suite
*/
- public void displaySuiteRunning(String suiteName) {
+ public void displaySuiteRunning(final ResultDisplayHandler token, String
suiteName) {
synchronized (this) {
@@ -240,7 +241,7 @@ public final class ResultDisplayHandler {
*
* @param suite name of the running suite
*/
- public void displaySuiteRunning(TestSuite suite) {
+ public void displaySuiteRunning(final ResultDisplayHandler token,
TestSuite suite) {
synchronized (this) {
assert runningSuite == null;
suite = (suite != null) ? suite : TestSuite.ANONYMOUS_TEST_SUITE;
@@ -254,7 +255,7 @@ public final class ResultDisplayHandler {
/**
*/
- public void displayReport(final Report report) {
+ public void displayReport(final ResultDisplayHandler token, final Report
report) {
synchronized (this) {
if (treePanel == null) {
@@ -272,7 +273,7 @@ public final class ResultDisplayHandler {
/**
*/
- public void displayMessage(final String msg) {
+ public void displayMessage(final ResultDisplayHandler token, final String
msg) {
/* Called from the AntLogger's thread */
@@ -289,7 +290,7 @@ public final class ResultDisplayHandler {
/**
*/
- public void displayMessageSessionFinished(final String msg) {
+ public void displayMessageSessionFinished(final ResultDisplayHandler
token, final String msg) {
/* Called from the AntLogger's thread */
@@ -395,4 +396,9 @@ public final class ResultDisplayHandler {
Lookup getLookup() {
return l;
}
+
+ @Override
+ public ResultDisplayHandler create(TestSession session) {
+ return null;
+ }
}
diff --git
a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/Manager.java
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/Manager.java
index 94d37bc..2a8cca7 100644
---
a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/Manager.java
+++
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/Manager.java
@@ -276,7 +276,7 @@ public final class Manager {
final String text,
final boolean error) {
- final ResultDisplayHandler displayHandler = getDisplayHandler(session);
+ final TestResultDisplayHandler displayHandler =
getDisplayHandler(session);
displayHandler.displayOutput(text, error);
displayInWindow(session, displayHandler);
}
@@ -290,7 +290,7 @@ public final class Manager {
public synchronized void displaySuiteRunning(final TestSession session,
final String suiteName) {
- final ResultDisplayHandler displayHandler = getDisplayHandler(session);
+ final TestResultDisplayHandler displayHandler =
getDisplayHandler(session);
displayHandler.displaySuiteRunning(suiteName);
displayInWindow(session, displayHandler);
}
@@ -304,7 +304,7 @@ public final class Manager {
public synchronized void displaySuiteRunning(final TestSession session,
final TestSuite suite) {
- final ResultDisplayHandler displayHandler = getDisplayHandler(session);
+ final TestResultDisplayHandler displayHandler =
getDisplayHandler(session);
displayHandler.displaySuiteRunning(suite);
displayInWindow(session, displayHandler);
}
@@ -334,7 +334,7 @@ public final class Manager {
/* Called from the AntLogger's thread */
report.setCompleted(completed);
- final ResultDisplayHandler displayHandler = getDisplayHandler(session);
+ final TestResultDisplayHandler displayHandler =
getDisplayHandler(session);
displayHandler.displayReport(report);
displayInWindow(session, displayHandler);
}
@@ -364,7 +364,7 @@ public final class Manager {
/* Called from the AntLogger's thread */
- final ResultDisplayHandler displayHandler = getDisplayHandler(session);
+ final TestResultDisplayHandler displayHandler =
getDisplayHandler(session);
displayInWindow(session, displayHandler, sessionEnd);
if (!sessionEnd) {
displayHandler.displayMessage(message);
@@ -376,7 +376,7 @@ public final class Manager {
/**
*/
private void displayInWindow(final TestSession session,
- final ResultDisplayHandler displayHandler) {
+ final TestResultDisplayHandler
displayHandler) {
displayInWindow(session, displayHandler, false);
}
@@ -388,7 +388,7 @@ public final class Manager {
"LBL_NotificationDisplayer_NoTestsExecuted_title=No tests executed for
project: {0}",
"LBL_NotificationDisplayer_detailsText=Open Test Results Window"})
private void displayInWindow(final TestSession session,
- final ResultDisplayHandler displayHandler,
+ final TestResultDisplayHandler displayHandler,
final boolean sessionEnd) {
final boolean firstDisplay = (testSessions.add(session) == true);
@@ -447,9 +447,9 @@ public final class Manager {
*
*/
private class Displayer implements Runnable {
- private final ResultDisplayHandler displayHandler;
+ private final TestResultDisplayHandler displayHandler;
private final boolean promote;
- Displayer(final ResultDisplayHandler displayHandler,
+ Displayer(final TestResultDisplayHandler displayHandler,
final boolean promote) {
this.displayHandler = displayHandler;
this.promote = promote;
@@ -465,48 +465,50 @@ public final class Manager {
/** singleton of the <code>ResultDisplayHandler</code> */
// the ResultDisplayHandler holds TestSession and is referenced from other
// places so we use WeakReference, otherwise there would be memory leak
- private Map<TestSession,WeakReference<ResultDisplayHandler>>
displayHandlers;
+ private Map<TestSession,WeakReference<TestResultDisplayHandler>>
displayHandlers;
private Semaphore lock;
/**
*/
@NbBundle.Messages({"Null_Session_Error=Test session passed was null"})
- private synchronized ResultDisplayHandler getDisplayHandler(final
TestSession session) {
+ private synchronized TestResultDisplayHandler getDisplayHandler(final
TestSession session) {
// just in case a client passes null as a test session catch it early
here
assert session != null : Bundle.Null_Session_Error();
- ResultDisplayHandler displayHandler = null;
+ TestResultDisplayHandler displayHandler = null;
if (displayHandlers != null) {
- WeakReference<ResultDisplayHandler> reference =
displayHandlers.get(session);
+ WeakReference<TestResultDisplayHandler> reference =
displayHandlers.get(session);
if (reference != null) {
displayHandler = reference.get();
}
} else {
- displayHandlers = new
WeakHashMap<TestSession,WeakReference<ResultDisplayHandler>>(7);
+ displayHandlers = new
WeakHashMap<TestSession,WeakReference<TestResultDisplayHandler>>(7);
}
if (displayHandler == null) {
- displayHandler = new ResultDisplayHandler(session);
- createIO(displayHandler);
- displayHandlers.put(session, new
WeakReference<ResultDisplayHandler>(displayHandler));
- final ResultDisplayHandler dispHandler = displayHandler;
- lock = new Semaphore(1);
- try {
- lock.acquire(1);
- } catch (InterruptedException e) {
- LOGGER.log(Level.FINE, "Current thread was interrupted while
acquiring a permit: {0}", e);
- }
- Mutex.EVENT.writeAccess(new Runnable() {
+ displayHandler = TestResultDisplayHandler.create(session);
+ displayHandlers.put(session, new
WeakReference<TestResultDisplayHandler>(displayHandler));
+ TestResultDisplayHandler.Spi handlerSpi = displayHandler.getSpi();
+ if (handlerSpi instanceof ResultDisplayHandler) {
+ createIO((ResultDisplayHandler)handlerSpi);
+ lock = new Semaphore(1);
+ try {
+ lock.acquire(1);
+ } catch (InterruptedException e) {
+ LOGGER.log(Level.FINE, "Current thread was interrupted
while acquiring a permit: {0}", e);
+ }
+ Mutex.EVENT.writeAccess(new Runnable() {
- @Override
- public void run() {
- StatisticsPanel comp = (StatisticsPanel)
dispHandler.getDisplayComponent().getLeftComponent();
- dispHandler.setTreePanel(comp.getTreePanel());
- lock.release();
+ @Override
+ public void run() {
+ StatisticsPanel comp = (StatisticsPanel)
((ResultDisplayHandler)handlerSpi).getDisplayComponent().getLeftComponent();
+
((ResultDisplayHandler)handlerSpi).setTreePanel(comp.getTreePanel());
+ lock.release();
+ }
+ });
+ try {
+ lock.acquire(1);
+ } catch (InterruptedException e) {
+ LOGGER.log(Level.FINE, "Current thread was interrupted
while acquiring a permit: {0}", e);
}
- });
- try {
- lock.acquire(1);
- } catch (InterruptedException e) {
- LOGGER.log(Level.FINE, "Current thread was interrupted while
acquiring a permit: {0}", e);
}
}
return displayHandler;
diff --git
a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestMethodController.java
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestMethodController.java
index 2107a4d..8ec2330 100644
---
a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestMethodController.java
+++
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestMethodController.java
@@ -36,16 +36,22 @@ public class TestMethodController {
}
public static final class TestMethod {
+ private final String testClassName;
private final SingleMethod method;
private final Position start;
private final Position end;
- public TestMethod(SingleMethod method, Position start, Position end) {
+ public TestMethod(String testClassName, SingleMethod method, Position
start, Position end) {
+ this.testClassName = testClassName;
this.method = method;
this.start = start;
this.end = end;
}
+ public String getTestClassName() {
+ return testClassName;
+ }
+
public SingleMethod method() {
return method;
}
@@ -57,8 +63,5 @@ public class TestMethodController {
public Position end() {
return end;
}
-
-
}
-
}
diff --git
a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestResultDisplayHandler.java
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestResultDisplayHandler.java
new file mode 100644
index 0000000..e500e9f
--- /dev/null
+++
b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestResultDisplayHandler.java
@@ -0,0 +1,220 @@
+/*
+ * 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.gsf.testrunner.ui.api;
+
+import org.netbeans.modules.gsf.testrunner.api.Report;
+import org.netbeans.modules.gsf.testrunner.api.TestSession;
+import org.netbeans.modules.gsf.testrunner.api.TestSuite;
+import org.netbeans.modules.gsf.testrunner.ui.ResultDisplayHandler;
+import org.openide.util.Lookup;
+
+/**
+ * API class for handlers displaying test results.
+ *
+ * @since 1.22
+ * @author Dusan Balek
+ */
+public abstract class TestResultDisplayHandler {
+
+ /**
+ * Creates the {@link TestResultDisplayHandler} instance for the test
session.
+ *
+ * @param session test session
+ * @return {@link TestResultDisplayHandler} instance
+ */
+ public static final TestResultDisplayHandler create(TestSession session) {
+ Spi provider = Lookup.getDefault().lookup(Spi.class);
+ if (provider != null) {
+ return new Impl<>(provider.create(session), provider);
+ } else {
+ return new Impl<>(null, new ResultDisplayHandler(session));
+ }
+ }
+
+ private TestResultDisplayHandler() {
+ }
+
+ /**
+ * Display output produced by running test.
+ *
+ * @param text output text
+ * @param error mark the output text as error
+ */
+ public abstract void displayOutput(String text, boolean error);
+
+ /**
+ * Display information that a test suite is running.
+ *
+ * @param suiteName name of the running suite; or {@code null} in the case
+ * of anonymous suite
+ */
+ public abstract void displaySuiteRunning(String suiteName);
+
+ /**
+ * Display information that a test suite is running.
+ *
+ * @param suite the running suite
+ */
+ public abstract void displaySuiteRunning(TestSuite suite);
+
+ /**
+ * Display test results.
+ *
+ * @param report summary report to display
+ */
+ public abstract void displayReport(Report report);
+
+ /**
+ * Display message produced by running test.
+ *
+ * @param message message to display
+ */
+ public abstract void displayMessage(String message);
+
+ /**
+ * Display information that a test session has finished.
+ *
+ * @param message message to display
+ */
+ public abstract void displayMessageSessionFinished(String message);
+
+ /**
+ * Return total number of tests in session if known.
+ *
+ * @return number of tests
+ */
+ public abstract int getTotalTests();
+
+ /**
+ * Interface providing SPI for {@link TestResultDisplayHandler}s.
+ * Instances should be registered in the default lookup.
+ */
+ public static interface Spi<T> {
+
+ /**
+ * Creates {@link Spi} instance for the test session.
+ * @param session test session
+ * @return {@link Spi} instance
+ */
+ public T create(TestSession session);
+
+ /**
+ * Display output produced by running test.
+ *
+ * @param text output text
+ * @param error mark the output text as error
+ */
+ public void displayOutput(T token, String text, boolean error);
+
+ /**
+ * Display information that a test suite is running.
+ *
+ * @param suiteName name of the running suite; or {@code null} in the
case
+ * of anonymous suite
+ */
+ public void displaySuiteRunning(T token, String suiteName);
+
+ /**
+ * Display information that a test suite is running.
+ *
+ * @param suite the running suite
+ */
+ public void displaySuiteRunning(T token, TestSuite suite);
+
+ /**
+ * Display test results.
+ *
+ * @param report summary report to display
+ */
+ public void displayReport(T token, Report report);
+
+ /**
+ * Display message produced by running test.
+ *
+ * @param message message to display
+ */
+ public void displayMessage(T token, String message);
+
+ /**
+ * Display information that a test session has finished.
+ *
+ * @param message message to display
+ */
+ public void displayMessageSessionFinished(T token, String message);
+
+ /**
+ * Return total number of tests in session if known.
+ *
+ * @return number of tests
+ */
+ public int getTotalTests(T token);
+ }
+
+ private static final class Impl<T> extends TestResultDisplayHandler {
+
+ private final T token;
+ private final Spi<T> spi;
+
+ private Impl(T token, Spi<T> spi) {
+ this.token = token;
+ this.spi = spi;
+ }
+
+ public void displayOutput(String text, boolean error) {
+ spi.displayOutput(token, text, error);
+ }
+
+ @Override
+ public void displaySuiteRunning(String suiteName) {
+ spi.displaySuiteRunning(token, suiteName);
+ }
+
+ @Override
+ public void displaySuiteRunning(TestSuite suite) {
+ spi.displaySuiteRunning(token, suite);
+ }
+
+ @Override
+ public void displayReport(Report report) {
+ spi.displayReport(token, report);
+ }
+
+ @Override
+ public void displayMessage(String message) {
+ spi.displayMessage(token, message);
+ }
+
+ @Override
+ public void displayMessageSessionFinished(String message) {
+ spi.displayMessageSessionFinished(token, message);
+ }
+
+ @Override
+ public int getTotalTests() {
+ return spi.getTotalTests(token);
+ }
+
+ @Override
+ Spi<T> getSpi() {
+ return spi;
+ }
+ }
+
+ abstract Spi getSpi();
+}
diff --git a/ide/gsf.testrunner/apichanges.xml
b/ide/gsf.testrunner/apichanges.xml
index f0dbc99..1a49bed 100644
--- a/ide/gsf.testrunner/apichanges.xml
+++ b/ide/gsf.testrunner/apichanges.xml
@@ -52,6 +52,19 @@
<!-- ACTUAL CHANGES BEGIN HERE: -->
<changes>
+ <change id="CommonTestrunnerAPI_Public">
+ <api name="CommonTestrunnerAPI"/>
+ <summary>Common Test Runner API made public</summary>
+ <version major="2" minor="22"/>
+ <date day="19" month="2" year="2021"/>
+ <author login="dbalek"/>
+ <compatibility addition="yes"/>
+ <description>
+ Common Test Runner API made public instead of long list of friend
modules.
+ </description>
+ <package name="org.netbeans.modules.gsf.testrunner.api"/>
+ <package name="org.netbeans.modules.gsf.testrunner.plugin"/>
+ </change>
<change id="TestCreatorProvider_Registration_Identifier">
<api name="CommonTestrunnerAPI"/>
<summary>Added identifier attribute in TestCreatorProvider
@Registration</summary>
diff --git a/ide/gsf.testrunner/manifest.mf b/ide/gsf.testrunner/manifest.mf
index d684753..d4c8e00 100644
--- a/ide/gsf.testrunner/manifest.mf
+++ b/ide/gsf.testrunner/manifest.mf
@@ -3,5 +3,5 @@ AutoUpdate-Show-In-Client: false
OpenIDE-Module: org.netbeans.modules.gsf.testrunner/2
OpenIDE-Module-Localizing-Bundle:
org/netbeans/modules/gsf/testrunner/Bundle.properties
OpenIDE-Module-Layer: org/netbeans/modules/gsf/testrunner/layer.xml
-OpenIDE-Module-Specification-Version: 2.21
+OpenIDE-Module-Specification-Version: 2.22
diff --git a/ide/gsf.testrunner/nbproject/project.xml
b/ide/gsf.testrunner/nbproject/project.xml
index 54acb65..836606c 100644
--- a/ide/gsf.testrunner/nbproject/project.xml
+++ b/ide/gsf.testrunner/nbproject/project.xml
@@ -68,51 +68,10 @@
</run-dependency>
</dependency>
</module-dependencies>
- <friend-packages>
- <friend>com.sun.tools.tuxedo.testrunner</friend>
- <friend>org.netbeans.gradle.project</friend>
- <friend>org.netbeans.modules.gradle.test</friend>
- <friend>org.netbeans.modules.android.testrunner</friend>
- <friend>org.netbeans.modules.cnd.cncppunit</friend>
- <friend>org.netbeans.modules.cnd.testrunner</friend>
- <friend>org.netbeans.modules.gototest</friend>
- <friend>org.netbeans.modules.groovy.support</friend>
- <friend>org.netbeans.modules.gsf.testrunner.ui</friend>
- <friend>org.netbeans.modules.hudson.ui</friend>
- <friend>org.netbeans.modules.java.testrunner</friend>
- <friend>org.netbeans.modules.java.testrunner.ant</friend>
- <friend>org.netbeans.modules.java.testrunner.ui</friend>
- <friend>org.netbeans.modules.javascript.jstestdriver</friend>
- <friend>org.netbeans.modules.javascript.karma</friend>
- <friend>org.netbeans.modules.junit</friend>
- <friend>org.netbeans.modules.junit.ant</friend>
- <friend>org.netbeans.modules.junit.ant.ui</friend>
- <friend>org.netbeans.modules.junit.ui</friend>
- <friend>org.netbeans.modules.maven.junit</friend>
- <friend>org.netbeans.modules.maven.junit.ui</friend>
- <friend>org.netbeans.modules.mobility.j2meunit</friend>
- <friend>org.netbeans.modules.mobility.project</friend>
- <friend>org.netbeans.modules.php.atoum</friend>
- <friend>org.netbeans.modules.php.codeception</friend>
- <friend>org.netbeans.modules.php.nette.tester</friend>
- <friend>org.netbeans.modules.php.phpunit</friend>
- <friend>org.netbeans.modules.php.project</friend>
- <friend>org.netbeans.modules.python.testrunner</friend>
- <friend>org.netbeans.modules.ruby.testrunner</friend>
- <friend>org.netbeans.modules.selenium2</friend>
- <friend>org.netbeans.modules.selenium2.java</friend>
- <friend>org.netbeans.modules.selenium2.maven</friend>
- <friend>org.netbeans.modules.selenium2.php</friend>
- <friend>org.netbeans.modules.selenium2.webclient</friend>
- <friend>org.netbeans.modules.selenium2.webclient.mocha</friend>
-
<friend>org.netbeans.modules.selenium2.webclient.protractor</friend>
- <friend>org.netbeans.modules.testng</friend>
- <friend>org.netbeans.modules.testng.ant</friend>
- <friend>org.netbeans.modules.testng.ui</friend>
- <friend>org.netbeans.modules.web.clientproject.api</friend>
+ <public-packages>
<package>org.netbeans.modules.gsf.testrunner.api</package>
<package>org.netbeans.modules.gsf.testrunner.plugin</package>
- </friend-packages>
+ </public-packages>
</data>
</configuration>
</project>
diff --git a/java/java.lsp.server/nbcode/nbproject/platform.properties
b/java/java.lsp.server/nbcode/nbproject/platform.properties
index 3b34848..2723dce 100644
--- a/java/java.lsp.server/nbcode/nbproject/platform.properties
+++ b/java/java.lsp.server/nbcode/nbproject/platform.properties
@@ -162,7 +162,6 @@ disabled.modules=\
org.netbeans.modules.gradle.kit,\
org.netbeans.modules.gradle.persistence,\
org.netbeans.modules.gradle.spring,\
- org.netbeans.modules.gradle.test,\
org.netbeans.modules.html.angular,\
org.netbeans.modules.html.custom,\
org.netbeans.modules.html.editor,\
@@ -258,8 +257,6 @@ disabled.modules=\
org.netbeans.modules.maven.grammar,\
org.netbeans.modules.maven.graph,\
org.netbeans.modules.maven.hints,\
- org.netbeans.modules.maven.junit,\
- org.netbeans.modules.maven.junit.ui,\
org.netbeans.modules.maven.kit,\
org.netbeans.modules.maven.osgi,\
org.netbeans.modules.maven.persistence,\
diff --git a/java/java.lsp.server/nbproject/project.xml
b/java/java.lsp.server/nbproject/project.xml
index e9a1f99..1403771 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -165,11 +165,29 @@
</run-dependency>
</dependency>
<dependency>
+
<code-name-base>org.netbeans.modules.extexecution</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>2</release-version>
+ <specification-version>1.59</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
+
<code-name-base>org.netbeans.modules.gsf.testrunner</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>2</release-version>
+ <specification-version>2.22</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.gsf.testrunner.ui</code-name-base>
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
- <specification-version>1.21</specification-version>
+ <specification-version>1.22</specification-version>
</run-dependency>
</dependency>
<dependency>
@@ -216,6 +234,15 @@
</run-dependency>
</dependency>
<dependency>
+
<code-name-base>org.netbeans.modules.java.project</code-name-base>
+ <build-prerequisite/>
+ <compile-dependency/>
+ <run-dependency>
+ <release-version>1</release-version>
+ <specification-version>1.83</specification-version>
+ </run-dependency>
+ </dependency>
+ <dependency>
<code-name-base>org.netbeans.modules.java.source</code-name-base>
<build-prerequisite/>
<compile-dependency/>
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
index cb1c2b9..fef4535 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchDelegate.java
@@ -22,7 +22,6 @@ import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
@@ -40,12 +39,13 @@ import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.queries.UnitTestForSourceQuery;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
+import org.netbeans.modules.java.lsp.server.Utils;
import org.netbeans.modules.java.lsp.server.debugging.DebugAdapterContext;
import org.netbeans.modules.java.lsp.server.debugging.NbSourceProvider;
import org.netbeans.modules.java.lsp.server.progress.OperationContext;
import org.netbeans.modules.java.lsp.server.progress.ProgressOperationEvent;
import org.netbeans.modules.java.lsp.server.progress.ProgressOperationListener;
-import org.netbeans.modules.progress.spi.InternalHandle;
+import org.netbeans.modules.java.lsp.server.progress.TestProgressHandler;
import org.netbeans.spi.project.ActionProgress;
import org.netbeans.spi.project.ActionProvider;
import org.netbeans.spi.project.SingleMethod;
@@ -68,7 +68,7 @@ public abstract class NbLaunchDelegate {
// no op.
}
- public final CompletableFuture<Void> nbLaunch(FileObject toRun, String
method, DebugAdapterContext context, boolean debug,
Consumer<NbProcessConsole.ConsoleMessage> consoleMessages) {
+ public final CompletableFuture<Void> nbLaunch(FileObject toRun, String
method, DebugAdapterContext context, boolean debug, boolean testRun,
Consumer<NbProcessConsole.ConsoleMessage> consoleMessages) {
CompletableFuture<Void> launchFuture = new CompletableFuture<>();
NbProcessConsole ioContext = new NbProcessConsole(consoleMessages);
SingleMethod singleMethod;
@@ -77,7 +77,7 @@ public abstract class NbLaunchDelegate {
} else {
singleMethod = null;
}
- CompletableFuture<Pair<ActionProvider, String>> commandFuture =
findTargetWithPossibleRebuild(toRun, singleMethod, debug, ioContext);
+ CompletableFuture<Pair<ActionProvider, String>> commandFuture =
findTargetWithPossibleRebuild(toRun, singleMethod, debug, testRun, ioContext);
commandFuture.thenAccept((providerAndCommand) -> {
if (debug) {
DebuggerManager.getDebuggerManager().addDebuggerListener(new
DebuggerManagerAdapter() {
@@ -119,11 +119,6 @@ public abstract class NbLaunchDelegate {
notifyFinished(context, success);
}
};
- Lookup launchCtx = new ProxyLookup(
- Lookups.fixed(
- toRun, ioContext, progress
- ), Lookup.getDefault()
- );
OperationContext ctx = OperationContext.find(Lookup.getDefault());
ctx.addProgressOperationListener(null, new
ProgressOperationListener() {
@Override
@@ -131,6 +126,11 @@ public abstract class NbLaunchDelegate {
context.setProcessExecutorHandle(e.getProgressHandle());
}
});
+ TestProgressHandler testProgressHandler =
ctx.getClient().getNbCodeCapabilities().hasTestResultsSupport() ? new
TestProgressHandler(ctx.getClient(), Utils.toUri(toRun)) : null;
+ Lookup launchCtx = new ProxyLookup(
+ testProgressHandler != null ? Lookups.fixed(toRun,
ioContext, progress, testProgressHandler) : Lookups.fixed(toRun, ioContext,
progress),
+ Lookup.getDefault()
+ );
Lookup lookup;
if (singleMethod != null) {
@@ -149,8 +149,8 @@ public abstract class NbLaunchDelegate {
return launchFuture;
}
- private CompletableFuture<Pair<ActionProvider, String>>
findTargetWithPossibleRebuild(FileObject toRun, SingleMethod singleMethod,
boolean debug, NbProcessConsole ioContext) throws IllegalArgumentException {
- Pair<ActionProvider, String> providerAndCommand = findTarget(toRun,
singleMethod, debug);
+ private CompletableFuture<Pair<ActionProvider, String>>
findTargetWithPossibleRebuild(FileObject toRun, SingleMethod singleMethod,
boolean debug, boolean testRun, NbProcessConsole ioContext) throws
IllegalArgumentException {
+ Pair<ActionProvider, String> providerAndCommand = findTarget(toRun,
singleMethod, debug, testRun);
if (providerAndCommand != null) {
return CompletableFuture.completedFuture(providerAndCommand);
}
@@ -166,7 +166,7 @@ public abstract class NbLaunchDelegate {
@Override
public void finished(boolean success) {
if (success) {
- Pair<ActionProvider, String> providerAndCommand =
findTarget(toRun, singleMethod, debug);
+ Pair<ActionProvider, String> providerAndCommand =
findTarget(toRun, singleMethod, debug, testRun);
if (providerAndCommand != null) {
afterBuild.complete(providerAndCommand);
return;
@@ -199,14 +199,14 @@ public abstract class NbLaunchDelegate {
return afterBuild;
}
- protected static @CheckForNull Pair<ActionProvider, String>
findTarget(FileObject toRun, SingleMethod singleMethod, boolean debug) {
+ protected static @CheckForNull Pair<ActionProvider, String>
findTarget(FileObject toRun, SingleMethod singleMethod, boolean debug, boolean
testRun) {
ClassPath sourceCP = ClassPath.getClassPath(toRun, ClassPath.SOURCE);
FileObject fileRoot = sourceCP != null ? sourceCP.findOwnerRoot(toRun)
: null;
boolean mainSource;
if (fileRoot != null) {
mainSource = UnitTestForSourceQuery.findUnitTests(fileRoot).length
> 0;
} else {
- mainSource = true;
+ mainSource = !testRun;
}
ActionProvider provider = null;
String command = null;
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchRequestHandler.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchRequestHandler.java
index 66ab8d9..2ed7418 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchRequestHandler.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/debugging/launch/NbLaunchRequestHandler.java
@@ -124,7 +124,8 @@ public final class NbLaunchRequestHandler {
context.setSourcePaths((String[])
launchArguments.get("sourcePaths"));
}
String singleMethod = (String)launchArguments.get("singleMethod");
- activeLaunchHandler.nbLaunch(file, singleMethod, context, !noDebug,
new OutputListener(context)).thenRun(() -> {
+ boolean testRun = (Boolean) launchArguments.getOrDefault("testRun",
Boolean.FALSE);
+ activeLaunchHandler.nbLaunch(file, singleMethod, context, !noDebug,
testRun, new OutputListener(context)).thenRun(() -> {
activeLaunchHandler.postLaunch(launchArguments, context);
resultFuture.complete(null);
}).exceptionally(e -> {
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/TestProgressHandler.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/TestProgressHandler.java
new file mode 100644
index 0000000..2818905
--- /dev/null
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/progress/TestProgressHandler.java
@@ -0,0 +1,140 @@
+/*
+ * 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.java.lsp.server.progress;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.netbeans.api.extexecution.print.LineConvertors;
+import org.netbeans.modules.gsf.testrunner.api.Report;
+import org.netbeans.modules.gsf.testrunner.api.TestSession;
+import org.netbeans.modules.gsf.testrunner.api.TestSuite;
+import org.netbeans.modules.gsf.testrunner.ui.api.TestResultDisplayHandler;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.java.lsp.server.protocol.TestProgressParams;
+import org.netbeans.modules.java.lsp.server.protocol.TestSuiteInfo;
+import org.openide.filesystems.FileObject;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public final class TestProgressHandler implements
TestResultDisplayHandler.Spi<TestProgressHandler> {
+
+ private final NbCodeLanguageClient client;
+ private final String uri;
+
+ public TestProgressHandler(NbCodeLanguageClient client, String uri) {
+ this.client = client;
+ this.uri = uri;
+ }
+
+ @Override
+ public TestProgressHandler create(TestSession session) {
+ return this;
+ }
+
+ @Override
+ public void displayOutput(TestProgressHandler token, String text, boolean
error) {
+ }
+
+ @Override
+ public void displaySuiteRunning(TestProgressHandler token, String
suiteName) {
+ client.notifyTestProgress(new TestProgressParams(uri, new
TestSuiteInfo(suiteName, TestSuiteInfo.State.Running)));
+ }
+
+ @Override
+ public void displaySuiteRunning(TestProgressHandler token, TestSuite
suite) {
+ client.notifyTestProgress(new TestProgressParams(uri, new
TestSuiteInfo(suite.getName(), TestSuiteInfo.State.Running)));
+ }
+
+ @Override
+ public void displayReport(TestProgressHandler token, Report report) {
+ Map<String, FileObject> fileLocations = new HashMap<>();
+ List<TestSuiteInfo.TestCaseInfo> tests =
report.getTests().stream().map((test) -> {
+ String className = test.getClassName();
+ String displayName = test.getDisplayName();
+ String shortName = displayName.startsWith(className + '.') ?
displayName.substring(className.length() + 1) : displayName;
+ int idx = shortName.indexOf('(');
+ if (idx > 0) {
+ shortName = shortName.substring(0, idx);
+ }
+ String status;
+ switch (test.getStatus()) {
+ case PASSED:
+ status = TestSuiteInfo.State.Passed;
+ break;
+ case ERROR:
+ status = TestSuiteInfo.State.Errored;
+ break;
+ case FAILED:
+ status = TestSuiteInfo.State.Failed;
+ break;
+ case SKIPPED:
+ status = TestSuiteInfo.State.Skipped;
+ break;
+ default:
+ throw new IllegalStateException("Unexpected testcase
status: " + test.getStatus());
+ }
+ List<String> stackTrace = test.getTrouble() != null ?
Arrays.asList(test.getTrouble().getStackTrace()) : null;
+ String location = test.getLocation();
+ FileObject fo = location != null ?
fileLocations.computeIfAbsent(location, loc -> {
+ LineConvertors.FileLocator fileLocator =
test.getSession().getProject().getLookup().lookup(LineConvertors.FileLocator.class);
+ int i = loc.indexOf(':');
+ if (i > 0) {
+ loc = loc.substring(0, i);
+ }
+ return fileLocator != null ? fileLocator.find(loc) : null;
+ }) : null;
+ return new TestSuiteInfo.TestCaseInfo(className + ':' + shortName,
shortName, displayName,
+ fo != null ? Utils.toUri(fo) : null, null, status,
stackTrace);
+ }).collect(Collectors.toList());
+ String status;
+ switch (report.getStatus()) {
+ case PASSED:
+ case FAILED:
+ status = TestSuiteInfo.State.Completed;
+ break;
+ case ERROR:
+ status = TestSuiteInfo.State.Errored;
+ break;
+ default:
+ throw new IllegalStateException("Unexpected testsuite status:
" + report.getStatus());
+ }
+ FileObject fo = fileLocations.size() == 1 ?
fileLocations.values().iterator().next() : null;
+ client.notifyTestProgress(new TestProgressParams(uri, new
TestSuiteInfo(report.getSuiteClassName(),
+ fo != null ? Utils.toUri(fo) : null, null, status, tests)));
+ }
+
+ @Override
+ public void displayMessage(TestProgressHandler token, String message) {
+ }
+
+ @Override
+ public void displayMessageSessionFinished(TestProgressHandler token,
String message) {
+ }
+
+ @Override
+ public int getTotalTests(TestProgressHandler token) {
+ return 0;
+ }
+}
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java
index 33046d3..60a83ea 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientCapabilities.java
@@ -47,7 +47,15 @@ public final class NbCodeClientCapabilities {
* </ul>
*/
private Boolean statusBarMessageSupport;
-
+
+ /**
+ * Supports test results display:
+ * <ul>
+ * <li>window/notifyTestProgress
+ * </ul>
+ */
+ private Boolean testResultsSupport;
+
public ClientCapabilities getClientCapabilities() {
return clientCaps;
}
@@ -63,7 +71,19 @@ public final class NbCodeClientCapabilities {
public void setStatusBarMessageSupport(Boolean statusBarMessageSupport) {
this.statusBarMessageSupport = statusBarMessageSupport;
}
-
+
+ public Boolean getTestResultsSupport() {
+ return testResultsSupport;
+ }
+
+ public boolean hasTestResultsSupport() {
+ return testResultsSupport != null && testResultsSupport.booleanValue();
+ }
+
+ public void setTestResultsSupport(Boolean testResultsSupport) {
+ this.testResultsSupport = testResultsSupport;
+ }
+
private NbCodeClientCapabilities withCapabilities(ClientCapabilities caps)
{
if (caps == null) {
caps = new ClientCapabilities();
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
index 975f018..0d4f513 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeClientWrapper.java
@@ -76,6 +76,11 @@ class NbCodeClientWrapper implements NbCodeLanguageClient {
}
@Override
+ public void notifyTestProgress(TestProgressParams params) {
+ remote.notifyTestProgress(params);
+ }
+
+ @Override
public CompletableFuture<ApplyWorkspaceEditResponse>
applyEdit(ApplyWorkspaceEditParams params) {
return remote.applyEdit(params);
}
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
index 50fe926..6954b40 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/NbCodeLanguageClient.java
@@ -63,6 +63,15 @@ public interface NbCodeLanguageClient extends LanguageClient
{
public CompletableFuture<String> showInputBox(@NonNull ShowInputBoxParams
params);
/**
+ * Notifies client of running tests progress. Provides information about a
test suite being loaded,
+ * started, completed or skipped during a test run.
+ *
+ * @param params test run information
+ */
+ @JsonNotification("window/notifyTestProgress")
+ public void notifyTestProgress(@NonNull TestProgressParams params);
+
+ /**
* Returns extended code capabilities.
* @return code capabilities.
*/
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java
index 3b17385..633c703 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/QuickPickItem.java
@@ -86,7 +86,7 @@ public class QuickPickItem {
* A human-readable string which is rendered prominent.
*/
public void setLabel(@NonNull final String label) {
- this.label = label;
+ this.label = Preconditions.checkNotNull(label, "label");
}
/**
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
index d2b0251..15e2b62 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/Server.java
@@ -225,7 +225,7 @@ public final class Server {
private static final RequestProcessor SERVER_INIT_RP = new
RequestProcessor(LanguageServerImpl.class.getName());
- private static class LanguageServerImpl implements LanguageServer,
LanguageClientAware {
+ static class LanguageServerImpl implements LanguageServer,
LanguageClientAware {
private static final Logger LOG =
Logger.getLogger(LanguageServerImpl.class.getName());
private NbCodeClientWrapper client;
@@ -241,7 +241,7 @@ public final class Server {
return sessionLookup;
}
- private void asyncOpenSelectedProjects(CompletableFuture f,
List<FileObject> projectCandidates) {
+ void asyncOpenSelectedProjects(CompletableFuture f, List<FileObject>
projectCandidates) {
List<Project> projects = new ArrayList<>();
try {
for (FileObject candidate : projectCandidates) {
@@ -323,7 +323,7 @@ public final class Server {
capabilities.setDocumentHighlightProvider(true);
capabilities.setReferencesProvider(true);
List<String> commands = new ArrayList<>(Arrays.asList(
- JAVA_BUILD_WORKSPACE, GRAALVM_PAUSE_SCRIPT,
JAVA_SUPER_IMPLEMENTATION));
+ JAVA_BUILD_WORKSPACE, JAVA_LOAD_WORKSPACE_TESTS,
GRAALVM_PAUSE_SCRIPT, JAVA_SUPER_IMPLEMENTATION));
for (CodeGenerator codeGenerator :
Lookup.getDefault().lookupAll(CodeGenerator.class)) {
commands.addAll(codeGenerator.getCommands());
}
@@ -419,6 +419,7 @@ public final class Server {
}
public static final String JAVA_BUILD_WORKSPACE = "java.build.workspace";
+ public static final String JAVA_LOAD_WORKSPACE_TESTS =
"java.load.workspace.tests";
public static final String JAVA_SUPER_IMPLEMENTATION =
"java.super.implementation";
public static final String GRAALVM_PAUSE_SCRIPT = "graalvm.pause.script";
static final String INDEXING_COMPLETED = "Indexing completed.";
@@ -450,6 +451,11 @@ public final class Server {
}
@Override
+ public void notifyTestProgress(TestProgressParams params) {
+ logWarning(params);
+ }
+
+ @Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
logWarning();
return caps;
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
index a262919..68ad7a0 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
@@ -65,7 +65,7 @@ public class ShowInputBoxParams {
* The text to display underneath the input box.
*/
public void setPrompt(@NonNull final String prompt) {
- this.prompt = prompt;
+ this.prompt = Preconditions.checkNotNull(prompt, "prompt");
}
/**
@@ -81,7 +81,7 @@ public class ShowInputBoxParams {
* The value to prefill in the input box.
*/
public void setValue(@NonNull final String value) {
- this.value = value;
+ this.value = Preconditions.checkNotNull(value, "value");
}
@Override
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java
index 107084f..5046329 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowQuickPickParams.java
@@ -78,7 +78,7 @@ public class ShowQuickPickParams {
* A string to show as placeholder in the input box to guide the user what
to pick on.
*/
public void setPlaceHolder(@NonNull final String placeHolder) {
- this.placeHolder = placeHolder;
+ this.placeHolder = Preconditions.checkNotNull(placeHolder,
"placeHolder");
}
/**
@@ -109,7 +109,7 @@ public class ShowQuickPickParams {
* A list of items.
*/
public void setItems(@NonNull final List<QuickPickItem> items) {
- this.items = items;
+ this.items = Preconditions.checkNotNull(items, "items");
}
@Override
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TestProgressParams.java
similarity index 54%
copy from
java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
copy to
java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TestProgressParams.java
index a262919..21550fe 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/ShowInputBoxParams.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TestProgressParams.java
@@ -29,79 +29,84 @@ import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
* @author Dusan Balek
*/
@SuppressWarnings("all")
-public class ShowInputBoxParams {
+public class TestProgressParams {
/**
- * The text to display underneath the input box.
+ * The test suite or the workspace folder the test suite belongs to.
*/
@NonNull
- private String prompt;
+ private String uri;
/**
- * The value to prefill in the input box.
+ * Information about a test suite being loaded, started, completed or
skipped
+ * during a test run.
*/
@NonNull
- private String value;
+ private TestSuiteInfo suite;
- public ShowInputBoxParams() {
- this("", "");
+ public TestProgressParams() {
+ this("", new TestSuiteInfo());
}
- public ShowInputBoxParams(@NonNull final String prompt, @NonNull final
String value) {
- this.prompt = Preconditions.checkNotNull(prompt, "prompt");
- this.value = Preconditions.checkNotNull(value, "value");
+ public TestProgressParams(@NonNull final String uri, @NonNull final
TestSuiteInfo suite) {
+ this.uri = Preconditions.checkNotNull(uri, "uri");
+ this.suite = Preconditions.checkNotNull(suite, "suite");
}
/**
- * The text to display underneath the input box.
+ * The test suite or the workspace folder the test suite belongs to.
*/
@Pure
@NonNull
- public String getPrompt() {
- return prompt;
+ public String getUri() {
+ return uri;
}
/**
- * The text to display underneath the input box.
+ * The test suite or the workspace folder the test suite belongs to.
*/
- public void setPrompt(@NonNull final String prompt) {
- this.prompt = prompt;
+ public void setUri(@NonNull final String uri) {
+ this.uri = Preconditions.checkNotNull(uri, "uri");
}
/**
- * The value to prefill in the input box.
+ * Information about a test suite being loaded, started, completed or
skipped
+ * during a test run.
*/
@Pure
@NonNull
- public String getValue() {
- return value;
+ public TestSuiteInfo getSuite() {
+ return suite;
}
/**
- * The value to prefill in the input box.
+ * Information about a test suite being loaded, started, completed or
skipped
+ * during a test run.
*/
- public void setValue(@NonNull final String value) {
- this.value = value;
+ public void setSuite(@NonNull final TestSuiteInfo suite) {
+ this.suite = Preconditions.checkNotNull(suite, "suite");
}
@Override
@Pure
public String toString() {
ToStringBuilder b = new ToStringBuilder(this);
- b.add("prompt", prompt);
- b.add("value", value);
+ b.add("uri", uri);
+ b.add("suite", suite);
return b.toString();
}
@Override
+ @Pure
public int hashCode() {
int hash = 5;
- hash = 59 * hash + Objects.hashCode(this.prompt);
- hash = 59 * hash + Objects.hashCode(this.value);
+ hash = 59 * hash + Objects.hashCode(this.uri);
+ hash = 59 * hash + Objects.hashCode(this.suite);
return hash;
}
@Override
+ @Pure
public boolean equals(Object obj) {
if (this == obj) {
return true;
@@ -112,11 +117,11 @@ public class ShowInputBoxParams {
if (getClass() != obj.getClass()) {
return false;
}
- final ShowInputBoxParams other = (ShowInputBoxParams) obj;
- if (!Objects.equals(this.prompt, other.prompt)) {
+ final TestProgressParams other = (TestProgressParams) obj;
+ if (!Objects.equals(this.uri, other.uri)) {
return false;
}
- if (!Objects.equals(this.value, other.value)) {
+ if (!Objects.equals(this.suite, other.suite)) {
return false;
}
return true;
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TestSuiteInfo.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TestSuiteInfo.java
new file mode 100644
index 0000000..d92337d
--- /dev/null
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TestSuiteInfo.java
@@ -0,0 +1,477 @@
+/*
+ * 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.java.lsp.server.protocol;
+
+import java.util.List;
+import java.util.Objects;
+import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
+import org.eclipse.lsp4j.util.Preconditions;
+import org.eclipse.xtext.xbase.lib.Pure;
+import org.eclipse.xtext.xbase.lib.util.ToStringBuilder;
+
+/**
+ * Information about a test suite.
+ *
+ * @author Dusan Balek
+ */
+public final class TestSuiteInfo {
+
+ /**
+ * The test suite name to be displayed by the Test Explorer.
+ */
+ @NonNull
+ private String suiteName;
+
+ /**
+ * The file containing this suite (if known).
+ */
+ private String file;
+
+ /**
+ * The line within the specified file where the suite definition starts
(if known).
+ */
+ private Integer line;
+
+ /**
+ * The state of the tests suite. Can be one of the following values:
+ * "loaded" | "running" | "completed" | "errored"
+ */
+ @NonNull
+ private String state;
+
+ /**
+ * The test cases of the test suite.
+ */
+ private List<TestCaseInfo> tests;
+
+ public TestSuiteInfo() {
+ this("", "");
+ }
+
+ public TestSuiteInfo(@NonNull final String suiteName, @NonNull final
String state) {
+ this.suiteName = Preconditions.checkNotNull(suiteName, "suiteName");
+ this.state = Preconditions.checkNotNull(state, "state");
+ }
+
+ public TestSuiteInfo(@NonNull final String suiteName, final String file,
final Integer line, @NonNull final String state, final List<TestCaseInfo>
tests) {
+ this(suiteName, state);
+ this.file = file;
+ this.line = line;
+ this.tests = tests;
+ }
+
+ /**
+ * The test suite name to be displayed by the Test Explorer.
+ */
+ @Pure
+ @NonNull
+ public String getSuiteName() {
+ return suiteName;
+ }
+
+ /**
+ * The test suite name to be displayed by the Test Explorer.
+ */
+ public void setSuiteName(@NonNull final String suiteName) {
+ this.suiteName = Preconditions.checkNotNull(suiteName, "suiteName");
+ }
+
+ /**
+ * The file containing this suite (if known).
+ */
+ @Pure
+ public String getFile() {
+ return file;
+ }
+
+ /**
+ * The file containing this suite (if known).
+ */
+ public void setFile(final String file) {
+ this.file = file;
+ }
+
+ /**
+ * The line within the specified file where the suite definition starts
(if known).
+ */
+ @Pure
+ public Integer getLine() {
+ return line;
+ }
+
+ /**
+ * The line within the specified file where the suite definition starts
(if known).
+ */
+ public void setLine(final Integer line) {
+ this.line = line;
+ }
+
+ /**
+ * The state of the tests suite. Can be one of the following values:
+ * "loaded" | "running" | "completed" | "errored"
+ */
+ @Pure
+ @NonNull
+ public String getState() {
+ return state;
+ }
+
+ /**
+ * The state of the tests suite. Can be one of the following values:
+ * "loaded" | "running" | "completed" | "errored"
+ */
+ public void setState(@NonNull final String state) {
+ this.state = Preconditions.checkNotNull(state, "state");
+ }
+
+ /**
+ * The test cases of the test suite.
+ */
+ @Pure
+ public List<TestCaseInfo> getTests() {
+ return tests;
+ }
+
+ /**
+ * The test cases of the test suite.
+ */
+ public void setTests(List<TestCaseInfo> tests) {
+ this.tests = tests;
+ }
+
+ @Override
+ @Pure
+ public String toString() {
+ ToStringBuilder b = new ToStringBuilder(this);
+ b.add("suiteName", suiteName);
+ b.add("file", file);
+ b.add("line", line);
+ b.add("state", state);
+ b.add("tests", tests);
+ return b.toString();
+ }
+
+ @Override
+ @Pure
+ public int hashCode() {
+ int hash = 7;
+ hash = 67 * hash + Objects.hashCode(this.suiteName);
+ hash = 67 * hash + Objects.hashCode(this.file);
+ hash = 67 * hash + Objects.hashCode(this.line);
+ hash = 67 * hash + Objects.hashCode(this.state);
+ hash = 67 * hash + Objects.hashCode(this.tests);
+ return hash;
+ }
+
+ @Override
+ @Pure
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final TestSuiteInfo other = (TestSuiteInfo) obj;
+ if (!Objects.equals(this.suiteName, other.suiteName)) {
+ return false;
+ }
+ if (!Objects.equals(this.file, other.file)) {
+ return false;
+ }
+ if (!Objects.equals(this.line, other.line)) {
+ return false;
+ }
+ if (!Objects.equals(this.state, other.state)) {
+ return false;
+ }
+ if (!Objects.equals(this.tests, other.tests)) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Information about a test case.
+ */
+ public static class TestCaseInfo {
+
+ /**
+ * The test case ID.
+ */
+ @NonNull
+ private String id;
+
+ /**
+ * The short name to be displayed by the Test Explorer for this test
case.
+ */
+ @NonNull
+ private String shortName;
+
+ /**
+ * The full name to be displayed by the Test Explorer when you hover
over
+ * this test case.
+ */
+ @NonNull
+ private String fullName;
+
+ /**
+ * The file containing this test case (if known).
+ */
+ private String file;
+
+ /**
+ * The line within the specified file where the test case definition
starts (if known).
+ */
+ private Integer line;
+
+ /**
+ * The state of the test case. Can be one of the following values:
+ * "loaded" | "running" | "passed" | "failed" | "skipped" | "errored"
+ */
+ @NonNull
+ private String state;
+
+ /**
+ * Stack trace for a test failure.
+ */
+ private List<String> stackTrace;
+
+ public TestCaseInfo() {
+ this("", "", "", "");
+ }
+
+ public TestCaseInfo(@NonNull final String id, @NonNull final String
shortName, @NonNull final String fullName, @NonNull final String state) {
+ this.id = Preconditions.checkNotNull(id, "id");
+ this.shortName = Preconditions.checkNotNull(shortName,
"shortName");
+ this.fullName = Preconditions.checkNotNull(fullName, "fullName");
+ this.state = Preconditions.checkNotNull(state, "state");
+ }
+
+ public TestCaseInfo(@NonNull final String id, @NonNull final String
shortName, @NonNull final String fullName, final String file, final Integer
line, @NonNull final String state, final List<String> stackTrace) {
+ this(id, shortName, fullName, state);
+ this.file = file;
+ this.line = line;
+ this.stackTrace = stackTrace;
+ }
+
+ /**
+ * The test case ID.
+ */
+ @Pure
+ @NonNull
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * The test case ID.
+ */
+ public void setId(@NonNull final String id) {
+ this.id = Preconditions.checkNotNull(id, "id");
+ }
+
+ /**
+ * The short name to be displayed by the Test Explorer for this test
case.
+ */
+ @Pure
+ @NonNull
+ public String getShortName() {
+ return shortName;
+ }
+
+ /**
+ * The short name to be displayed by the Test Explorer for this test
case.
+ */
+ public void setShortName(@NonNull final String shortName) {
+ this.shortName = Preconditions.checkNotNull(shortName,
"shortName");
+ }
+
+ /**
+ * The full name to be displayed by the Test Explorer when you hover
over
+ * this test case.
+ */
+ @Pure
+ @NonNull
+ public String getFullName() {
+ return fullName;
+ }
+
+ /**
+ * The full name to be displayed by the Test Explorer when you hover
over
+ * this test case.
+ */
+ public void setFullName(@NonNull final String fullName) {
+ this.fullName = Preconditions.checkNotNull(fullName, "fullName");
+ }
+
+ /**
+ * The file containing this test case (if known).
+ */
+ @Pure
+ public String getFile() {
+ return file;
+ }
+
+ /**
+ * The file containing this test case (if known).
+ */
+ public void setFile(final String file) {
+ this.file = file;
+ }
+
+ /**
+ * The line within the specified file where the test case definition
starts (if known).
+ */
+ @Pure
+ public Integer getLine() {
+ return line;
+ }
+
+ /**
+ * The line within the specified file where the test case definition
starts (if known).
+ */
+ public void setLine(final Integer line) {
+ this.line = line;
+ }
+
+ /**
+ * The state of the test case. Can be one of the following values:
+ * "loaded" | "running" | "passed" | "failed" | "skipped" | "errored"
+ */
+ @Pure
+ @NonNull
+ public String getState() {
+ return state;
+ }
+
+ /**
+ * The state of the test case. Can be one of the following values:
+ * "loaded" | "running" | "passed" | "failed" | "skipped" | "errored"
+ */
+ public void setState(@NonNull final String state) {
+ this.state = Preconditions.checkNotNull(state, "state");
+ }
+
+ /**
+ * Stack trace for a test failure.
+ */
+ @Pure
+ public List<String> getStackTrace() {
+ return stackTrace;
+ }
+
+ /**
+ * Stack trace for a test failure.
+ */
+ public void setStackTrace(final List<String> stackTrace) {
+ this.stackTrace = stackTrace;
+ }
+
+ @Override
+ @Pure
+ public String toString() {
+ ToStringBuilder b = new ToStringBuilder(this);
+ b.add("id", id);
+ b.add("shortName", shortName);
+ b.add("fullName", fullName);
+ b.add("file", file);
+ b.add("line", line);
+ b.add("state", state);
+ b.add("stackTrace", stackTrace);
+ return b.toString();
+ }
+
+ @Override
+ @Pure
+ public int hashCode() {
+ int hash = 5;
+ hash = 97 * hash + Objects.hashCode(this.id);
+ hash = 97 * hash + Objects.hashCode(this.shortName);
+ hash = 97 * hash + Objects.hashCode(this.fullName);
+ hash = 97 * hash + Objects.hashCode(this.file);
+ hash = 97 * hash + Objects.hashCode(this.line);
+ hash = 97 * hash + Objects.hashCode(this.state);
+ hash = 97 * hash + Objects.hashCode(this.stackTrace);
+ return hash;
+ }
+
+ @Override
+ @Pure
+ public boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final TestCaseInfo other = (TestCaseInfo) obj;
+ if (!Objects.equals(this.id, other.id)) {
+ return false;
+ }
+ if (!Objects.equals(this.shortName, other.shortName)) {
+ return false;
+ }
+ if (!Objects.equals(this.fullName, other.fullName)) {
+ return false;
+ }
+ if (!Objects.equals(this.file, other.file)) {
+ return false;
+ }
+ if (!Objects.equals(this.line, other.line)) {
+ return false;
+ }
+ if (!Objects.equals(this.state, other.state)) {
+ return false;
+ }
+ if (!Objects.equals(this.stackTrace, other.stackTrace)) {
+ return false;
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Constants for test states.
+ */
+ public static final class State {
+
+ private State() {}
+
+ public static final String Loaded = "loaded";
+
+ public static final String Running = "running";
+
+ public static final String Completed = "completed";
+
+ public static final String Passed = "passed";
+
+ public static final String Failed = "failed";
+
+ public static final String Skipped = "skipped";
+
+ public static final String Errored = "errored";
+ }
+}
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
index 6f4d889..dc88000 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TextDocumentServiceImpl.java
@@ -234,7 +234,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
private final Map<String, RequestProcessor.Task> diagnosticTasks = new
HashMap<>();
private NbCodeLanguageClient client;
- public TextDocumentServiceImpl() {
+ TextDocumentServiceImpl() {
Lookup.getDefault().lookup(RefreshDocument.class).register(this);
}
@@ -830,7 +830,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends
LocationLink>>> definition(DefinitionParams params) {
String uri = params.getTextDocument().getUri();
- JavaSource js = getSource(uri);
+ JavaSource js = getJavaSource(uri);
GoToTarget[] target = new GoToTarget[1];
LineMap[] thisFileLineMap = new LineMap[1];
try {
@@ -855,7 +855,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
@Override
public CompletableFuture<Either<List<? extends Location>, List<? extends
LocationLink>>> implementation(ImplementationParams params) {
String uri = params.getTextDocument().getUri();
- JavaSource js = getSource(uri);
+ JavaSource js = getJavaSource(uri);
List<GoToTarget> targets = new ArrayList<>();
LineMap[] thisFileLineMap = new LineMap[1];
try {
@@ -944,7 +944,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
}
};
WORKER.post(() -> {
- JavaSource js = getSource(params.getTextDocument().getUri());
+ JavaSource js = getJavaSource(params.getTextDocument().getUri());
try {
WhereUsedQuery[] query = new WhereUsedQuery[1];
List<Location> locations = new ArrayList<>();
@@ -1066,7 +1066,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
Preferences node = MarkOccurencesSettings.getCurrentNode();
- JavaSource js = getSource(params.getTextDocument().getUri());
+ JavaSource js = getJavaSource(params.getTextDocument().getUri());
List<DocumentHighlight> result = new ArrayList<>();
try {
js.runUserActionTask(cc -> {
@@ -1090,7 +1090,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
@Override
public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>>
documentSymbol(DocumentSymbolParams params) {
- JavaSource js = getSource(params.getTextDocument().getUri());
+ JavaSource js = getJavaSource(params.getTextDocument().getUri());
List<Either<SymbolInformation, DocumentSymbol>> result = new
ArrayList<>();
try {
js.runUserActionTask(cc -> {
@@ -1373,7 +1373,8 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
@Override
public CompletableFuture<List<? extends CodeLens>> codeLens(CodeLensParams
params) {
- JavaSource source = getSource(params.getTextDocument().getUri());
+ String uri = params.getTextDocument().getUri();
+ JavaSource source = getJavaSource(uri);
if (source == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@@ -1381,25 +1382,36 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
try {
source.runUserActionTask(cc -> {
cc.toPhase(Phase.ELEMENTS_RESOLVED);
- List<CodeLens> lens = new ArrayList<>();
//look for test methods:
+ List<TestMethod> testMethods = new ArrayList<>();
for (ComputeTestMethods.Factory methodsFactory :
Lookup.getDefault().lookupAll(ComputeTestMethods.Factory.class)) {
- List<TestMethod> methods =
methodsFactory.create().computeTestMethods(cc);
- if (methods != null) {
- for (TestMethod method : methods) {
- Range range = new
Range(Utils.createPosition(cc.getCompilationUnit(), method.start().getOffset()),
-
Utils.createPosition(cc.getCompilationUnit(), method.end().getOffset()));
- List<Object> arguments = Arrays.asList(new
Object[]{method.method().getFile().toURI(), method.method().getMethodName()});
- lens.add(new CodeLens(range,
- new Command("Run test",
"java.run.codelens", arguments),
- null));
- lens.add(new CodeLens(range,
- new Command("Debug test",
"java.debug.codelens", arguments),
- null));
+
testMethods.addAll(methodsFactory.create().computeTestMethods(cc));
+ }
+ if (!testMethods.isEmpty()) {
+ String testClassName = null;
+ List<TestSuiteInfo.TestCaseInfo> tests = new
ArrayList<>(testMethods.size());
+ for (TestMethod testMethod : testMethods) {
+ if (testClassName == null) {
+ testClassName = testMethod.getTestClassName();
+ }
+ String id = testMethod.getTestClassName() + ':' +
testMethod.method().getMethodName();
+ String fullName = testMethod.getTestClassName() + '.'
+ testMethod.method().getMethodName();
+ int line =
Utils.createPosition(cc.getCompilationUnit(),
testMethod.start().getOffset()).getLine();
+ tests.add(new TestSuiteInfo.TestCaseInfo(id,
testMethod.method().getMethodName(), fullName, uri, line,
TestSuiteInfo.State.Loaded, null));
+ }
+ Integer line = null;
+ Trees trees = cc.getTrees();
+ for (Tree tree : cc.getCompilationUnit().getTypeDecls()) {
+ Element element =
trees.getElement(trees.getPath(cc.getCompilationUnit(), tree));
+ if (element != null && element.getKind().isClass() &&
((TypeElement)element).getQualifiedName().contentEquals(testClassName)) {
+ line =
Utils.createPosition(cc.getCompilationUnit(),
(int)trees.getSourcePositions().getStartPosition(cc.getCompilationUnit(),
tree)).getLine();
+ break;
}
}
+ client.notifyTestProgress(new TestProgressParams(uri, new
TestSuiteInfo(testClassName, uri, line, TestSuiteInfo.State.Loaded, tests)));
}
//look for main methods:
+ List<CodeLens> lens = new ArrayList<>();
new TreePathScanner<Void, Void>() {
public Void visitMethod(MethodTree tree, Void p) {
Element el =
cc.getTrees().getElement(getCurrentPath());
@@ -1407,10 +1419,10 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
Range range = Utils.treeRange(cc, tree);
List<Object> arguments =
Collections.singletonList(params.getTextDocument().getUri());
lens.add(new CodeLens(range,
- new Command("Run main",
"java.run.codelens", arguments),
+ new Command("Run main",
"java.run.single", arguments),
null));
lens.add(new CodeLens(range,
- new Command("Debug main",
"java.debug.codelens", arguments),
+ new Command("Debug main",
"java.debug.single", arguments),
null));
}
return null;
@@ -1446,7 +1458,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
@Override
public CompletableFuture<Either<Range, PrepareRenameResult>>
prepareRename(PrepareRenameParams params) {
- JavaSource source = getSource(params.getTextDocument().getUri());
+ JavaSource source = getJavaSource(params.getTextDocument().getUri());
if (source == null) {
return CompletableFuture.completedFuture(Either.forLeft(null));
}
@@ -1504,7 +1516,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
}
};
WORKER.post(() -> {
- JavaSource js = getSource(params.getTextDocument().getUri());
+ JavaSource js = getJavaSource(params.getTextDocument().getUri());
try {
RenameRefactoring[] refactoring = new RenameRefactoring[1];
js.runUserActionTask(cc -> {
@@ -1603,7 +1615,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
@Override
public CompletableFuture<List<FoldingRange>>
foldingRange(FoldingRangeRequestParams params) {
- JavaSource source = getSource(params.getTextDocument().getUri());
+ JavaSource source = getJavaSource(params.getTextDocument().getUri());
if (source == null) {
return CompletableFuture.completedFuture(Collections.emptyList());
}
@@ -1730,7 +1742,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
}
CompletableFuture<Location> superImplementation(String uri, Position
position) {
- JavaSource js = getSource(uri);
+ JavaSource js = getJavaSource(uri);
GoToTarget[] target = new GoToTarget[1];
LineMap[] thisFileLineMap = new LineMap[1];
try {
@@ -1952,7 +1964,7 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
public List<ErrorDescription> computeErrors(CompilationInfo info,
Document doc) throws IOException;
}
- private JavaSource getSource(String fileUri) {
+ public JavaSource getJavaSource(String fileUri) {
Document doc = openedDocuments.get(fileUri);
if (doc == null) {
try {
@@ -1966,6 +1978,20 @@ public class TextDocumentServiceImpl implements
TextDocumentService, LanguageCli
}
}
+ public Source getSource(String fileUri) {
+ Document doc = openedDocuments.get(fileUri);
+ if (doc == null) {
+ try {
+ FileObject file = Utils.fromUri(fileUri);
+ return Source.create(file);
+ } catch (MalformedURLException ex) {
+ return null;
+ }
+ } else {
+ return Source.create(doc);
+ }
+ }
+
public static List<TextEdit> modify2TextEdits(JavaSource js,
Task<WorkingCopy> task) throws IOException {
FileObject[] file = new FileObject[1];
LineMap[] lm = new LineMap[1];
diff --git
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
index 587624f..80305fe 100644
---
a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
+++
b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/WorkspaceServiceImpl.java
@@ -20,17 +20,23 @@ package org.netbeans.modules.java.lsp.server.protocol;
import com.google.gson.Gson;
import com.google.gson.JsonPrimitive;
+import com.sun.source.tree.CompilationUnitTree;
+import com.sun.source.tree.LineMap;
+import com.sun.source.tree.Tree;
import com.sun.source.util.TreePath;
+import com.sun.source.util.Trees;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collections;
+import java.util.Enumeration;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
+import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.lang.model.element.Element;
@@ -45,38 +51,44 @@ import org.eclipse.lsp4j.SymbolInformation;
import org.eclipse.lsp4j.WorkspaceSymbolParams;
import org.eclipse.lsp4j.services.LanguageClient;
import org.eclipse.lsp4j.services.LanguageClientAware;
-import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.lsp4j.services.WorkspaceService;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.debugger.ActionsManager;
import org.netbeans.api.debugger.DebuggerManager;
+import org.netbeans.api.java.project.JavaProjectConstants;
+import org.netbeans.api.java.queries.UnitTestForSourceQuery;
import org.netbeans.api.java.source.ClasspathInfo;
+import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.ElementHandle;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.SourceUtils;
-import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
+import org.netbeans.api.project.ProjectUtils;
+import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.ui.OpenProjects;
+import org.netbeans.modules.gsf.testrunner.ui.api.TestMethodController;
import org.netbeans.modules.java.lsp.server.Utils;
import org.netbeans.modules.java.source.ui.JavaSymbolProvider;
import org.netbeans.modules.java.source.ui.JavaSymbolProvider.ResultHandler;
import
org.netbeans.modules.java.source.ui.JavaSymbolProvider.ResultHandler.Exec;
import org.netbeans.modules.java.source.usages.ClassIndexImpl;
+import org.netbeans.modules.java.testrunner.ui.spi.ComputeTestMethods;
+import org.netbeans.modules.parsing.api.ParserManager;
+import org.netbeans.modules.parsing.api.ResultIterator;
+import org.netbeans.modules.parsing.api.Source;
+import org.netbeans.modules.parsing.api.UserTask;
import org.netbeans.modules.parsing.lucene.support.Queries;
+import org.netbeans.modules.parsing.spi.ParseException;
import org.netbeans.spi.jumpto.type.SearchType;
import org.netbeans.spi.project.ActionProgress;
import org.netbeans.spi.project.ActionProvider;
-import org.netbeans.spi.project.SingleMethod;
-import org.openide.awt.StatusDisplayer;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.URLMapper;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
-import org.openide.util.Mutex;
-import org.openide.util.NbBundle;
import org.openide.util.Pair;
import org.openide.util.RequestProcessor;
import org.openide.util.lookup.Lookups;
@@ -90,10 +102,10 @@ public final class WorkspaceServiceImpl implements
WorkspaceService, LanguageCli
private static final RequestProcessor WORKER = new
RequestProcessor(WorkspaceServiceImpl.class.getName(), 1, false, false);
private final Gson gson = new Gson();
- private final LanguageServer server;
+ private final Server.LanguageServerImpl server;
private NbCodeLanguageClient client;
- public WorkspaceServiceImpl(LanguageServer server) {
+ WorkspaceServiceImpl(Server.LanguageServerImpl server) {
this.server = server;
}
@@ -117,6 +129,46 @@ public final class WorkspaceServiceImpl implements
WorkspaceService, LanguageCli
progressOfCompilation.checkStatus();
return progressOfCompilation.getFinishFuture();
}
+ case Server.JAVA_LOAD_WORKSPACE_TESTS: {
+ String uri = ((JsonPrimitive)
params.getArguments().get(0)).getAsString();
+ FileObject file;
+ try {
+ file = URLMapper.findFileObject(new URL(uri));
+ } catch (MalformedURLException ex) {
+ Exceptions.printStackTrace(ex);
+ return CompletableFuture.completedFuture(true);
+ }
+ CompletableFuture<Project[]> projectsFuture = new
CompletableFuture<>();
+ server.asyncOpenSelectedProjects(projectsFuture,
Collections.singletonList(file));
+ return projectsFuture.thenApply(projects -> {
+ List<TestMethodController.TestMethod> testMethods = new
ArrayList<>();
+ for (Project prj : projects) {
+ Set<URL> testRootURLs = new HashSet<>();
+ for (SourceGroup sg :
ProjectUtils.getSources(prj).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA))
{
+ for (URL url :
UnitTestForSourceQuery.findUnitTests(sg.getRootFolder())) {
+ testRootURLs.add(url);
+ }
+ }
+ findTestMethods(testRootURLs, testMethods);
+ }
+ if (testMethods.isEmpty()) {
+ return Collections.emptyList();
+ }
+ Map<FileObject, TestSuiteInfo> file2TestSuites = new
HashMap<>();
+ for (TestMethodController.TestMethod testMethod :
testMethods) {
+ TestSuiteInfo suite =
file2TestSuites.computeIfAbsent(testMethod.method().getFile(), fo -> {
+ String foUri = Utils.toUri(fo);
+ Integer line =
getTestLine(((TextDocumentServiceImpl)server.getTextDocumentService()).getJavaSource(foUri),
testMethod.getTestClassName());
+ return new
TestSuiteInfo(testMethod.getTestClassName(), foUri, line,
TestSuiteInfo.State.Loaded, new ArrayList<>());
+ });
+ String id = testMethod.getTestClassName() + ':' +
testMethod.method().getMethodName();
+ String fullName = testMethod.getTestClassName() + '.'
+ testMethod.method().getMethodName();
+ int line =
Utils.createPosition(testMethod.method().getFile(),
testMethod.start().getOffset()).getLine();
+ suite.getTests().add(new
TestSuiteInfo.TestCaseInfo(id, testMethod.method().getMethodName(), fullName,
suite.getFile(), line, TestSuiteInfo.State.Loaded, null));
+ }
+ return file2TestSuites.values();
+ });
+ }
case Server.JAVA_SUPER_IMPLEMENTATION:
String uri = ((JsonPrimitive)
params.getArguments().get(0)).getAsString();
Position pos =
gson.fromJson(gson.toJson(params.getArguments().get(1)), Position.class);
@@ -131,6 +183,58 @@ public final class WorkspaceServiceImpl implements
WorkspaceService, LanguageCli
throw new UnsupportedOperationException("Command not supported: " +
params.getCommand());
}
+ private void findTestMethods(Set<URL> testRootURLs,
List<TestMethodController.TestMethod> testMethods) {
+ for (URL testRootURL : testRootURLs) {
+ FileObject testRoot = URLMapper.findFileObject(testRootURL);
+ List<Source> sources = new ArrayList<>();
+ Enumeration<? extends FileObject> children =
testRoot.getChildren(true);
+ while(children.hasMoreElements()) {
+ FileObject fo = children.nextElement();
+ if (fo.hasExt("java")) {
+
sources.add(((TextDocumentServiceImpl)server.getTextDocumentService()).getSource(Utils.toUri(fo)));
+ }
+ }
+ if (!sources.isEmpty()) {
+ try {
+ ParserManager.parse(sources, new UserTask() {
+ @Override
+ public void run(ResultIterator resultIterator) throws
Exception {
+ CompilationController cc =
CompilationController.get(resultIterator.getParserResult());
+ cc.toPhase(Phase.ELEMENTS_RESOLVED);
+ for (ComputeTestMethods.Factory methodsFactory :
Lookup.getDefault().lookupAll(ComputeTestMethods.Factory.class)) {
+
testMethods.addAll(methodsFactory.create().computeTestMethods(cc));
+ }
+ }
+ });
+ } catch (ParseException ex) {}
+ }
+ }
+ }
+
+ private Integer getTestLine(JavaSource javaSource, String className) {
+ final int[] offset = new int[] {-1};
+ final LineMap[] lm = new LineMap[1];
+ if (javaSource != null) {
+ try {
+ javaSource.runUserActionTask(cc -> {
+ cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+ Trees trees = cc.getTrees();
+ CompilationUnitTree cu = cc.getCompilationUnit();
+ lm[0] = cu.getLineMap();
+ for (Tree tree : cu.getTypeDecls()) {
+ Element element = trees.getElement(trees.getPath(cu,
tree));
+ if (element != null && element.getKind().isClass() &&
((TypeElement)element).getQualifiedName().contentEquals(className)) {
+ offset[0] =
(int)trees.getSourcePositions().getStartPosition(cu, tree);
+ return;
+ }
+ }
+ }, true);
+ } catch (IOException ioe) {
+ }
+ }
+ return offset[0] < 0 ? null : Utils.createPosition(lm[0],
offset[0]).getLine();
+ }
+
@Override
public CompletableFuture<List<? extends SymbolInformation>>
symbol(WorkspaceSymbolParams params) {
String query = params.getQuery();
@@ -329,6 +433,7 @@ public final class WorkspaceServiceImpl implements
WorkspaceService, LanguageCli
@Override
protected synchronized void started() {
running++;
+ notify();
}
@Override
@@ -342,6 +447,12 @@ public final class WorkspaceServiceImpl implements
WorkspaceService, LanguageCli
}
synchronized final void checkStatus() {
+ if (running == 0) {
+ try {
+ wait(100);
+ } catch (InterruptedException ex) {
+ }
+ }
if (running <= success + failure) {
commandFinished.complete(failure == 0);
}
diff --git
a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/progress/TestProgressHandlerTest.java
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/progress/TestProgressHandlerTest.java
new file mode 100644
index 0000000..261c80a
--- /dev/null
+++
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/progress/TestProgressHandlerTest.java
@@ -0,0 +1,200 @@
+/*
+ * 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.java.lsp.server.progress;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import org.eclipse.lsp4j.MessageActionItem;
+import org.eclipse.lsp4j.MessageParams;
+import org.eclipse.lsp4j.PublishDiagnosticsParams;
+import org.eclipse.lsp4j.ShowMessageRequestParams;
+import org.junit.Test;
+import org.netbeans.api.extexecution.print.LineConvertors;
+import org.netbeans.api.project.Project;
+import org.netbeans.junit.NbTestCase;
+import org.netbeans.modules.gsf.testrunner.api.Report;
+import org.netbeans.modules.gsf.testrunner.api.Status;
+import org.netbeans.modules.gsf.testrunner.api.TestSession;
+import org.netbeans.modules.gsf.testrunner.api.Testcase;
+import org.netbeans.modules.gsf.testrunner.api.Trouble;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeClientCapabilities;
+import org.netbeans.modules.java.lsp.server.protocol.NbCodeLanguageClient;
+import org.netbeans.modules.java.lsp.server.protocol.QuickPickItem;
+import org.netbeans.modules.java.lsp.server.protocol.ShowInputBoxParams;
+import org.netbeans.modules.java.lsp.server.protocol.ShowQuickPickParams;
+import org.netbeans.modules.java.lsp.server.protocol.ShowStatusMessageParams;
+import org.netbeans.modules.java.lsp.server.protocol.TestProgressParams;
+import org.netbeans.modules.java.lsp.server.protocol.TestSuiteInfo;
+import org.openide.filesystems.FileObject;
+import org.openide.filesystems.FileUtil;
+import org.openide.util.Exceptions;
+import org.openide.util.Lookup;
+import org.openide.util.lookup.Lookups;
+
+/**
+ *
+ * @author Dusan Balek
+ */
+public class TestProgressHandlerTest extends NbTestCase {
+
+ public TestProgressHandlerTest(String name) {
+ super(name);
+ }
+
+ @Test
+ public void testProgress() {
+ FileObject fo = null;
+ try {
+ fo = FileUtil.toFileObject(getWorkDir());
+ } catch (IOException ex) {
+ Exceptions.printStackTrace(ex);
+ }
+ assertNotNull(fo);
+ List<TestProgressParams> msgs = new ArrayList<>();
+ MockLanguageClient mlc = new MockLanguageClient(msgs);
+ TestProgressHandler progressHandler = new TestProgressHandler(mlc,
fo.toURI().toString());
+ progressHandler.displaySuiteRunning(progressHandler, "TestSuiteName");
+ FileObject projectDir = fo;
+ Project project = new Project() {
+ @Override
+ public FileObject getProjectDirectory() {
+ return projectDir;
+ }
+
+ @Override
+ public Lookup getLookup() {
+ return Lookups.fixed(new LineConvertors.FileLocator() {
+ @Override
+ public FileObject find(String filename) {
+ return "TestSuiteName".equals(filename) ? projectDir :
null;
+ }
+ });
+ }
+ };
+ Report report = new Report("TestSuiteName", project);
+ TestSession session = new TestSession("TestSession", project,
TestSession.SessionType.TEST);
+ Testcase[] tests = new Testcase[] {
+ new Testcase("test1", "TestSuiteName.test1", "TEST", session),
+ new Testcase("test2", "TestSuiteName.test2", "TEST", session)
+ };
+ tests[0].setClassName("TestSuiteName");
+ tests[0].setLocation("TestSuiteName:1");
+ tests[0].setStatus(Status.PASSED);
+ tests[1].setClassName("TestSuiteName");
+ tests[1].setLocation("TestSuiteName:2");
+ tests[1].setStatus(Status.FAILED);
+ Trouble trouble = new Trouble(false);
+ trouble.setStackTrace(new String[] {"TestSuiteName:2"});
+ tests[1].setTrouble(trouble);
+ report.setTests(Arrays.asList(tests));
+ report.setTotalTests(2);
+ report.setPassed(1);
+ report.setFailures(1);
+ progressHandler.displayReport(progressHandler, report);
+ assertEquals("Two messages", 2, msgs.size());
+ assertEquals(fo.toURI().toString(), msgs.get(0).getUri());
+ TestSuiteInfo suite = msgs.get(0).getSuite();
+ assertEquals("TestSuiteName", suite.getSuiteName());
+ assertEquals(TestSuiteInfo.State.Running, suite.getState());
+ assertEquals(fo.toURI().toString(), msgs.get(1).getUri());
+ suite = msgs.get(1).getSuite();
+ assertEquals("TestSuiteName", suite.getSuiteName());
+ assertEquals(TestSuiteInfo.State.Completed, suite.getState());
+ assertEquals(2, suite.getTests().size());
+ TestSuiteInfo.TestCaseInfo testCase = suite.getTests().get(0);
+ assertEquals("TestSuiteName:test1", testCase.getId());
+ assertEquals("test1", testCase.getShortName());
+ assertEquals("TestSuiteName.test1", testCase.getFullName());
+ assertEquals(fo.toURI().toString(), testCase.getFile());
+ assertEquals(TestSuiteInfo.State.Passed, testCase.getState());
+ assertNull(testCase.getStackTrace());
+ testCase = suite.getTests().get(1);
+ assertEquals("TestSuiteName:test2", testCase.getId());
+ assertEquals("test2", testCase.getShortName());
+ assertEquals("TestSuiteName.test2", testCase.getFullName());
+ assertEquals(fo.toURI().toString(), testCase.getFile());
+ assertEquals(TestSuiteInfo.State.Failed, testCase.getState());
+ assertNotNull(testCase.getStackTrace());
+ }
+
+ private static final class MockLanguageClient implements
NbCodeLanguageClient {
+ private final List<TestProgressParams> messages;
+
+ MockLanguageClient(List<TestProgressParams> messages) {
+ this.messages = messages;
+ }
+
+ @Override
+ public void telemetryEvent(Object object) {
+ fail();
+ }
+
+ @Override
+ public void publishDiagnostics(PublishDiagnosticsParams diagnostics) {
+ fail();
+ }
+
+ @Override
+ public void showMessage(MessageParams messageParams) {
+ fail();
+ }
+
+ @Override
+ public CompletableFuture<MessageActionItem>
showMessageRequest(ShowMessageRequestParams requestParams) {
+ fail();
+ return null;
+ }
+
+ @Override
+ public void logMessage(MessageParams message) {
+ fail();
+ }
+
+ @Override
+ public void showStatusBarMessage(ShowStatusMessageParams params) {
+ fail();
+ }
+
+ @Override
+ public CompletableFuture<List<QuickPickItem>>
showQuickPick(ShowQuickPickParams params) {
+ fail();
+ return null;
+ }
+
+ @Override
+ public CompletableFuture<String> showInputBox(ShowInputBoxParams
params) {
+ fail();
+ return null;
+ }
+
+ @Override
+ public void notifyTestProgress(TestProgressParams params) {
+ messages.add(params);
+ }
+
+ @Override
+ public NbCodeClientCapabilities getNbCodeCapabilities() {
+ fail();
+ return null;
+ }
+ }
+}
diff --git
a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
index 9b54d39..4b3bd3f 100644
---
a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
+++
b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
@@ -19,7 +19,6 @@
package org.netbeans.modules.java.lsp.server.protocol;
import com.google.gson.Gson;
-import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import java.io.File;
import java.io.FileWriter;
@@ -1354,6 +1353,11 @@ public class ServerTest extends NbTestCase {
}
@Override
+ public void notifyTestProgress(TestProgressParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
throw new UnsupportedOperationException("Not supported yet.");
}
@@ -2714,6 +2718,11 @@ public class ServerTest extends NbTestCase {
}
@Override
+ public void notifyTestProgress(TestProgressParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
throw new UnsupportedOperationException("Not supported yet.");
}
@@ -2908,6 +2917,11 @@ public class ServerTest extends NbTestCase {
}
@Override
+ public void notifyTestProgress(TestProgressParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
throw new UnsupportedOperationException("Not supported yet.");
}
@@ -3019,6 +3033,11 @@ public class ServerTest extends NbTestCase {
}
@Override
+ public void notifyTestProgress(TestProgressParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
throw new UnsupportedOperationException("Not supported yet.");
}
@@ -3131,6 +3150,11 @@ public class ServerTest extends NbTestCase {
}
@Override
+ public void notifyTestProgress(TestProgressParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
throw new UnsupportedOperationException("Not supported yet.");
}
@@ -3230,6 +3254,11 @@ public class ServerTest extends NbTestCase {
}
@Override
+ public void notifyTestProgress(TestProgressParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
throw new UnsupportedOperationException("Not supported yet.");
}
@@ -3334,6 +3363,11 @@ public class ServerTest extends NbTestCase {
}
@Override
+ public void notifyTestProgress(TestProgressParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
throw new UnsupportedOperationException("Not supported yet.");
}
@@ -3435,6 +3469,11 @@ public class ServerTest extends NbTestCase {
}
@Override
+ public void notifyTestProgress(TestProgressParams params) {
+ throw new UnsupportedOperationException("Not supported yet.");
+ }
+
+ @Override
public NbCodeClientCapabilities getNbCodeCapabilities() {
throw new UnsupportedOperationException("Not supported yet.");
}
diff --git a/java/java.lsp.server/vscode/package-lock.json
b/java/java.lsp.server/vscode/package-lock.json
index 23864b2..c38b96d 100644
--- a/java/java.lsp.server/vscode/package-lock.json
+++ b/java/java.lsp.server/vscode/package-lock.json
@@ -35,7 +35,8 @@
"@types/ps-node": {
"version": "0.1.0",
"resolved":
"https://registry.npmjs.org/@types/ps-node/-/ps-node-0.1.0.tgz",
- "integrity":
"sha512-HI5l+f38o93x81mbOWZ1IEzj87rGCHfN4A4QyCU1MuViT5Slvlo5F+YVvmBFHfZsEGi0Lo8TghWU2Ew6vBILNA=="
+ "integrity":
"sha512-HI5l+f38o93x81mbOWZ1IEzj87rGCHfN4A4QyCU1MuViT5Slvlo5F+YVvmBFHfZsEGi0Lo8TghWU2Ew6vBILNA==",
+ "dev": true
},
"@types/vscode": {
"version": "1.49.0",
@@ -911,6 +912,11 @@
"is-number": "^7.0.0"
}
},
+ "tslib": {
+ "version": "1.14.1",
+ "resolved":
"https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity":
"sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+ },
"typescript": {
"version": "3.9.7",
"resolved":
"https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
@@ -970,6 +976,20 @@
"rimraf": "^2.6.3"
}
},
+ "vscode-test-adapter-api": {
+ "version": "1.9.0",
+ "resolved":
"https://registry.npmjs.org/vscode-test-adapter-api/-/vscode-test-adapter-api-1.9.0.tgz",
+ "integrity":
"sha512-lltjehUP0J9H3R/HBctjlqeUCwn2t9Lbhj2Y500ib+j5Y4H3hw+hVTzuSsfw16LtxY37knlU39QIlasa7svzOQ=="
+ },
+ "vscode-test-adapter-util": {
+ "version": "0.7.1",
+ "resolved":
"https://registry.npmjs.org/vscode-test-adapter-util/-/vscode-test-adapter-util-0.7.1.tgz",
+ "integrity":
"sha512-OZZvLDDNhayVVISyTmgUntOhMzl6j9/wVGfNqI2zuR5bQIziTQlDs9W29dFXDTGXZOxazS6uiHkrr86BKDzYUA==",
+ "requires": {
+ "tslib": "^1.11.1",
+ "vscode-test-adapter-api": "^1.8.0"
+ }
+ },
"which": {
"version": "1.3.1",
"resolved":
"https://registry.npmjs.org/which/-/which-1.3.1.tgz",
diff --git a/java/java.lsp.server/vscode/package.json
b/java/java.lsp.server/vscode/package.json
index c357b49..0af9f63 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -166,9 +166,9 @@
"menus": {
"editor/context": [
{
- "command": "java.goto.super.implementation",
- "when": "nbJavaLSReady && editorLangId ==
java && editorTextFocus",
- "group": "navigation@100"
+ "command":
"java.goto.super.implementation",
+ "when": "nbJavaLSReady && editorLangId
== java && editorTextFocus",
+ "group": "navigation@100"
}
],
"commandPalette": [
@@ -209,6 +209,11 @@
},
"dependencies": {
"vscode-debugadapter": "1.42.1",
- "vscode-languageclient": "6.1.3"
- }
+ "vscode-languageclient": "6.1.3",
+ "vscode-test-adapter-api": "^1.9.0",
+ "vscode-test-adapter-util": "^0.7.1"
+ },
+ "extensionDependencies": [
+ "hbenl.vscode-test-explorer"
+ ]
}
diff --git a/java/java.lsp.server/vscode/src/extension.ts
b/java/java.lsp.server/vscode/src/extension.ts
index 7135154..86e6e25 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -37,11 +37,15 @@ import * as fs from 'fs';
import * as path from 'path';
import { ChildProcess } from 'child_process';
import * as vscode from 'vscode';
+import { testExplorerExtensionId, TestHub } from 'vscode-test-adapter-api';
+import { TestAdapterRegistrar } from 'vscode-test-adapter-util';
import * as launcher from './nbcode';
-import { StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest,
InputBoxRequest } from './protocol';
+import {NbTestAdapter} from './testAdapter';
+import { StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest,
InputBoxRequest, TestProgressNotification } from './protocol';
const API_VERSION : string = "1.0";
let client: Promise<LanguageClient>;
+let testAdapterRegistrar: TestAdapterRegistrar<NbTestAdapter>;
let nbProcess : ChildProcess | null = null;
let debugPort: number = -1;
let consoleLog: boolean = !!process.env['ENABLE_CONSOLE_LOG'];
@@ -224,30 +228,52 @@ export function activate(context: ExtensionContext):
VSNetBeansAPI {
]);
}
}));
- const runCodelens = async (uri : any, methodName : string, noDebug :
boolean) => {
+ const runDebug = async (noDebug : boolean, testRun: boolean, uri : any,
methodName? : string) => {
const editor = window.activeTextEditor;
if (editor) {
const docUri = editor.document.uri;
const workspaceFolder =
vscode.workspace.getWorkspaceFolder(docUri);
const debugConfig : vscode.DebugConfiguration = {
type: "java8+",
- name: "CodeLens Debug",
+ name: "Java Single Debug",
request: "launch",
mainClass: uri,
- singleMethod: methodName,
+ methodName,
+ testRun
};
const debugOptions : vscode.DebugSessionOptions = {
noDebug: noDebug,
}
- await vscode.debug.startDebugging(workspaceFolder, debugConfig,
debugOptions).then();
+ const ret = await vscode.debug.startDebugging(workspaceFolder,
debugConfig, debugOptions);
+ return ret ? new Promise((resolve) => {
+ const listener = vscode.debug.onDidTerminateDebugSession(() =>
{
+ listener.dispose();
+ resolve(true);
+ });
+ }) : ret;
}
};
- context.subscriptions.push(commands.registerCommand('java.run.codelens',
async (uri, methodName) => {
- await runCodelens(uri, methodName, true);
+ context.subscriptions.push(commands.registerCommand('java.run.test', async
(uri, methodName?) => {
+ await runDebug(true, true, uri, methodName);
+ }));
+ context.subscriptions.push(commands.registerCommand('java.run.single',
async (uri, methodName?) => {
+ await runDebug(true, false, uri, methodName);
}));
- context.subscriptions.push(commands.registerCommand('java.debug.codelens',
async (uri, methodName) => {
- await runCodelens(uri, methodName, false);
+ context.subscriptions.push(commands.registerCommand('java.debug.single',
async (uri, methodName?) => {
+ await runDebug(false, false, uri, methodName);
}));
+
+ // get the Test Explorer extension and register TestAdapter
+ const testExplorerExtension =
vscode.extensions.getExtension<TestHub>(testExplorerExtensionId);
+ if (testExplorerExtension) {
+ const testHub = testExplorerExtension.exports;
+ testAdapterRegistrar = new TestAdapterRegistrar(
+ testHub,
+ workspaceFolder => new NbTestAdapter(workspaceFolder,
client)
+ );
+ context.subscriptions.push(testAdapterRegistrar);
+ }
+
return Object.freeze({
version : API_VERSION
});
@@ -451,7 +477,8 @@ function doActivateWithJDK(specifiedJDK: string | null,
context: ExtensionContex
progressOnInitialization: true,
initializationOptions : {
'nbcodeCapabilities' : {
- 'statusBarMessageSupport' : true
+ 'statusBarMessageSupport' : true,
+ 'testResultsSupport' : true
}
},
errorHandler: {
@@ -487,6 +514,17 @@ function doActivateWithJDK(specifiedJDK: string | null,
context: ExtensionContex
c.onRequest(InputBoxRequest.type, async param => {
return await window.showInputBox({ prompt: param.prompt,
value: param.value });
});
+ c.onNotification(TestProgressNotification.type, param => {
+ if (testAdapterRegistrar) {
+ const ws =
workspace.getWorkspaceFolder(vscode.Uri.parse(param.uri));
+ if (ws) {
+ const adapter = testAdapterRegistrar.getAdapter(ws);
+ if (adapter) {
+ adapter.testProgress(param.suite);
+ }
+ }
+ }
+ })
handleLog(log, 'Language Client: Ready');
setClient[0](c);
commands.executeCommand('setContext', 'nbJavaLSReady', true);
diff --git a/java/java.lsp.server/vscode/src/protocol.ts
b/java/java.lsp.server/vscode/src/protocol.ts
index 6f56c6d..c42bc18 100644
--- a/java/java.lsp.server/vscode/src/protocol.ts
+++ b/java/java.lsp.server/vscode/src/protocol.ts
@@ -69,3 +69,30 @@ export interface ShowInputBoxParams {
export namespace InputBoxRequest {
export const type = new RequestType<ShowInputBoxParams, string |
undefined, void, void>('window/showInputBox');
}
+
+export interface TestProgressParams {
+ uri: string;
+ suite: TestSuite;
+}
+
+export interface TestSuite {
+ suiteName: string;
+ file?: string;
+ line?: number;
+ state: 'running' | 'completed' | 'errored';
+ tests?: TestCase[];
+}
+
+export interface TestCase {
+ id: string;
+ shortName: string;
+ fullName: string;
+ file?: string;
+ line?: number;
+ state: 'running' | 'passed' | 'failed' | 'skipped' | 'errored';
+ stackTrace?: string[];
+}
+
+export namespace TestProgressNotification {
+ export const type = new NotificationType<TestProgressParams,
void>('window/notifyTestProgress');
+};
diff --git a/java/java.lsp.server/vscode/src/test/runTest.ts
b/java/java.lsp.server/vscode/src/test/runTest.ts
index 5ed5f50..747a155 100644
--- a/java/java.lsp.server/vscode/src/test/runTest.ts
+++ b/java/java.lsp.server/vscode/src/test/runTest.ts
@@ -1,8 +1,8 @@
import * as path from 'path';
-import { runTests } from 'vscode-test';
+import { downloadAndUnzipVSCode, resolveCliPathFromVSCodeExecutablePath,
runTests } from 'vscode-test';
-import * as os from 'os';
+import * as cp from 'child_process';
import * as fs from 'fs';
async function main() {
@@ -11,6 +11,14 @@ async function main() {
// Passed to `--extensionDevelopmentPath`
const extensionDevelopmentPath = path.resolve(__dirname, '../../');
+ const vscodeExecutablePath: string = await
downloadAndUnzipVSCode('stable');
+ const cliPath: string =
resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath);
+
+ cp.spawnSync(cliPath, ['--install-extension',
'hbenl.vscode-test-explorer'], {
+ encoding: 'utf-8',
+ stdio: 'inherit',
+ });
+
// The path to test runner
// Passed to --extensionTestsPath
const extensionTestsPath = path.resolve(__dirname, './suite/index');
@@ -23,7 +31,7 @@ async function main() {
// Download VS Code, unzip it and run the integration test
await runTests({
- version: "1.50.0",
+ vscodeExecutablePath,
extensionDevelopmentPath,
extensionTestsPath,
extensionTestsEnv: {
diff --git a/java/java.lsp.server/vscode/src/testAdapter.ts
b/java/java.lsp.server/vscode/src/testAdapter.ts
new file mode 100644
index 0000000..6beed2c
--- /dev/null
+++ b/java/java.lsp.server/vscode/src/testAdapter.ts
@@ -0,0 +1,212 @@
+/*
+ * 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.
+ */
+'use strict';
+
+import { WorkspaceFolder, Event, EventEmitter, Uri, commands, debug } from
"vscode";
+import * as path from 'path';
+import { TestAdapter, TestSuiteEvent, TestEvent, TestLoadFinishedEvent,
TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteInfo,
TestInfo } from "vscode-test-adapter-api";
+import { TestSuite } from "./protocol";
+import { LanguageClient } from "vscode-languageclient";
+import { getVSCodeDownloadUrl } from "vscode-test/out/util";
+
+export class NbTestAdapter implements TestAdapter {
+
+ private disposables: { dispose(): void }[] = [];
+ private children: (TestSuiteInfo | TestInfo)[] = [];
+ private readonly testSuite: TestSuiteInfo;
+
+ private readonly testsEmitter = new EventEmitter<TestLoadStartedEvent |
TestLoadFinishedEvent>();
+ private readonly statesEmitter = new EventEmitter<TestRunStartedEvent |
TestRunFinishedEvent | TestSuiteEvent | TestEvent>();
+
+ constructor(
+ public readonly workspaceFolder: WorkspaceFolder,
+ private readonly client: Promise<LanguageClient>
+ ) {
+ this.disposables.push(this.testsEmitter);
+ this.disposables.push(this.statesEmitter);
+ this.testSuite = { type: 'suite', id: '*', label: 'Tests', children:
this.children };
+ }
+
+ get tests(): Event<TestLoadStartedEvent | TestLoadFinishedEvent> {
+ return this.testsEmitter.event;
+ }
+
+ get testStates(): Event<TestRunStartedEvent | TestRunFinishedEvent |
TestSuiteEvent | TestEvent> {
+ return this.statesEmitter.event;
+ }
+
+ async load(): Promise<void> {
+ this.testsEmitter.fire(<TestLoadStartedEvent>{ type: 'started' });
+ let clnt = await this.client;
+ console.log(clnt);
+ this.children.length = 0;
+ const loadedTests: any = await
commands.executeCommand('java.load.workspace.tests',
this.workspaceFolder.uri.toString());
+ if (loadedTests) {
+ loadedTests.forEach((suite: TestSuite) => {
+ const children: TestInfo[] = suite.tests ?
suite.tests.map(test => ({ type: 'test', id: test.id, label: test.shortName,
tooltip: test.fullName, file: test.file ? Uri.parse(test.file)?.path :
undefined, line: test.line })) : [];
+ this.children.push({ type: 'suite', id: suite.suiteName,
label: suite.suiteName, file: suite.file ? Uri.parse(suite.file)?.path :
undefined, line: suite.line, children });
+ });
+ }
+ this.testsEmitter.fire(<TestLoadFinishedEvent>{ type:
'finished', suite: this.testSuite });
+ }
+
+ async run(tests: string[]): Promise<void> {
+ this.statesEmitter.fire(<TestRunStartedEvent>{ type: 'started',
tests });
+ if (tests.length === 1) {
+ if (tests[0] === '*') {
+ await commands.executeCommand('java.run.test',
this.workspaceFolder.uri.toString());
+ this.statesEmitter.fire(<TestRunFinishedEvent>{ type:
'finished' });
+ } else {
+ const idx = tests[0].indexOf(':');
+ const suiteName = idx < 0 ? tests[0] : tests[0].slice(0, idx);
+ const current = this.children.find(s => s.id === suiteName);
+ if (current && current.file) {
+ const methodName = idx < 0 ? undefined :
tests[0].slice(idx + 1);
+ if (methodName) {
+ await commands.executeCommand('java.run.single',
Uri.file(current.file).toString(), methodName);
+ } else {
+ await commands.executeCommand('java.run.single',
Uri.file(current.file).toString());
+ }
+ this.statesEmitter.fire(<TestRunFinishedEvent>{ type:
'finished' });
+ } else {
+ this.statesEmitter.fire(<TestLoadFinishedEvent>{ type:
'finished', errorMessage: `Cannot find suite to run: ${tests[0]}` });
+ }
+ }
+ } else {
+ this.statesEmitter.fire(<TestLoadFinishedEvent>{ type:
'finished', errorMessage: 'Failed to run mutliple tests'});
+ }
+ }
+
+ async debug(tests: string[]): Promise<void> {
+ this.statesEmitter.fire(<TestRunStartedEvent>{ type: 'started',
tests });
+ if (tests.length === 1) {
+ const idx = tests[0].indexOf(':');
+ const suiteName = idx < 0 ? tests[0] : tests[0].slice(0, idx);
+ const current = this.children.find(s => s.id === suiteName);
+ if (current && current.file) {
+ const methodName = idx < 0 ? undefined : tests[0].slice(idx +
1);
+ if (methodName) {
+ await commands.executeCommand('java.debug.single',
Uri.file(current.file).toString(), methodName);
+ } else {
+ await commands.executeCommand('java.debug.single',
Uri.file(current.file).toString());
+ }
+ this.statesEmitter.fire(<TestRunFinishedEvent>{ type:
'finished' });
+ } else {
+ this.statesEmitter.fire(<TestLoadFinishedEvent>{ type:
'finished', errorMessage: `Cannot find suite to debug: ${tests[0]}` });
+ }
+ } else {
+ this.statesEmitter.fire(<TestLoadFinishedEvent>{ type:
'finished', errorMessage: 'Failed to debug mutliple tests'});
+ }
+ }
+
+ cancel(): void {
+ debug.stopDebugging();
+ }
+
+ dispose(): void {
+ this.cancel();
+ for (const disposable of this.disposables) {
+ disposable.dispose();
+ }
+ this.disposables = [];
+ }
+
+ testProgress(suite: TestSuite): void {
+ this.updateTests(suite);
+ if (suite.state === 'running') {
+ this.statesEmitter.fire(<TestSuiteEvent>{ type: 'suite', suite:
suite.suiteName, state: suite.state });
+ } else {
+ if (suite.tests) {
+ suite.tests.forEach(test => {
+ let message;
+ let decorations;
+ if (test.stackTrace) {
+ message = test.stackTrace.join('\n');
+ const testFile = test.file ?
Uri.parse(test.file)?.path : undefined;
+ if (testFile) {
+ const fileName = path.basename(testFile);
+ const line = test.stackTrace.map(frame => {
+ const info =
frame.match(/^\s*at\s*\S*\((\S*):(\d*)\)$/);
+ if (info && info.length >= 3 && info[1] ===
fileName) {
+ return parseInt(info[2]);
+ }
+ return null;
+ }).find(l => l);
+ if (line) {
+ decorations = [{ line: line - 1, message:
test.stackTrace[0] }];
+ }
+ }
+ }
+ this.statesEmitter.fire(<TestEvent>{ type: 'test', test:
test.id, state: test.state, message, decorations });
+ });
+ }
+ this.statesEmitter.fire(<TestSuiteEvent>{ type: 'suite', suite:
suite.suiteName, state: suite.state });
+ }
+ }
+
+ updateTests(suite: TestSuite): void {
+ let changed = false;
+ const currentSuite = this.children.find(s => s.id === suite.suiteName);
+ if (currentSuite) {
+ const file = suite.file ? Uri.parse(suite.file)?.path : undefined;
+ if (file && currentSuite.file !== file) {
+ currentSuite.file = file;
+ changed = true;
+ }
+ if (suite.line && currentSuite.line !== suite.line) {
+ currentSuite.line = suite.line;
+ changed = true
+ }
+ if (suite.tests) {
+ suite.tests.forEach(test => {
+ const children: (TestSuiteInfo | TestInfo)[] = [];
+ let currentTest = (currentSuite as
TestSuiteInfo).children.find(ti => ti.id === test.id);
+ if (currentTest) {
+ children.push(currentTest);
+ const file = test.file ? Uri.parse(test.file)?.path :
undefined;
+ if (file && currentTest.file !== file) {
+ currentTest.file = file;
+ changed = true;
+ }
+ if (test.line && currentTest.line !== test.line) {
+ currentTest.line = test.line;
+ changed = true;
+ }
+ } else {
+ children.push({ type: 'test', id: test.id, label:
test.shortName, tooltip: test.fullName, file: test.file ?
Uri.parse(test.file)?.path : undefined, line: test.line });
+ changed = true;
+ }
+ if ((currentSuite as TestSuiteInfo).children.length !==
children.length) {
+ changed = true;
+ }
+ (currentSuite as TestSuiteInfo).children = children;
+ });
+ }
+ } else {
+ const children: TestInfo[] = suite.tests ? suite.tests.map(test =>
{
+ return { type: 'test', id: test.id, label: test.shortName,
tooltip: test.fullName, file: test.file ? Uri.parse(test.file)?.path :
undefined, line: test.line };
+ }) : [];
+ this.children.push({ type: 'suite', id: suite.suiteName, label:
suite.suiteName, file: suite.file ? Uri.parse(suite.file)?.path : undefined,
line: suite.line, children });
+ changed = true;
+ }
+ if (changed) {
+ this.testsEmitter.fire(<TestLoadFinishedEvent>{ type: 'finished',
suite: this.testSuite });
+ }
+ }
+}
diff --git a/java/junit.ui/manifest.mf b/java/junit.ui/manifest.mf
index 691154e..dda88c0 100644
--- a/java/junit.ui/manifest.mf
+++ b/java/junit.ui/manifest.mf
@@ -1,5 +1,5 @@
Manifest-Version: 1.0
OpenIDE-Module: org.netbeans.modules.junit.ui
OpenIDE-Module-Localizing-Bundle:
org/netbeans/modules/junit/ui/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.16
+OpenIDE-Module-Specification-Version: 1.17
diff --git a/java/junit.ui/nbproject/project.xml
b/java/junit.ui/nbproject/project.xml
index 621c5b2..f0f3fad 100644
--- a/java/junit.ui/nbproject/project.xml
+++ b/java/junit.ui/nbproject/project.xml
@@ -91,7 +91,7 @@
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
- <specification-version>1.0</specification-version>
+ <specification-version>1.22</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git
a/java/junit.ui/src/org/netbeans/modules/junit/ui/actions/TestClassInfoTask.java
b/java/junit.ui/src/org/netbeans/modules/junit/ui/actions/TestClassInfoTask.java
index 9ec5443..e28cac0 100644
---
a/java/junit.ui/src/org/netbeans/modules/junit/ui/actions/TestClassInfoTask.java
+++
b/java/junit.ui/src/org/netbeans/modules/junit/ui/actions/TestClassInfoTask.java
@@ -40,6 +40,7 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
+import javax.swing.text.Position;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.JavaSource.Phase;
@@ -123,7 +124,9 @@ public final class TestClassInfoTask implements
Task<CompilationController> {
int end = (int) sp.getEndPosition(tp.getCompilationUnit(),
tp.getLeaf());
Document doc =
info.getSnapshot().getSource().getDocument(false);
try {
- result.add(new TestMethod(new SingleMethod(fileObject,
mn), doc != null ? doc.createPosition(start) : null, doc != null ?
doc.createPosition(end) : null));
+ result.add(new
TestMethod(typeElement.getQualifiedName().toString(), new
SingleMethod(fileObject, mn),
+ doc != null ? doc.createPosition(start) : new
SimplePosition(start),
+ doc != null ? doc.createPosition(end) : new
SimplePosition(end)));
} catch (BadLocationException ex) {
//ignore
}
@@ -192,4 +195,18 @@ public final class TestClassInfoTask implements
Task<CompilationController> {
}
}
+
+ private static class SimplePosition implements Position {
+
+ private final int offset;
+
+ private SimplePosition(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getOffset() {
+ return offset;
+ }
+ }
}
diff --git a/java/testng.ui/manifest.mf b/java/testng.ui/manifest.mf
index 0fca12e..3b9cb78 100644
--- a/java/testng.ui/manifest.mf
+++ b/java/testng.ui/manifest.mf
@@ -1,5 +1,5 @@
Manifest-Version: 1.0
OpenIDE-Module: org.netbeans.modules.testng.ui
OpenIDE-Module-Localizing-Bundle:
org/netbeans/modules/testng/ui/Bundle.properties
-OpenIDE-Module-Specification-Version: 1.19
+OpenIDE-Module-Specification-Version: 1.20
diff --git a/java/testng.ui/nbproject/project.xml
b/java/testng.ui/nbproject/project.xml
index 2665fb9..1bbbafb 100644
--- a/java/testng.ui/nbproject/project.xml
+++ b/java/testng.ui/nbproject/project.xml
@@ -170,7 +170,7 @@
<build-prerequisite/>
<compile-dependency/>
<run-dependency>
- <specification-version>1.0</specification-version>
+ <specification-version>1.22</specification-version>
</run-dependency>
</dependency>
<dependency>
diff --git
a/java/testng.ui/src/org/netbeans/modules/testng/ui/actions/TestClassInfoTask.java
b/java/testng.ui/src/org/netbeans/modules/testng/ui/actions/TestClassInfoTask.java
index e271969..e252f51 100644
---
a/java/testng.ui/src/org/netbeans/modules/testng/ui/actions/TestClassInfoTask.java
+++
b/java/testng.ui/src/org/netbeans/modules/testng/ui/actions/TestClassInfoTask.java
@@ -37,6 +37,7 @@ import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
+import javax.swing.text.Position;
import org.netbeans.api.java.source.CancellableTask;
import org.netbeans.api.java.source.CompilationController;
import org.netbeans.api.java.source.CompilationInfo;
@@ -113,13 +114,14 @@ public final class TestClassInfoTask implements
CancellableTask<CompilationContr
public static List<TestMethod> computeTestMethods(CompilationInfo info,
AtomicBoolean cancel, int caretPosIfAny) {
//TODO: first verify if this is a test class/class in a test source
group?
FileObject fileObject = info.getFileObject();
+ ClassTree clazz;
List<TreePath> methods;
if (caretPosIfAny == (-1)) {
Optional<? extends Tree> anyClass =
info.getCompilationUnit().getTypeDecls().stream().filter(t -> t.getKind() ==
Kind.CLASS).findAny();
if (!anyClass.isPresent()) {
return Collections.emptyList();
}
- ClassTree clazz = (ClassTree) anyClass.get();
+ clazz = (ClassTree) anyClass.get();
TreePath pathToClass = new TreePath(new
TreePath(info.getCompilationUnit()), clazz);
methods = clazz.getMembers().stream().filter(m -> m.getKind() ==
Kind.METHOD).map(m -> new TreePath(pathToClass,
m)).collect(Collectors.toList());
} else {
@@ -128,11 +130,13 @@ public final class TestClassInfoTask implements
CancellableTask<CompilationContr
tp = tp.getParentPath();
}
if (tp != null) {
+ clazz = (ClassTree) tp.getParentPath().getLeaf();
methods = Collections.singletonList(tp);
} else {
return Collections.emptyList();
}
}
+ TypeElement typeElement = (TypeElement) info.getTrees().getElement(new
TreePath(new TreePath(info.getCompilationUnit()), clazz));
Elements elements = info.getElements();
List<TestMethod> result = new ArrayList<>();
for (TreePath tp : methods) {
@@ -144,22 +148,24 @@ public final class TestClassInfoTask implements
CancellableTask<CompilationContr
List<? extends AnnotationMirror> allAnnotationMirrors =
elements.getAllAnnotationMirrors(element);
for (Iterator<? extends AnnotationMirror> it =
allAnnotationMirrors.iterator(); it.hasNext();) {
AnnotationMirror annotationMirror = it.next();
- TypeElement typeElement = (TypeElement)
annotationMirror.getAnnotationType().asElement();
- if
(typeElement.getQualifiedName().contentEquals(ANNOTATION)) {
+ TypeElement annTypeElement = (TypeElement)
annotationMirror.getAnnotationType().asElement();
+ if
(annTypeElement.getQualifiedName().contentEquals(ANNOTATION)) {
String mn = element.getSimpleName().toString();
SourcePositions sp =
info.getTrees().getSourcePositions();
int start = (int)
sp.getStartPosition(tp.getCompilationUnit(), tp.getLeaf());
int end = (int)
sp.getEndPosition(tp.getCompilationUnit(), tp.getLeaf());
Document doc =
info.getSnapshot().getSource().getDocument(false);
try {
- result.add(new TestMethod(new
SingleMethod(fileObject, mn), doc != null ? doc.createPosition(start) : null,
doc != null ? doc.createPosition(end) : null));
+ result.add(new
TestMethod(typeElement.getQualifiedName().toString(), new
SingleMethod(fileObject, mn),
+ doc != null ? doc.createPosition(start) :
new SimplePosition(start),
+ doc != null ? doc.createPosition(end) :
new SimplePosition(end)));
} catch (BadLocationException ex) {
//ignore
- }
}
}
}
}
+ }
return result;
}
@@ -187,4 +193,18 @@ public final class TestClassInfoTask implements
CancellableTask<CompilationContr
}
}
+
+ private static class SimplePosition implements Position {
+
+ private final int offset;
+
+ private SimplePosition(int offset) {
+ this.offset = offset;
+ }
+
+ @Override
+ public int getOffset() {
+ return offset;
+ }
+ }
}
diff --git
a/nbbuild/misc/prepare-bundles/src/main/resources/org/netbeans/prepare/bundles/vscode-test-adapter-util-0.7.1-license
b/nbbuild/misc/prepare-bundles/src/main/resources/org/netbeans/prepare/bundles/vscode-test-adapter-util-0.7.1-license
new file mode 100644
index 0000000..a572e90
--- /dev/null
+++
b/nbbuild/misc/prepare-bundles/src/main/resources/org/netbeans/prepare/bundles/vscode-test-adapter-util-0.7.1-license
@@ -0,0 +1,22 @@
+The MIT License (MIT)
+
+Copyright (c) 2018 Holger Benl <[email protected]>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists