You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@netbeans.apache.org by db...@apache.org on 2021/09/27 08:33:38 UTC

[netbeans] branch master updated: LSP: Test results displayed using the new TestExplorer API. (#3186)

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 253b52d  LSP: Test results displayed using the new TestExplorer API. (#3186)
253b52d is described below

commit 253b52dd2cebac79110b153fe0e90db72328649b
Author: Dusan Balek <du...@oracle.com>
AuthorDate: Mon Sep 27 10:33:02 2021 +0200

    LSP: Test results displayed using the new TestExplorer API. (#3186)
---
 .../gsf/testrunner/ui/TestMethodFinderImpl.java    |   4 +-
 .../gsf/testrunner/ui/api/TestMethodFinder.java    |   6 +-
 .../server/debugging/launch/NbLaunchDelegate.java  |   2 +-
 .../lsp/server/progress/TestProgressHandler.java   |   6 +-
 .../java/lsp/server/protocol/TestSuiteInfo.java    | 152 ++++-----
 .../lsp/server/protocol/WorkspaceServiceImpl.java  |  18 +-
 .../server/progress/TestProgressHandlerTest.java   |  13 +-
 java/java.lsp.server/vscode/package-lock.json      |  27 +-
 java/java.lsp.server/vscode/package.json           |  18 +-
 java/java.lsp.server/vscode/src/extension.ts       |  36 +-
 java/java.lsp.server/vscode/src/protocol.ts        |  54 ++-
 java/java.lsp.server/vscode/src/test/runTest.ts    |  17 +-
 java/java.lsp.server/vscode/src/testAdapter.ts     | 373 +++++++++------------
 13 files changed, 308 insertions(+), 418 deletions(-)

diff --git a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodFinderImpl.java b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodFinderImpl.java
index 6588969..1198c8d 100644
--- a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodFinderImpl.java
+++ b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/TestMethodFinderImpl.java
@@ -107,7 +107,9 @@ public final class TestMethodFinderImpl extends EmbeddingIndexer {
                     pw.print("method: "); //NOI18N
                     pw.print(method.method().getMethodName());
                     pw.print(':'); //NOI18N
-                    pw.println(method.start().getOffset());
+                    pw.print(method.start().getOffset());
+                    pw.print('-'); //NOI18N
+                    pw.println(method.end().getOffset());
                 }
             } catch (IOException ex) {
                 Exceptions.printStackTrace(ex);
diff --git a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestMethodFinder.java b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestMethodFinder.java
index b1dc558..22ae987 100644
--- a/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestMethodFinder.java
+++ b/ide/gsf.testrunner.ui/src/org/netbeans/modules/gsf/testrunner/ui/api/TestMethodFinder.java
@@ -102,8 +102,10 @@ public final class TestMethodFinder {
                     String info = line.substring(8);
                     int idx = info.lastIndexOf(':');
                     String name = (idx < 0 ? info : info.substring(0, idx)).trim();
-                    Position methodPosition = idx < 0 ? null : () -> Integer.parseInt(info.substring(idx + 1));
-                    testMethods.add(new TestMethodController.TestMethod(className, classPosition, new SingleMethod(fo, name), methodPosition, null, null));
+                    String[] range = idx < 0 ? new String[0] : info.substring(idx + 1).split("-");
+                    Position methodStart = range.length > 0 ? () -> Integer.parseInt(range[0]) : null;
+                    Position methodEnd = range.length > 1 ? () -> Integer.parseInt(range[1]) : null;
+                    testMethods.add(new TestMethodController.TestMethod(className, classPosition, new SingleMethod(fo, name), methodStart, null, methodEnd));
                 }
             }
         } catch (IOException ex) {
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 f05b675..ccfcece 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
@@ -508,7 +508,7 @@ public abstract class NbLaunchDelegate {
 
         if (provider == null) {
             command = debug ? mainSource ? ActionProvider.COMMAND_DEBUG
-                                         : ActionProvider.COMMAND_DEBUG // DEBUG_TEST is missing?
+                                         : ActionProvider.COMMAND_TEST //TODO: COMMAND_DEBUG_TEST is missing?
                             : mainSource ? ActionProvider.COMMAND_RUN
                                          : ActionProvider.COMMAND_TEST;
             provider = findActionProvider(command, actionProviders, testLookup);
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
index 1a2b551..be7b83d 100644
--- 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
@@ -70,12 +70,12 @@ public final class TestProgressHandler implements TestResultDisplayHandler.Spi<T
 
     @Override
     public void displaySuiteRunning(TestProgressHandler token, String suiteName) {
-        lspClient.notifyTestProgress(new TestProgressParams(uri, new TestSuiteInfo(suiteName, TestSuiteInfo.State.Running)));
+        lspClient.notifyTestProgress(new TestProgressParams(uri, new TestSuiteInfo(suiteName, TestSuiteInfo.State.Started)));
     }
 
     @Override
     public void displaySuiteRunning(TestProgressHandler token, TestSuite suite) {
-        lspClient.notifyTestProgress(new TestProgressParams(uri, new TestSuiteInfo(suite.getName(), TestSuiteInfo.State.Running)));
+        lspClient.notifyTestProgress(new TestProgressParams(uri, new TestSuiteInfo(suite.getName(), TestSuiteInfo.State.Started)));
     }
 
     @Override
@@ -121,7 +121,7 @@ public final class TestProgressHandler implements TestResultDisplayHandler.Spi<T
             if (info != null) {
                 updateState(info, status);
             } else {
-                info = new TestSuiteInfo.TestCaseInfo(id, name, className + '.' + name, fo != null ? Utils.toUri(fo) : null, null, status, stackTrace);
+                info = new TestSuiteInfo.TestCaseInfo(id, name, fo != null ? Utils.toUri(fo) : null, null, status, stackTrace);
                 testCases.put(id, info);
             }
         }
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
index d92337d..36ea81b 100644
--- 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
@@ -20,6 +20,7 @@ package org.netbeans.modules.java.lsp.server.protocol;
 
 import java.util.List;
 import java.util.Objects;
+import org.eclipse.lsp4j.Range;
 import org.eclipse.lsp4j.jsonrpc.validation.NonNull;
 import org.eclipse.lsp4j.util.Preconditions;
 import org.eclipse.xtext.xbase.lib.Pure;
@@ -36,7 +37,7 @@ public final class TestSuiteInfo {
      * The test suite name to be displayed by the Test Explorer.
      */
     @NonNull
-    private String suiteName;
+    private String name;
 
     /**
      * The file containing this suite (if known).
@@ -44,13 +45,13 @@ public final class TestSuiteInfo {
     private String file;
 
     /**
-     * The line within the specified file where the suite definition starts (if known).
+     * The range within the specified file where the suite definition is located (if known).
      */
-    private Integer line;
+    private Range range;
 
     /**
      * The state of the tests suite. Can be one of the following values:
-     * "loaded" | "running" | "completed" | "errored"
+     * "loaded" | "started" | "completed" | "errored"
      */
     @NonNull
     private String state;
@@ -64,15 +65,15 @@ public final class TestSuiteInfo {
         this("", "");
     }
 
-    public TestSuiteInfo(@NonNull final String suiteName, @NonNull final String state) {
-        this.suiteName = Preconditions.checkNotNull(suiteName, "suiteName");
+    public TestSuiteInfo(@NonNull final String name, @NonNull final String state) {
+        this.name = Preconditions.checkNotNull(name, "name");
         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);
+    public TestSuiteInfo(@NonNull final String name, final String file, final Range range, @NonNull final String state, final List<TestCaseInfo> tests) {
+        this(name, state);
         this.file = file;
-        this.line = line;
+        this.range = range;
         this.tests = tests;
     }
 
@@ -81,15 +82,15 @@ public final class TestSuiteInfo {
      */
     @Pure
     @NonNull
-    public String getSuiteName() {
-        return suiteName;
+    public String getName() {
+        return name;
     }
 
     /**
      * The test suite name to be displayed by the Test Explorer.
      */
-    public void setSuiteName(@NonNull final String suiteName) {
-        this.suiteName = Preconditions.checkNotNull(suiteName, "suiteName");
+    public void setSuiteName(@NonNull final String name) {
+        this.name = Preconditions.checkNotNull(name, "name");
     }
 
     /**
@@ -108,23 +109,23 @@ public final class TestSuiteInfo {
     }
 
     /**
-     * The line within the specified file where the suite definition starts (if known).
+     * The range within the specified file where the suite definition is located (if known).
      */
     @Pure
-    public Integer getLine() {
-        return line;
+    public Range getRange() {
+        return range;
     }
 
     /**
-     * The line within the specified file where the suite definition starts (if known).
+     * The range within the specified file where the suite definition is located (if known).
      */
-    public void setLine(final Integer line) {
-        this.line = line;
+    public void setRange(final Range range) {
+        this.range = range;
     }
 
     /**
      * The state of the tests suite. Can be one of the following values:
-     * "loaded" | "running" | "completed" | "errored"
+     * "loaded" | "started" | "completed" | "errored"
      */
     @Pure
     @NonNull
@@ -134,7 +135,7 @@ public final class TestSuiteInfo {
 
     /**
      * The state of the tests suite. Can be one of the following values:
-     * "loaded" | "running" | "completed" | "errored"
+     * "loaded" | "started" | "completed" | "errored"
      */
     public void setState(@NonNull final String state) {
         this.state = Preconditions.checkNotNull(state, "state");
@@ -159,9 +160,9 @@ public final class TestSuiteInfo {
     @Pure
     public String toString() {
         ToStringBuilder b = new ToStringBuilder(this);
-        b.add("suiteName", suiteName);
+        b.add("name", name);
         b.add("file", file);
-        b.add("line", line);
+        b.add("range", range);
         b.add("state", state);
         b.add("tests", tests);
         return b.toString();
@@ -171,9 +172,9 @@ public final class TestSuiteInfo {
     @Pure
     public int hashCode() {
         int hash = 7;
-        hash = 67 * hash + Objects.hashCode(this.suiteName);
+        hash = 67 * hash + Objects.hashCode(this.name);
         hash = 67 * hash + Objects.hashCode(this.file);
-        hash = 67 * hash + Objects.hashCode(this.line);
+        hash = 67 * hash + Objects.hashCode(this.range);
         hash = 67 * hash + Objects.hashCode(this.state);
         hash = 67 * hash + Objects.hashCode(this.tests);
         return hash;
@@ -192,13 +193,13 @@ public final class TestSuiteInfo {
             return false;
         }
         final TestSuiteInfo other = (TestSuiteInfo) obj;
-        if (!Objects.equals(this.suiteName, other.suiteName)) {
+        if (!Objects.equals(this.name, other.name)) {
             return false;
         }
         if (!Objects.equals(this.file, other.file)) {
             return false;
         }
-        if (!Objects.equals(this.line, other.line)) {
+        if (!Objects.equals(this.range, other.range)) {
             return false;
         }
         if (!Objects.equals(this.state, other.state)) {
@@ -213,7 +214,7 @@ public final class TestSuiteInfo {
     /**
      * Information about a test case.
      */
-    public static class TestCaseInfo {
+    public static final class TestCaseInfo {
 
         /**
          * The test case ID.
@@ -222,17 +223,10 @@ public final class TestSuiteInfo {
         private String id;
 
         /**
-         * The short name to be displayed by the Test Explorer for this test case.
+         * The 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;
+        private String name;
 
         /**
          * The file containing this test case (if known).
@@ -240,13 +234,13 @@ public final class TestSuiteInfo {
         private String file;
 
         /**
-         * The line within the specified file where the test case definition starts (if known).
+         * The range within the specified file where the test case definition is located (if known).
          */
-        private Integer line;
+        private Range range;
 
         /**
          * The state of the test case. Can be one of the following values:
-         * "loaded" | "running" | "passed" | "failed" | "skipped" | "errored"
+         * "loaded" | "started" | "passed" | "failed" | "skipped" | "errored"
          */
         @NonNull
         private String state;
@@ -257,20 +251,19 @@ public final class TestSuiteInfo {
         private List<String> stackTrace;
 
         public TestCaseInfo() {
-            this("", "", "", "");
+            this("", "", "");
         }
 
-        public TestCaseInfo(@NonNull final String id, @NonNull final String shortName, @NonNull final String fullName, @NonNull final String state) {
+        public TestCaseInfo(@NonNull final String id, @NonNull final String name, @NonNull final String state) {
             this.id = Preconditions.checkNotNull(id, "id");
-            this.shortName = Preconditions.checkNotNull(shortName, "shortName");
-            this.fullName = Preconditions.checkNotNull(fullName, "fullName");
+            this.name = Preconditions.checkNotNull(name, "name");
             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);
+        public TestCaseInfo(@NonNull final String id, @NonNull final String name, final String file, final Range range, @NonNull final String state, final List<String> stackTrace) {
+            this(id, name, state);
             this.file = file;
-            this.line = line;
+            this.range = range;
             this.stackTrace = stackTrace;
         }
 
@@ -291,37 +284,19 @@ public final class TestSuiteInfo {
         }
 
         /**
-         * 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.
+         * The name to be displayed by the Test Explorer for this test case.
          */
         @Pure
         @NonNull
-        public String getFullName() {
-            return fullName;
+        public String getName() {
+            return name;
         }
 
         /**
-         * The full name to be displayed by the Test Explorer when you hover over
-         * this test case.
+         * The name to be displayed by the Test Explorer for this test case.
          */
-        public void setFullName(@NonNull final String fullName) {
-            this.fullName = Preconditions.checkNotNull(fullName, "fullName");
+        public void setName(@NonNull final String name) {
+            this.name = Preconditions.checkNotNull(name, "name");
         }
 
         /**
@@ -340,23 +315,23 @@ public final class TestSuiteInfo {
         }
 
         /**
-         * The line within the specified file where the test case definition starts (if known).
+         * The range within the specified file where the test case definition is located (if known).
          */
         @Pure
-        public Integer getLine() {
-            return line;
+        public Range getRange() {
+            return range;
         }
 
         /**
-         * The line within the specified file where the test case definition starts (if known).
+         * The range within the specified file where the test case definition is located (if known).
          */
-        public void setLine(final Integer line) {
-            this.line = line;
+        public void setRange(final Range range) {
+            this.range = range;
         }
 
         /**
          * The state of the test case. Can be one of the following values:
-         * "loaded" | "running" | "passed" | "failed" | "skipped" | "errored"
+         * "loaded" | "started" | "passed" | "failed" | "skipped" | "errored"
          */
         @Pure
         @NonNull
@@ -366,7 +341,7 @@ public final class TestSuiteInfo {
 
         /**
          * The state of the test case. Can be one of the following values:
-         * "loaded" | "running" | "passed" | "failed" | "skipped" | "errored"
+         * "loaded" | "started" | "passed" | "failed" | "skipped" | "errored"
          */
         public void setState(@NonNull final String state) {
             this.state = Preconditions.checkNotNull(state, "state");
@@ -392,10 +367,9 @@ public final class TestSuiteInfo {
         public String toString() {
             ToStringBuilder b = new ToStringBuilder(this);
             b.add("id", id);
-            b.add("shortName", shortName);
-            b.add("fullName", fullName);
+            b.add("name", name);
             b.add("file", file);
-            b.add("line", line);
+            b.add("range", range);
             b.add("state", state);
             b.add("stackTrace", stackTrace);
             return b.toString();
@@ -406,10 +380,9 @@ public final class TestSuiteInfo {
         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.name);
             hash = 97 * hash + Objects.hashCode(this.file);
-            hash = 97 * hash + Objects.hashCode(this.line);
+            hash = 97 * hash + Objects.hashCode(this.range);
             hash = 97 * hash + Objects.hashCode(this.state);
             hash = 97 * hash + Objects.hashCode(this.stackTrace);
             return hash;
@@ -431,16 +404,13 @@ public final class TestSuiteInfo {
             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)) {
+            if (!Objects.equals(this.name, other.name)) {
                 return false;
             }
             if (!Objects.equals(this.file, other.file)) {
                 return false;
             }
-            if (!Objects.equals(this.line, other.line)) {
+            if (!Objects.equals(this.range, other.range)) {
                 return false;
             }
             if (!Objects.equals(this.state, other.state)) {
@@ -462,7 +432,7 @@ public final class TestSuiteInfo {
 
         public static final String Loaded = "loaded";
 
-        public static final String Running = "running";
+        public static final String Started = "started";
 
         public static final String Completed  = "completed";
 
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 0d65a1f..4ab6a64 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
@@ -205,22 +205,22 @@ public final class WorkspaceServiceImpl implements WorkspaceService, LanguageCli
                                 List<TestSuiteInfo.TestCaseInfo> tests = new ArrayList<>(methods.size());
                                 String url = Utils.toUri(fo);
                                 String testClassName = null;
-                                Integer testClassLine = null;
+                                Range testClassRange = null;
                                 for (TestMethodController.TestMethod testMethod : methods) {
                                     if (testClassName == null) {
                                         testClassName = testMethod.getTestClassName();
                                     }
-                                    if (testClassLine == null) {
-                                        testClassLine = testMethod.getTestClassPosition() != null
-                                                ? Utils.createPosition(fo, testMethod.getTestClassPosition().getOffset()).getLine()
-                                                : null;
+                                    if (testClassRange == null && testMethod.getTestClassPosition() != null) {
+                                        Position pos = Utils.createPosition(fo, testMethod.getTestClassPosition().getOffset());
+                                        testClassRange = new Range(pos, pos);
                                     }
                                     String id = testMethod.getTestClassName() + ':' + testMethod.method().getMethodName();
-                                    String fullName = testMethod.getTestClassName() + '.' + testMethod.method().getMethodName();
-                                    int testLine = Utils.createPosition(fo, testMethod.start().getOffset()).getLine();
-                                    tests.add(new TestSuiteInfo.TestCaseInfo(id, testMethod.method().getMethodName(), fullName, url, testLine, TestSuiteInfo.State.Loaded, null));
+                                    Position startPos = testMethod.start() != null ? Utils.createPosition(fo, testMethod.start().getOffset()) : null;
+                                    Position endPos = testMethod.end() != null ? Utils.createPosition(fo, testMethod.end().getOffset()) : startPos;
+                                    Range range = startPos != null ? new Range(startPos, endPos) : null;
+                                    tests.add(new TestSuiteInfo.TestCaseInfo(id, testMethod.method().getMethodName(), url, range, TestSuiteInfo.State.Loaded, null));
                                 }
-                                return new TestSuiteInfo(testClassName, url, testClassLine, TestSuiteInfo.State.Loaded, tests);
+                                return new TestSuiteInfo(testClassName, url, testClassRange, TestSuiteInfo.State.Loaded, tests);
                             };
                             testMethodsListener.compareAndSet(null, (fo, methods) -> {
                                 try {
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
index 1265b97..c325b59 100644
--- 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
@@ -27,7 +27,6 @@ import org.eclipse.lsp4j.MessageActionItem;
 import org.eclipse.lsp4j.MessageParams;
 import org.eclipse.lsp4j.PublishDiagnosticsParams;
 import org.eclipse.lsp4j.ShowMessageRequestParams;
-import org.eclipse.lsp4j.debug.OutputEventArguments;
 import org.eclipse.lsp4j.debug.services.IDebugProtocolClient;
 import org.junit.Test;
 import org.netbeans.api.extexecution.print.LineConvertors;
@@ -117,24 +116,22 @@ public class TestProgressHandlerTest extends NbTestCase {
         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("TestSuiteName", suite.getName());
+        assertEquals(TestSuiteInfo.State.Started, suite.getState());
         assertEquals(fo.toURI().toString(), msgs.get(1).getUri());
         suite = msgs.get(1).getSuite();
-        assertEquals("TestSuiteName", suite.getSuiteName());
+        assertEquals("TestSuiteName", suite.getName());
         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("test1", testCase.getName());
         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("test2", testCase.getName());
         assertEquals(fo.toURI().toString(), testCase.getFile());
         assertEquals(TestSuiteInfo.State.Failed, testCase.getState());
         assertNotNull(testCase.getStackTrace());
diff --git a/java/java.lsp.server/vscode/package-lock.json b/java/java.lsp.server/vscode/package-lock.json
index 33e724a..2a231d4 100644
--- a/java/java.lsp.server/vscode/package-lock.json
+++ b/java/java.lsp.server/vscode/package-lock.json
@@ -1,6 +1,6 @@
 {
 	"name": "apache-netbeans-java",
-	"version": "12.4.0",
+	"version": "0.1.0",
 	"lockfileVersion": 1,
 	"requires": true,
 	"dependencies": {
@@ -39,9 +39,9 @@
 			"dev": true
 		},
 		"@types/vscode": {
-			"version": "1.49.0",
-			"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.49.0.tgz",
-			"integrity": "sha512-wfNQmLmm1VdMBr6iuNdprWmC1YdrgZ9dQzadv+l2eSjJlElOdJw8OTm4RU4oGTBcfvG6RZI2jOcppkdSS18mZw==",
+			"version": "1.60.0",
+			"resolved": "https://registry.npmjs.org/@types/vscode/-/vscode-1.60.0.tgz",
+			"integrity": "sha512-wZt3VTmzYrgZ0l/3QmEbCq4KAJ71K3/hmMQ/nfpv84oH8e81KKwPEoQ5v8dNCxfHFVJ1JabHKmCvqdYOoVm1Ow==",
 			"dev": true
 		},
 		"agent-base": {
@@ -917,11 +917,6 @@
 				"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",
@@ -981,20 +976,6 @@
 				"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 4186922..92bb6f0 100644
--- a/java/java.lsp.server/vscode/package.json
+++ b/java/java.lsp.server/vscode/package.json
@@ -4,7 +4,7 @@
 	"description": "Apache NetBeans Language Server Extension for Visual Studio Code",
 	"author": "Apache NetBeans",
 	"license": "Apache 2.0",
-	"version": "12.4.0",
+	"version": "0.1.0",
 	"preview": true,
 	"repository": {
 		"type": "git",
@@ -15,13 +15,14 @@
 	"categories": [
 		"Programming Languages",
 		"Debuggers",
+		"Testing",
 		"Other"
 	],
 	"keywords": [
 		"multi-root ready"
 	],
 	"engines": {
-		"vscode": "^1.49.0"
+		"vscode": "^1.60.0"
 	},
 	"activationEvents": [
 		"onLanguage:java",
@@ -389,13 +390,13 @@
 		"nbcode": "node ./out/nbcode.js",
 		"nbjavac": "node ./out/nbcode.js -J-Dnetbeans.close=true --modules --install .*nbjavac.*",
 		"apisupport": "node ./out/nbcode.js -J-Dnetbeans.close=true --modules --install '(org.netbeans.libs.xerces|org.netbeans.modules.editor.structure|org.netbeans.modules.xml|org.netbeans.modules.xml.axi|org.netbeans.modules.xml.retriever|org.netbeans.modules.xml.schema.model|org.netbeans.modules.xml.tax|org.netbeans.modules.xml.text|org.netbeans.modules.ant.browsetask|.*apisupport.*|org.netbeans.modules.debugger.jpda.ant)' && node ./out/nbcode.js -J-Dnetbeans.close=true --modules --enable  [...]
-        },
+	},
 	"devDependencies": {
 		"@types/glob": "^7.1.1",
 		"@types/mocha": "^7.0.2",
 		"@types/node": "^13.11.0",
 		"@types/ps-node": "^0.1.0",
-		"@types/vscode": "^1.49.0",
+		"@types/vscode": "^1.60.0",
 		"glob": "^7.1.6",
 		"mocha": "^7.1.2",
 		"ps-node": "^0.1.6",
@@ -405,11 +406,6 @@
 	"dependencies": {
 		"jsonc-parser": "3.0.0",
 		"vscode-debugadapter": "1.42.1",
-		"vscode-languageclient": "6.1.3",
-		"vscode-test-adapter-api": "^1.9.0",
-		"vscode-test-adapter-util": "^0.7.1"
-	},
-	"extensionDependencies": [
-		"hbenl.vscode-test-explorer"
-	]
+		"vscode-languageclient": "6.1.3"
+	}
 }
diff --git a/java/java.lsp.server/vscode/src/extension.ts b/java/java.lsp.server/vscode/src/extension.ts
index 2b6723c..55dc8eb 100644
--- a/java/java.lsp.server/vscode/src/extension.ts
+++ b/java/java.lsp.server/vscode/src/extension.ts
@@ -30,8 +30,7 @@ import {
     MessageType,
     LogMessageNotification,
     RevealOutputChannelOn,
-    DocumentSelector,
-    DocumentFilter
+    DocumentSelector
 } from 'vscode-languageclient';
 
 import * as net from 'net';
@@ -39,18 +38,16 @@ 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 {NbTestAdapter} from './testAdapter';
-import { StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest, InputBoxRequest, TestProgressNotification, DebugConnector,
+import { asRanges, StatusMessageRequest, ShowStatusMessageParams, QuickPickRequest, InputBoxRequest, TestProgressNotification, DebugConnector,
          TextEditorDecorationCreateRequest, TextEditorDecorationSetNotification, TextEditorDecorationDisposeNotification,
 } from './protocol';
 import * as launchConfigurations from './launchConfigurations';
 
 const API_VERSION : string = "1.0";
 let client: Promise<LanguageClient>;
-let testAdapterRegistrar: TestAdapterRegistrar<NbTestAdapter>;
+let testAdapter: NbTestAdapter | null = null;
 let nbProcess : ChildProcess | null = null;
 let debugPort: number = -1;
 let consoleLog: boolean = !!process.env['ENABLE_CONSOLE_LOG'];
@@ -333,6 +330,9 @@ export function activate(context: ExtensionContext): VSNetBeansAPI {
     context.subscriptions.push(commands.registerCommand('java.run.test', async (uri, methodName?, launchConfiguration?) => {
         await runDebug(true, true, uri, methodName, launchConfiguration);
     }));
+    context.subscriptions.push(commands.registerCommand('java.debug.test', async (uri, methodName?, launchConfiguration?) => {
+        await runDebug(false, true, uri, methodName, launchConfiguration);
+    }));
     context.subscriptions.push(commands.registerCommand('java.run.single', async (uri, methodName?, launchConfiguration?) => {
         await runDebug(true, false, uri, methodName, launchConfiguration);
     }));
@@ -343,16 +343,8 @@ export function activate(context: ExtensionContext): VSNetBeansAPI {
     // register completions:
     launchConfigurations.registerCompletion(context);
 
-    // 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);
-    }
+    // register TestAdapter
+    context.subscriptions.push(testAdapter = new NbTestAdapter(client));
 
     return Object.freeze({
         version : API_VERSION
@@ -606,14 +598,8 @@ function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContex
                 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);
-                        }
-                    }
+                if (testAdapter) {
+                    testAdapter.testProgress(param.suite);
                 }
             });
             let decorations = new Map<string, TextEditorDecorationType>();
@@ -629,7 +615,7 @@ function doActivateWithJDK(specifiedJDK: string | null, context: ExtensionContex
                         editor => editor.document.uri.toString() == param.uri
                     );
                     if (editorsWithUri.length > 0) {
-                        editorsWithUri[0].setDecorations(decorationType, param.ranges);
+                        editorsWithUri[0].setDecorations(decorationType, asRanges(param.ranges));
                     }
                 }
             });
diff --git a/java/java.lsp.server/vscode/src/protocol.ts b/java/java.lsp.server/vscode/src/protocol.ts
index 3c3a254..c4bcaac 100644
--- a/java/java.lsp.server/vscode/src/protocol.ts
+++ b/java/java.lsp.server/vscode/src/protocol.ts
@@ -18,17 +18,16 @@
  */
 'use strict';
 
-import {
-    DecorationRenderOptions,
-    QuickPickItem,
-    Range,
-    TextEditorDecorationType,
-} from 'vscode';
+import * as vscode from 'vscode';
 import {
     NotificationType,
     RequestType,
     ShowMessageParams
 } from 'vscode-languageclient';
+import {
+    Position,
+    Range
+} from 'vscode-languageserver-protocol';
 
 export interface ShowStatusMessageParams extends ShowMessageParams {
     /**
@@ -53,11 +52,11 @@ export interface ShowQuickPickParams {
     /**
      * A list of items.
      */
-    items: QuickPickItem[];
+    items: vscode.QuickPickItem[];
 }
 
 export namespace QuickPickRequest {
-    export const type = new RequestType<ShowQuickPickParams, QuickPickItem[], void, void>('window/showQuickPick');
+    export const type = new RequestType<ShowQuickPickParams, vscode.QuickPickItem[], void, void>('window/showQuickPick');
 }
 
 export interface ShowInputBoxParams {
@@ -81,20 +80,19 @@ export interface TestProgressParams {
 }
 
 export interface TestSuite {
-    suiteName: string;
+    name: string;
     file?: string;
-    line?: number;
-    state: 'loaded' | 'running' | 'completed' | 'errored';
+    range?: Range;
+    state: 'loaded' | 'started' | 'completed' | 'errored';
     tests?: TestCase[];
 }
 
 export interface TestCase {
     id: string;
-    shortName: string;
-    fullName: string;
+    name: string;
     file?: string;
-    line?: number;
-    state: 'loaded' | 'running' | 'passed' | 'failed' | 'skipped' | 'errored';
+    range?: Range;
+    state: 'loaded' | 'started' | 'passed' | 'failed' | 'skipped' | 'errored';
     stackTrace?: string[];
 }
 
@@ -118,7 +116,7 @@ export interface SetTextEditorDecorationParams {
 };
 
 export namespace TextEditorDecorationCreateRequest {
-    export const type = new RequestType<DecorationRenderOptions, string, void, void>('window/createTextEditorDecoration');
+    export const type = new RequestType<vscode.DecorationRenderOptions, string, void, void>('window/createTextEditorDecoration');
 };
 
 export namespace TextEditorDecorationSetNotification {
@@ -128,3 +126,27 @@ export namespace TextEditorDecorationSetNotification {
 export namespace TextEditorDecorationDisposeNotification {
     export const type = new NotificationType<string, void>('window/disposeTextEditorDecoration');
 };
+
+export function asPosition(value: undefined | null): undefined;
+export function asPosition(value: Position): vscode.Position;
+export function asPosition(value: Position | undefined | null): vscode.Position | undefined;
+export function asPosition(value: Position | undefined | null): vscode.Position | undefined {
+    if (!value) {
+        return undefined;
+    }
+    return new vscode.Position(value.line, value.character);
+}
+
+export function asRange(value: undefined | null): undefined;
+export function asRange(value: Range): vscode.Range;
+export function asRange(value: Range | undefined | null): vscode.Range | undefined;
+export function asRange(value: Range | undefined | null): vscode.Range | undefined {
+    if (!value) {
+        return undefined;
+    }
+    return new vscode.Range(asPosition(value.start), asPosition(value.end));
+}
+
+export function asRanges(value: Range[]): vscode.Range[] {
+    return value.map(value => asRange(value));
+}
diff --git a/java/java.lsp.server/vscode/src/test/runTest.ts b/java/java.lsp.server/vscode/src/test/runTest.ts
index d8d174c..9815e9e 100644
--- a/java/java.lsp.server/vscode/src/test/runTest.ts
+++ b/java/java.lsp.server/vscode/src/test/runTest.ts
@@ -1,8 +1,7 @@
 import * as path from 'path';
 
-import { downloadAndUnzipVSCode, resolveCliPathFromVSCodeExecutablePath, runTests } from 'vscode-test';
+import { downloadAndUnzipVSCode, runTests } from 'vscode-test';
 
-import * as cp from 'child_process';
 import * as fs from 'fs';
 
 async function main() {
@@ -11,13 +10,7 @@ async function main() {
         // Passed to `--extensionDevelopmentPath`
         const extensionDevelopmentPath = path.resolve(__dirname, '../../');
 
-        const vscodeExecutablePath: string = await downloadAndUnzipVSCode('1.56.2');
-        const cliPath: string = resolveCliPathFromVSCodeExecutablePath(vscodeExecutablePath);
-
-        cp.spawnSync(cliPath, ['--install-extension', 'hbenl.vscode-test-explorer'], {
-            encoding: 'utf-8',
-            stdio: 'inherit',
-        });
+        const vscodeExecutablePath: string = await downloadAndUnzipVSCode('stable');
 
         // The path to test runner
         // Passed to --extensionTestsPath
@@ -37,7 +30,11 @@ async function main() {
             extensionTestsEnv: {
                 'ENABLE_CONSOLE_LOG' : 'true'
             },
-            launchArgs: [workspaceDir, '--async-stack-traces']
+            launchArgs: [
+                workspaceDir,
+                '--disable-extensions',
+                '--disable-workspace-trust'
+            ]
         });
     } catch (err) {
         console.error('Failed to run tests');
diff --git a/java/java.lsp.server/vscode/src/testAdapter.ts b/java/java.lsp.server/vscode/src/testAdapter.ts
index 95085e5..e28e5f7 100644
--- a/java/java.lsp.server/vscode/src/testAdapter.ts
+++ b/java/java.lsp.server/vscode/src/testAdapter.ts
@@ -18,125 +18,78 @@
  */
 'use strict';
 
-import { WorkspaceFolder, Event, EventEmitter, Uri, commands, debug } from "vscode";
+import { commands, debug, tests, workspace, CancellationToken, TestController, TestItem, TestRunProfileKind, TestRunRequest, Uri, TestRun, TestMessage, Location, Position } from "vscode";
 import * as path from 'path';
-import { TestAdapter, TestSuiteEvent, TestEvent, TestLoadFinishedEvent, TestLoadStartedEvent, TestRunFinishedEvent, TestRunStartedEvent, TestSuiteInfo, TestInfo, TestDecoration } from "vscode-test-adapter-api";
-import { TestSuite } from "./protocol";
+import { asRange, TestSuite } from "./protocol";
 import { LanguageClient } from "vscode-languageclient";
 
-export class NbTestAdapter implements TestAdapter {
+export class NbTestAdapter {
 
+    private readonly testController: TestController;
 	private disposables: { dispose(): void }[] = [];
-    private children: TestSuiteInfo[] = [];
-    private readonly testSuite: TestSuiteInfo;
+    private currentRun: TestRun | undefined;
 
-	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;
+    constructor(client: Promise<LanguageClient>) {
+        this.testController = tests.createTestController('apacheNetBeansController', 'Apache NetBeans');
+        const runHandler = (request: TestRunRequest, cancellation: CancellationToken) => this.run(request, cancellation);
+        this.testController.createRunProfile('Run Tests', TestRunProfileKind.Run, runHandler);
+        this.testController.createRunProfile('Debug Tests', TestRunProfileKind.Debug, runHandler);
+        this.disposables.push(this.testController);
+        client.then(async () => await this.load());
     }
 
     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) => {
-                this.updateTests(suite);
-            });
-            this.children.sort((a, b) => a.label.localeCompare(b.label));
-        }
-        if (this.children.length > 0) {
-            this.testsEmitter.fire(<TestLoadFinishedEvent>{ type: 'finished', suite: this.testSuite });
-        } else {
-            this.testsEmitter.fire(<TestLoadFinishedEvent>{ type: 'finished' });
+        for (let workspaceFolder of workspace.workspaceFolders || []) {
+            const loadedTests: any = await commands.executeCommand('java.load.workspace.tests', workspaceFolder.uri.toString());
+            if (loadedTests) {
+                loadedTests.forEach((suite: TestSuite) => {
+                    this.updateTests(suite);
+                });
+            }
         }
     }
 
-    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) {
-                    let methodName;
-                    if (idx >= 0) {
-                        let test = current.children.find(t => t.id === tests[0]);
-                        if (test) {
-                            methodName = tests[0].slice(idx + 1);
-                        } else {
-                            let parents = current.children.filter(ti => tests[0].startsWith(ti.id));
-                            if (parents && parents.length === 1 && parents[0].type === 'suite') {
-                                methodName = parents[0].id.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]}` });
+    async run(request: TestRunRequest, cancellation: CancellationToken): Promise<void> {
+        cancellation.onCancellationRequested(() => this.cancel());
+        this.currentRun = this.testController.createTestRun(request);
+		if (request.include) {
+            const include = [...new Map(request.include.map(item => !item.uri && item.parent?.uri ? [item.parent.id, item.parent] : [item.id, item])).values()];
+            for (let item of include) {
+                if (item.uri) {
+                    this.set(item, 'enqueued');
+                    const idx = item.id.indexOf(':');
+                    await commands.executeCommand(request.profile?.kind === TestRunProfileKind.Debug ? 'java.debug.single' : 'java.run.single', item.uri.toString(), idx < 0 ? undefined : item.id.slice(idx + 1));
                 }
             }
 		} else {
-			this.statesEmitter.fire(<TestLoadFinishedEvent>{ type: 'finished', errorMessage: 'Failed to run mutliple tests'});
+            this.testController.items.forEach(item => this.set(item, 'enqueued'));
+            for (let workspaceFolder of workspace.workspaceFolders || []) {
+                if (!cancellation.isCancellationRequested) {
+                    await commands.executeCommand(request.profile?.kind === TestRunProfileKind.Debug ? 'java.debug.test': 'java.run.test', workspaceFolder.uri.toString());
+                }
+            }
         }
+        this.currentRun.end();
+        this.currentRun = undefined;
     }
 
-    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) {
-                let methodName;
-                if (idx >= 0) {
-                    let test = current.children.find(t => t.id === tests[0]);
-                    if (test) {
-                        methodName = tests[0].slice(idx + 1);
-                    } else {
-                        let parents = current.children.filter(ti => tests[0].startsWith(ti.id));
-                        if (parents && parents.length === 1 && parents[0].type === 'suite') {
-                            methodName = parents[0].id.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]}` });
+    set(item: TestItem, state: 'enqueued' | 'started' | 'passed' | 'failed' | 'skipped' | 'errored', message?: TestMessage | readonly TestMessage[], noPassDown? : boolean): void {
+        if (this.currentRun) {
+            switch (state) {
+                case 'enqueued':
+                case 'started':
+                case 'passed':
+                case 'skipped':
+                    this.currentRun[state](item);
+                    break;
+                case 'failed':
+                case 'errored':
+                    this.currentRun[state](item, message || new TestMessage(''));
+                    break;
+            }
+            if (!noPassDown) {
+                item.children.forEach(child => this.set(child, state, message, noPassDown));
             }
-		} else {
-			this.statesEmitter.fire(<TestLoadFinishedEvent>{ type: 'finished', errorMessage: 'Failed to debug mutliple tests'});
         }
     }
 
@@ -153,151 +106,135 @@ export class NbTestAdapter implements TestAdapter {
 	}
 
     testProgress(suite: TestSuite): void {
-        let cnt = this.children.length;
+        const currentSuite = this.testController.items.get(suite.name);
         switch (suite.state) {
             case 'loaded':
-                if (this.updateTests(suite)) {
-                    if (this.children.length !== cnt) {
-                        this.children.sort((a, b) => a.label.localeCompare(b.label));
-                    }
-                    this.testsEmitter.fire(<TestLoadFinishedEvent>{ type: 'finished', suite: this.testSuite });
-                }
+                this.updateTests(suite);
                 break;
-            case 'running':
-                this.statesEmitter.fire(<TestSuiteEvent>{ type: 'suite', suite: suite.suiteName, state: suite.state });
+            case 'started':
+                if (currentSuite) {
+                    this.set(currentSuite, 'started');
+                }
                 break;
             case 'completed':
             case 'errored':
-                let errMessage: string | undefined;
                 if (suite.tests) {
-                    if (this.updateTests(suite, true)) {
-                        if (this.children.length !== cnt) {
-                            this.children.sort((a, b) => a.label.localeCompare(b.label));
-                        }
-                        this.testsEmitter.fire(<TestLoadFinishedEvent>{ type: 'finished', suite: this.testSuite });
-                    }
-                    const currentSuite = this.children.find(s => s.id === suite.suiteName);
+                    this.updateTests(suite, true);
                     if (currentSuite) {
-                        suite.tests.forEach(test => {
-                            let message: string | undefined;
-                            let decorations: TestDecoration[] | undefined;
-                            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]);
+                        const suiteMessages: TestMessage[] = [];
+                        suite.tests?.forEach(test => {
+                            if (this.currentRun) {
+                                let currentTest = currentSuite.children.get(test.id);
+                                if (!currentTest) {
+                                    currentSuite.children.forEach(item => {
+                                        if (!currentTest && test.id.startsWith(item.id)) {
+                                            currentTest = item.children.get(test.id);
+                                        }
+                                    });
+                                }
+                                let message: TestMessage | undefined;
+                                if (test.stackTrace) {
+                                    message = new TestMessage(test.stackTrace.join('\n'));
+                                    if (currentTest) {
+                                        const testUri = currentTest.uri || currentTest.parent?.uri;
+                                        if (testUri) {
+                                            const fileName = path.basename(testUri.path);
+                                            const line = test.stackTrace.map(frame => {
+                                                const info = frame.match(/^\s*at[^\(]*\((\S*):(\d*)\)$/);
+                                                if (info && info.length >= 3 && info[1] === fileName) {
+                                                    return parseInt(info[2]);
+                                                }
+                                                return null;
+                                            }).find(l => l);
+                                            const pos = line ? new Position(line - 1, 0) : currentTest.range?.start;
+                                            if (pos) {
+                                                message.location = new Location(testUri, pos);
+                                            }
                                         }
-                                        return null;
-                                    }).find(l => l);
-                                    if (line) {
-                                        decorations = [{ line: line - 1, message: test.stackTrace[0] }];
+                                    } else {
+                                        message.location = new Location(currentSuite.uri!, currentSuite.range!.start);
                                     }
                                 }
-                            }
-                            let currentTest = (currentSuite as TestSuiteInfo).children.find(ti => ti.id === test.id);
-                            if (!currentTest) {
-                                let parents = (currentSuite as TestSuiteInfo).children.filter(ti => test.id.startsWith(ti.id));
-                                if (parents && parents.length === 1 && parents[0].type === 'suite') {
-                                    currentTest = parents[0].children.find(ti => ti.id === test.id);
+                                if (currentTest && test.state !== 'loaded') {
+                                    this.set(currentTest, test.state, message, true);
+                                } else if (test.state !== 'passed' && message) {
+                                    suiteMessages.push(message);
                                 }
                             }
-                            if (currentTest) {
-                                this.statesEmitter.fire(<TestEvent>{ type: 'test', test: test.id, state: test.state, message, decorations });
-                            } else if (test.state !== 'passed' && message && !errMessage) {
-                                suite.state = 'errored';
-                                errMessage = message;
-                            }
                         });
+                        if (suiteMessages.length > 0) {
+                            this.set(currentSuite, 'errored', suiteMessages, true);
+                            currentSuite.children.forEach(item => this.set(item, 'skipped'));
+                        }
                     }
                 }
-                this.statesEmitter.fire(<TestSuiteEvent>{ type: 'suite', suite: suite.suiteName, state: suite.state, message: errMessage });
                 break;
         }
     }
 
-    updateTests(suite: TestSuite, preserveMissingTests?: boolean): boolean {
-        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) {
-                const ids: Set<string> = new Set();
-                const parentSuites: Map<TestSuiteInfo, string[]> = new Map();
-                suite.tests.forEach(test => {
-                    ids.add(test.id);
-                    let currentTest = (currentSuite as TestSuiteInfo).children.find(ti => ti.id === test.id);
-                    if (currentTest) {
-                        const file = test.file ? Uri.parse(test.file)?.path : undefined;
-                        if (file && currentTest.file !== file) {
-                            currentTest.file = file;
-                            changed = true;
+    updateTests(suite: TestSuite, testExecution?: boolean): void {
+        let currentSuite = this.testController.items.get(suite.name);
+        const suiteUri = suite.file ? Uri.parse(suite.file) : undefined;
+        if (!currentSuite || suiteUri && currentSuite.uri?.toString() !== suiteUri.toString()) {
+            currentSuite = this.testController.createTestItem(suite.name, suite.name, suiteUri);
+            this.testController.items.add(currentSuite);
+        }
+        const suiteRange = asRange(suite.range);
+        if (!testExecution && suiteRange && suiteRange !== currentSuite.range) {
+            currentSuite.range = suiteRange;
+        }
+        const children: TestItem[] = []
+        const parentTests: Map<TestItem, TestItem[]> = new Map();
+        suite.tests?.forEach(test => {
+            let currentTest = currentSuite?.children.get(test.id);
+            const testUri = test.file ? Uri.parse(test.file) : undefined;
+            if (currentTest) {
+                if (currentTest.uri?.toString() !== testUri?.toString()) {
+                    currentTest = this.testController.createTestItem(test.id, test.name, testUri);
+                    currentSuite?.children.add(currentTest);
+                }
+                const testRange = asRange(test.range);
+                if (!testExecution && testRange && testRange !== currentTest.range) {
+                    currentTest.range = testRange;
+                }
+                children.push(currentTest);
+            } else {
+                if (testExecution) {
+                    const parents: TestItem[] = [];
+                    currentSuite?.children.forEach(item => {
+                        if (test.id.startsWith(item.id)) {
+                            parents.push(item);
                         }
-                        if (test.line && currentTest.line !== test.line) {
-                            currentTest.line = test.line;
-                            changed = true;
+                    });
+                    if (parents.length === 1) {
+                        let arr = parentTests.get(parents[0]);
+                        if (!arr) {
+                            parentTests.set(parents[0], arr = []);
+                            children.push(parents[0]);
                         }
-                    } else {
-                        let parents = (currentSuite as TestSuiteInfo).children.filter(ti => test.id.startsWith(ti.id));
-                        if (parents && parents.length === 1) {
-                            let childSuite: TestSuiteInfo = parents[0].type === 'suite' ? parents[0] : { type: 'suite', id: parents[0].id, label: parents[0].label, file: parents[0].file, line: parents[0].line, children: [] };
-                            if (!parentSuites.has(childSuite)) {
-                                parentSuites.set(childSuite, childSuite.children.map(ti => ti.id));
-                            }
-                            if (parents[0].type === 'test') {
-                                (currentSuite as TestSuiteInfo).children[(currentSuite as TestSuiteInfo).children.indexOf(parents[0])] = childSuite;
-                                changed = true;
-                            }
-                            currentTest = childSuite.children.find(ti => ti.id === test.id);
-                            if (currentTest) {
-                                let arr = parentSuites.get(childSuite);
-                                let idx = arr ? arr.indexOf(currentTest.id) : -1;
-                                if (idx >= 0) {
-                                    arr?.splice(idx, 1);
-                                }
-                            } else {
-                                let label = test.shortName;
-                                if (label.startsWith(childSuite.label)) {
-                                    label = label.slice(childSuite.label.length).trim();
-                                }
-                                childSuite.children.push({ type: 'test', id: test.id, label, tooltip: test.fullName, file: test.file ? Uri.parse(test.file)?.path : undefined, line: test.line });
-                                changed = true;
-                            }
-                        } else {
-                            (currentSuite as TestSuiteInfo).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;
+                        let label = test.name;
+                        if (label.startsWith(parents[0].label)) {
+                            label = label.slice(parents[0].label.length).trim();
                         }
+                        arr.push(this.testController.createTestItem(test.id, label));
                     }
-                });
-                parentSuites.forEach((val, key) => {
-                    if (val.length > 0) {
-                        key.children = key.children.filter(ti => val.indexOf(ti.id) < 0);
-                        changed = true;
-                    }
-                });
-                if (!preserveMissingTests && (currentSuite as TestSuiteInfo).children.length !== ids.size) {
-                    (currentSuite as TestSuiteInfo).children = (currentSuite as TestSuiteInfo).children.filter(ti => ids.has(ti.id));
-                    changed = true;
+                } else {
+                    currentTest = this.testController.createTestItem(test.id, test.name, testUri);
+                    currentTest.range = asRange(test.range);
+                    children.push(currentTest);
+                    currentSuite?.children.add(currentTest);
                 }
             }
+        });
+        if (testExecution) {
+            parentTests.forEach((val, key) => {
+                const item = this.testController.createTestItem(key.id, key.label, key.uri);
+                item.range = key.range;
+                item.children.replace(val);
+                currentSuite?.children.add(item);
+            });
         } 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;
+            currentSuite.children.replace(children);
         }
-        return changed;
     }
 }

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@netbeans.apache.org
For additional commands, e-mail: commits-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists