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 <dusan.ba...@oracle.com> 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 <hb...@evandor.de> + +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: commits-unsubscr...@netbeans.apache.org For additional commands, e-mail: commits-h...@netbeans.apache.org For further information about the NetBeans mailing lists, visit: https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists