diff --git a/ide/gototest/apichanges.xml b/ide/gototest/apichanges.xml
new file mode 100644
index 000000000000..7ae8b88aba2b
--- /dev/null
+++ b/ide/gototest/apichanges.xml
@@ -0,0 +1,97 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ Navigate To Test APIs
+
+
+
+
+
+ Adding TestOppositeLocator
+
+
+
+
+
+ A TestOppsiteLocator class introduced that finds one or multiple test files for a source file, and one or multiple source files for a test file.
+
+
+
+
+
+
+
+
+
+
+ Navigate To Test API changes by date
+
+
+
+
+
This document lists changes made to the Navigate To Test APIs. Please ask on the
+ dev@netbeans.apache.org
+ mailing list if you have any questions about the details of a
+ change, or are wondering how to convert existing code to be compatible.
+
+
+
+
+
@FOOTER@
+
+
+
+
diff --git a/ide/gototest/nbproject/project.xml b/ide/gototest/nbproject/project.xml
index 47a0a1c6e4de..a25850f94569 100644
--- a/ide/gototest/nbproject/project.xml
+++ b/ide/gototest/nbproject/project.xml
@@ -25,6 +25,15 @@
org.netbeans.modules.gototest
+
+ org.netbeans.api.annotations.common
+
+
+
+ 1
+ 1.50
+
+ org.netbeans.modules.gsf.testrunner
@@ -100,7 +109,7 @@
- org.openide.util.ui
+ org.openide.util
@@ -108,19 +117,19 @@
- org.openide.util
+ org.openide.util.lookup
- 9.3
+ 8.0
- org.openide.util.lookup
+ org.openide.util.ui
- 8.0
+ 9.3
@@ -135,6 +144,7 @@
org.netbeans.modules.jackpot30.fileorg.netbeans.modules.java.hints.declarative
+ org.netbeans.modules.java.lsp.serverorg.netbeans.modules.junitorg.netbeans.modules.php.projectorg.netbeans.modules.python.project
@@ -142,6 +152,7 @@
org.netbeans.modules.ruby.projectorg.netbeans.modules.testngorg.netbeans.modules.web.clientproject.api
+ org.netbeans.api.gototestorg.netbeans.spi.gototest
diff --git a/ide/gototest/src/org/netbeans/api/gototest/TestOppositesLocator.java b/ide/gototest/src/org/netbeans/api/gototest/TestOppositesLocator.java
new file mode 100644
index 000000000000..c1d4db4f0ffd
--- /dev/null
+++ b/ide/gototest/src/org/netbeans/api/gototest/TestOppositesLocator.java
@@ -0,0 +1,268 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.netbeans.api.gototest;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.stream.Collectors;
+import org.netbeans.api.annotations.common.CheckForNull;
+import org.netbeans.spi.gototest.TestLocator;
+import org.netbeans.spi.gototest.TestLocator.LocationListener;
+import org.netbeans.spi.gototest.TestLocator.LocationResult;
+import org.openide.filesystems.FileObject;
+import org.openide.util.Lookup;
+import org.openide.util.NbBundle;
+import org.openide.util.NbBundle.Messages;
+import org.openide.util.RequestProcessor;
+
+/**
+ * Find one or multiple test files for a source file,
+ * and one or multiple source files for a test file.
+ *
+ * @since 1.57
+ */
+public final class TestOppositesLocator {
+
+ private static final RequestProcessor WORKER = new RequestProcessor(TestOppositesLocator.class.getName(), 1, false, false);
+
+ /**
+ * The default instance of TestOppositesLocator.
+ *
+ * @return the default instance of TestOppositesLocator
+ */
+ public static TestOppositesLocator getDefault() {
+ return new TestOppositesLocator();
+ }
+
+ private TestOppositesLocator() {}
+
+ /**
+ * Given the file and position in the file, if the:
+ *
+ *
given file is a source file, find corresponding test file or test files, if exist.
+ *
given file is a test file, find corresponding source file or source files, if exist.
+ *
+ *
+ * @param fo the file for which the opposites should be found
+ * @param caretOffset position in the file, or {@code -1} if unknown
+ * @return a result describing either an error, or a possibly empty list of locations found;
+ * note one of {@code errorMessage} and {@code locations} is always {@code null},
+ * and one always non-{@code null}.
+ */
+ @NbBundle.Messages("No_Test_Or_Tested_Class_Found=No Test or Tested class found")
+ public CompletableFuture findOpposites(FileObject fo, int caretOffset) {
+ if (!isSupportedFileType(fo)) {
+ CompletableFuture result = new CompletableFuture<>();
+
+ result.complete(new LocatorResult(Bundle.No_Test_Or_Tested_Class_Found(), null, null));
+ return result;
+ }
+ else {
+ return populateLocationResults(fo, caretOffset)
+ .thenApply(locations -> new LocatorResult(null,
+ locations.stream()
+ .filter(l -> l.getErrorMessage() != null)
+ .map(l -> l.getErrorMessage())
+ .collect(Collectors.toList()),
+ locations.stream()
+ .filter(l -> l.getFileObject()!= null)
+ .map(l -> new Location(l.getFileObject(), l.getOffset()))
+ .collect(Collectors.toList())));
+ }
+ }
+
+ private CompletableFuture extends List> populateLocationResults(FileObject fo, int caretOffset) {
+ Collection extends TestLocator> locators = Lookup.getDefault()
+ .lookupAll(TestLocator.class)
+ .stream()
+ .filter(tl -> tl.appliesTo(fo))
+ .collect(Collectors.toList());
+ CompletableFuture> result = new CompletableFuture<>();
+
+ result.complete(new ArrayList<>());
+
+ for (TestLocator locator : locators) {
+ if (locator.appliesTo(fo)) {
+ CompletableFuture> currentFuture = new CompletableFuture<>();
+
+ if (locator.asynchronous()) {
+ locator.findOpposite(fo, caretOffset, new LocationListener() {
+ @Override
+ public void foundLocation(FileObject fo, LocationResult location) {
+ List resultList =
+ location != null ? Collections.singletonList(location)
+ : Collections.emptyList();
+
+ currentFuture.complete(resultList);
+ }
+ });
+ } else {
+ WORKER.post(() -> {
+ try {
+ LocationResult opposite = locator.findOpposite(fo, caretOffset);
+ List resultList =
+ opposite != null ? Collections.singletonList(opposite)
+ : Collections.emptyList();
+
+ currentFuture.complete(resultList);
+ }catch(ThreadDeath td){
+ throw td;
+ }catch (Throwable t) {
+ currentFuture.completeExceptionally(t);
+ }
+ });
+ }
+
+ result = result.thenCombine(currentFuture, (accumulator, currentList) -> {
+ accumulator.addAll(currentList);
+ return accumulator;
+ });
+ }
+ }
+
+ return result;
+ }
+
+ private TestLocator getLocatorFor(FileObject fo) {
+ Collection extends TestLocator> locators = Lookup.getDefault().lookupAll(TestLocator.class);
+ for (TestLocator locator : locators) {
+ if (locator.appliesTo(fo)) {
+ return locator;
+ }
+ }
+
+ return null;
+ }
+
+ private boolean isSupportedFileType(FileObject fo) {
+ TestLocator locator = fo != null ? getLocatorFor(fo) : null;
+ if (locator != null) {
+ return locator.getFileType(fo) != TestLocator.FileType.NEITHER;
+ }
+
+ return false;
+ }
+
+ /**
+ * A description of the found opposite files. Exactly one of {@code errorMessage}
+ * {@code locations} will be non-null;
+ */
+ public static final class LocatorResult {
+ private final String errorMessage;
+ private final Collection extends String> providerErrors;
+ private final Collection extends Location> locations;
+
+ private LocatorResult(String errorMessage,
+ List extends String> providerErrors,
+ List extends Location> locations) {
+ if (errorMessage == null && locations == null) {
+ throw new IllegalArgumentException("Both errorMessage and locations is null!");
+ }
+ if (errorMessage != null && locations != null) {
+ throw new IllegalArgumentException("Both errorMessage and locations is non-null!");
+ }
+ if (providerErrors == null ^ locations == null) {
+ throw new IllegalArgumentException("Both providerErrors and locations must either be null or non-null");
+ }
+ this.errorMessage = errorMessage;
+ this.providerErrors = providerErrors != null ? Collections.unmodifiableList(providerErrors) : null;
+ this.locations = locations != null ? Collections.unmodifiableList(locations) : null;
+ }
+
+ /**
+ * Get the error message if present.
+ *
+ * @return error message
+ */
+ public @CheckForNull String getErrorMessage() {
+ return errorMessage;
+ }
+
+ /**
+ * Get error messages provided by the providers.
+ *
+ * @return the errors from the providers.
+ */
+ public @CheckForNull Collection extends String> getProviderErrors() {
+ return providerErrors;
+ }
+
+ /**
+ * Get the locations if present.
+ *
+ * @return the found locations.
+ */
+ public @CheckForNull Collection extends Location> getLocations() {
+ return locations;
+ }
+
+ }
+
+ /**
+ * A description of a target location.
+ */
+ public static final class Location {
+ private final FileObject file;
+ private final int offset;
+
+ /**
+ * Construct a Location from a given file and offset.
+ * @param file The FileObject of the opposite file.
+ * @param offset The offset in the file, or -1 if the offset
+ * is unknown.
+ */
+ public Location(FileObject file, int offset) {
+ this.file = file;
+ this.offset = offset;
+ }
+
+ /**
+ * Get the FileObject associated with this location
+ * @return The FileObject for this location, or null if
+ * this is an invalid location. In that case, consult
+ * {@link #getErrorMessage} for more information.
+ */
+ public FileObject getFileObject() {
+ return file;
+ }
+
+ /**
+ * Get the offset associated with this location, if any.
+ * @return The offset for this location, or -1 if the offset
+ * is not known.
+ */
+ public int getOffset() {
+ return offset;
+ }
+
+ /**
+ * Get the proper display name for this location.
+ *
+ * @return the display name for this location
+ */
+ @Messages("DN_Error=Error")
+ public String getDisplayName() {
+ return file != null ? file.getName() : Bundle.DN_Error();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/ide/gototest/src/org/netbeans/modules/gototest/GotoOppositeAction.java b/ide/gototest/src/org/netbeans/modules/gototest/GotoOppositeAction.java
index cee8765c6698..b2f7f7aabe35 100644
--- a/ide/gototest/src/org/netbeans/modules/gototest/GotoOppositeAction.java
+++ b/ide/gototest/src/org/netbeans/modules/gototest/GotoOppositeAction.java
@@ -23,22 +23,23 @@
import java.awt.Point;
import java.awt.Rectangle;
import java.util.Collection;
-import java.util.HashMap;
-import java.util.concurrent.Semaphore;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
import java.util.logging.Level;
import java.util.logging.Logger;
+import java.util.stream.Collectors;
import javax.swing.Action;
import javax.swing.JEditorPane;
import javax.swing.SwingUtilities;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
+import org.netbeans.api.gototest.TestOppositesLocator;
+import org.netbeans.api.gototest.TestOppositesLocator.Location;
+import org.netbeans.api.gototest.TestOppositesLocator.LocatorResult;
import org.netbeans.modules.gsf.testrunner.ui.api.TestCreatorPanelDisplayer;
import org.netbeans.modules.gsf.testrunner.ui.api.UICommonUtils;
import org.netbeans.modules.parsing.api.Source;
-import org.netbeans.spi.gototest.TestLocator;
-import org.netbeans.spi.gototest.TestLocator.FileType;
-import org.netbeans.spi.gototest.TestLocator.LocationListener;
import org.netbeans.spi.gototest.TestLocator.LocationResult;
import org.openide.DialogDisplayer;
import org.openide.NotifyDescriptor;
@@ -64,8 +65,6 @@
* @author Tor Norbye
*/
public class GotoOppositeAction extends CallableSystemAction {
- private HashMap locationResults = new HashMap();
- private Semaphore lock;
public GotoOppositeAction() {
putValue("noIconInMenu", Boolean.TRUE); //NOI18N
@@ -113,7 +112,6 @@ protected boolean asynchronous() {
}
@Override
- @NbBundle.Messages("No_Test_Or_Tested_Class_Found=No Test or Tested class found")
public void performAction() {
int caretOffsetHolder[] = { -1 };
final FileObject fo = getApplicableFileObject(caretOffsetHolder);
@@ -126,26 +124,38 @@ public void performAction() {
@Override
public void run() {
- FileType currentFileType = getCurrentFileType();
- if(currentFileType == FileType.NEITHER) {
- StatusDisplayer.getDefault().setStatusText(Bundle.No_Test_Or_Tested_Class_Found());
+ LocatorResult opposites;
+
+ try {
+ opposites = TestOppositesLocator.getDefault().findOpposites(fo, caretOffset).get();
+ } catch (InterruptedException | ExecutionException ex) {
+ Exceptions.printStackTrace(ex);
+ return ;
+ }
+
+ if (opposites.getErrorMessage() != null) {
+ StatusDisplayer.getDefault().setStatusText(opposites.getErrorMessage());
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
TestCreatorPanelDisplayer.getDefault().displayPanel(UICommonUtils.getFileObjectsFromNodes(TopComponent.getRegistry().getActivatedNodes()), null, null);
}
});
- }
- else {
- populateLocationResults(fo, caretOffset);
+ } else {
+ opposites.getProviderErrors()
+ .stream()
+ .forEach(msg -> {
+ DialogDisplayer.getDefault().notify(
+ new NotifyDescriptor.Message(msg, NotifyDescriptor.INFORMATION_MESSAGE));
+ });
+ Collection extends Location> locations = opposites.getLocations();
SwingUtilities.invokeLater(new Runnable() {
-
@Override
public void run() {
- if (locationResults.size() == 1) {
- handleResult(locationResults.keySet().iterator().next());
- } else if (locationResults.size() > 1) {
- showPopup(fo);
+ if (locations.size() == 1) {
+ handleResult(locations.iterator().next());
+ } else if (locations.size() > 1) {
+ showPopup(fo, locations);
}
}
});
@@ -155,82 +165,8 @@ public void run() {
}
}
- private void populateLocationResults(FileObject fo, int caretOffset) {
- locationResults.clear();
-
- Collection extends TestLocator> locators = Lookup.getDefault().lookupAll(TestLocator.class);
-
- int permits = 0;
- for (TestLocator locator : locators) {
- if (locator.appliesTo(fo)) {
- permits++;
- }
- }
-
- lock = new Semaphore(permits);
- try {
- lock.acquire(permits);
- } catch (InterruptedException e) {
- }
-
- for (TestLocator locator : locators) {
- if (locator.appliesTo(fo)) {
- doPopulateLocationResults(fo, caretOffset, locator);
- }
- }
- try {
- lock.acquire(permits);
- } catch (InterruptedException ex) {
- Exceptions.printStackTrace(ex);
- }
- }
-
- private void doPopulateLocationResults(FileObject fo, int caretOffset, TestLocator locator) {
- if (locator != null) {
- if (locator.appliesTo(fo)) {
- if (locator.asynchronous()) {
- locator.findOpposite(fo, caretOffset, new LocationListener() {
-
- @Override
- public void foundLocation(FileObject fo, LocationResult location) {
- if (location != null) {
- FileObject fileObject = location.getFileObject();
- if(fileObject == null) {
- String msg = location.getErrorMessage();
- if (msg != null) {
- DialogDisplayer.getDefault().notify(
- new NotifyDescriptor.Message(msg, NotifyDescriptor.INFORMATION_MESSAGE));
- }
- } else {
- locationResults.put(location, fileObject.getName());
- }
- }
- lock.release();
- }
- });
- } else {
- LocationResult opposite = locator.findOpposite(fo, caretOffset);
-
- if (opposite != null) {
- FileObject fileObject = opposite.getFileObject();
- if (fileObject == null) {
- String msg = opposite.getErrorMessage();
- if (msg != null) {
- DialogDisplayer.getDefault().notify(
- new NotifyDescriptor.Message(msg, NotifyDescriptor.INFORMATION_MESSAGE));
- }
- } else {
- locationResults.put(opposite, fileObject.getName());
- }
- }
- lock.release();
- }
- }
- }
- }
-
@NbBundle.Messages("LBL_PickExpression=Go to Test")
- private void showPopup(FileObject fo) {
+ private void showPopup(FileObject fo, Collection extends Location> locations) {
JTextComponent pane;
Point l = new Point(-1, -1);
@@ -243,51 +179,20 @@ private void showPopup(FileObject fo) {
SwingUtilities.convertPointToScreen(l, pane);
String label = Bundle.LBL_PickExpression();
- PopupUtil.showPopup(new OppositeCandidateChooser(this, label, locationResults), label, l.x, l.y, true, -1);
+ PopupUtil.showPopup(new OppositeCandidateChooser(this, label, locations), label, l.x, l.y, true, -1);
}
} catch (BadLocationException ex) {
Logger.getLogger(GotoOppositeAction.class.getName()).log(Level.WARNING, null, ex);
}
}
- public void handleResult(LocationResult opposite) {
+ public void handleResult(Location opposite) {
FileObject fileObject = opposite.getFileObject();
if (fileObject != null) {
NbDocument.openDocument(fileObject, opposite.getOffset(), Line.ShowOpenType.OPEN, Line.ShowVisibilityType.FOCUS);
- } else if (opposite.getErrorMessage() != null) {
- String msg = opposite.getErrorMessage();
- NotifyDescriptor descr = new NotifyDescriptor.Message(msg,
- NotifyDescriptor.INFORMATION_MESSAGE);
- DialogDisplayer.getDefault().notify(descr);
- }
- }
-
- private TestLocator getLocatorFor(FileObject fo) {
- Collection extends TestLocator> locators = Lookup.getDefault().lookupAll(TestLocator.class);
- for (TestLocator locator : locators) {
- if (locator.appliesTo(fo)) {
- return locator;
- }
}
-
- return null;
}
- private FileType getFileType(FileObject fo) {
- TestLocator locator = getLocatorFor(fo);
- if (locator != null) {
- return locator.getFileType(fo);
- }
-
- return FileType.NEITHER;
- }
-
- private FileType getCurrentFileType() {
- FileObject fo = getApplicableFileObject(new int[1]);
-
- return (fo != null) ? getFileType(fo) : FileType.NEITHER;
- }
-
private FileObject getApplicableFileObject(int[] caretPosHolder) {
if (!EventQueue.isDispatchThread()) {
// Unsafe to ask for an editor pane from a random thread.
diff --git a/ide/gototest/src/org/netbeans/modules/gototest/OppositeCandidateChooser.java b/ide/gototest/src/org/netbeans/modules/gototest/OppositeCandidateChooser.java
index 8e1ec5f413c2..d1630e55eb86 100644
--- a/ide/gototest/src/org/netbeans/modules/gototest/OppositeCandidateChooser.java
+++ b/ide/gototest/src/org/netbeans/modules/gototest/OppositeCandidateChooser.java
@@ -24,12 +24,13 @@
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
-import java.util.HashMap;
+import java.util.Collection;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.ListModel;
+import org.netbeans.api.gototest.TestOppositesLocator.Location;
import org.netbeans.spi.gototest.TestLocator.LocationResult;
/**
@@ -39,10 +40,10 @@
public class OppositeCandidateChooser extends JPanel implements FocusListener {
private final String caption;
- private static HashMap toShow;
+ private static Collection extends Location> toShow;
private static GotoOppositeAction action;
- public OppositeCandidateChooser(GotoOppositeAction action, String caption, HashMap toShow) {
+ public OppositeCandidateChooser(GotoOppositeAction action, String caption, Collection extends Location> toShow) {
this.caption = caption;
OppositeCandidateChooser.action = action;
OppositeCandidateChooser.toShow = toShow;
@@ -122,14 +123,14 @@ private void jList1KeyPressed(java.awt.event.KeyEvent evt) {//GEN-FIRST:event_jL
// End of variables declaration//GEN-END:variables
private void openSelected() {
- LocationResult locator = (LocationResult) jList1.getSelectedValue();
+ Location locator = (Location) jList1.getSelectedValue();
action.handleResult(locator);
PopupUtil.hidePopup();
}
private ListModel createListModel() {
DefaultListModel dlm = new DefaultListModel();
- for (LocationResult cand: toShow.keySet()) {
+ for (Location cand: toShow) {
dlm.addElement(cand);
}
@@ -146,9 +147,9 @@ public Component getListCellRendererComponent(
boolean cellHasFocus) {
Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
- if (value instanceof LocationResult) {
- LocationResult locator = (LocationResult) value;
- setText(toShow.get(locator));
+ if (value instanceof Location) {
+ Location location = (Location) value;
+ setText(location.getDisplayName());
}
return c;
diff --git a/java/java.lsp.server/nbproject/project.xml b/java/java.lsp.server/nbproject/project.xml
index 4ec16db9ddc5..2ad0cde82e2e 100644
--- a/java/java.lsp.server/nbproject/project.xml
+++ b/java/java.lsp.server/nbproject/project.xml
@@ -305,6 +305,15 @@
1.20
+
+ org.netbeans.modules.gototest
+
+
+
+ 1
+ 1.57
+
+ org.netbeans.modules.gsf.testrunner
diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/commands/TestOppositesCommandProvider.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/commands/TestOppositesCommandProvider.java
new file mode 100644
index 000000000000..11c4755c213c
--- /dev/null
+++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/commands/TestOppositesCommandProvider.java
@@ -0,0 +1,68 @@
+/*
+ * 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.commands;
+
+import com.google.gson.JsonPrimitive;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import org.netbeans.api.gototest.TestOppositesLocator;
+import org.netbeans.modules.java.lsp.server.Utils;
+import org.netbeans.spi.lsp.CommandProvider;
+import org.openide.DialogDisplayer;
+import org.openide.NotifyDescriptor;
+import org.openide.filesystems.FileObject;
+import org.openide.util.lookup.ServiceProvider;
+
+@ServiceProvider(service=CommandProvider.class)
+public class TestOppositesCommandProvider implements CommandProvider {
+
+ private static final String NBLS_GO_TO_TEST = "nbls.go.to.test";
+ private static final Set COMMANDS = new HashSet<>(Arrays.asList(NBLS_GO_TO_TEST));
+
+ @Override
+ public Set getCommands() {
+ return COMMANDS;
+ }
+
+ @Override
+ public CompletableFuture