Skip to content

Commit

Permalink
Merge pull request #7276 from dbalek/dbalek/lsp-generate-tests-source…
Browse files Browse the repository at this point in the history
…-action

LSP: Generate Tests converted to Source Action.
  • Loading branch information
dbalek authored Apr 19, 2024
2 parents 1a4519c + 9e580b2 commit 553bcec
Show file tree
Hide file tree
Showing 4 changed files with 122 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# 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.

PROP_generate_setUp_default=false
PROP_generate_tearDown_default=false
PROP_generate_class_setUp_default=false
PROP_generate_class_tearDown_default=false
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,32 @@
*/
package org.netbeans.modules.java.lsp.server.protocol;

import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonPrimitive;
import com.google.gson.JsonSyntaxException;
import com.sun.source.tree.ClassTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.CodeAction;
import org.eclipse.lsp4j.CodeActionKind;
import org.eclipse.lsp4j.CodeActionParams;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.ShowDocumentParams;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.queries.UnitTestForSourceQuery;
Expand All @@ -59,6 +61,11 @@
import org.netbeans.modules.gsf.testrunner.plugin.GuiUtilsProvider;
import org.netbeans.modules.java.lsp.server.URITranslator;
import org.netbeans.modules.java.lsp.server.Utils;
import org.netbeans.modules.java.lsp.server.input.InputBoxStep;
import org.netbeans.modules.java.lsp.server.input.InputService;
import org.netbeans.modules.java.lsp.server.input.QuickPickItem;
import org.netbeans.modules.java.lsp.server.input.QuickPickStep;
import org.netbeans.modules.java.lsp.server.input.ShowMutliStepInputParams;
import org.netbeans.modules.parsing.api.ResultIterator;
import org.openide.filesystems.FileChangeAdapter;
import org.openide.filesystems.FileChangeListener;
Expand All @@ -75,18 +82,24 @@
*
* @author Dusan Balek
*/
@ServiceProvider(service = CodeActionsProvider.class, position = 100)
@ServiceProvider(service = CodeActionsProvider.class, position = 9)
public final class TestClassGenerator extends CodeActionsProvider {

private static final String GENERATE_TEST_CLASS_COMMAND = "nbls.java.generate.testClass";
private static final String FRAMEWORKS = "frameworks";
private static final String CLASS_NAME = "className";

private final Gson gson = new Gson();

@Override
@NbBundle.Messages({
"# {0} - the testing framework to be used, e.g. JUnit, TestNG,...",
"# {1} - the location where the test class will be created",
"DN_GenerateTestClass=Create Test Class [{0} in {1}]"
"DN_GenerateTestClass=Generate Tests..."
})
public List<CodeAction> getCodeActions(NbCodeLanguageClient client, ResultIterator resultIterator, CodeActionParams params) throws Exception {
List<String> only = params.getContext().getOnly();
if (only == null || !only.contains(CodeActionKind.Source)) {
return Collections.emptyList();
}
CompilationController info = resultIterator.getParserResult() != null ? CompilationController.get(resultIterator.getParserResult()) : null;
if (info == null) {
return Collections.emptyList();
Expand All @@ -97,18 +110,6 @@ public List<CodeAction> getCodeActions(NbCodeLanguageClient client, ResultIterat
if (!TreeUtilities.CLASS_TREE_KINDS.contains(tp.getLeaf().getKind())) {
return Collections.emptyList();
}
ClassTree cls = (ClassTree) tp.getLeaf();
SourcePositions sourcePositions = info.getTrees().getSourcePositions();
int startPos = (int) sourcePositions.getStartPosition(tp.getCompilationUnit(), cls);
String code = info.getText();
if (startPos < 0 || offset < 0 || offset < startPos || offset >= code.length()) {
return Collections.emptyList();
}
String headerText = code.substring(startPos, offset);
int idx = headerText.indexOf('{');
if (idx >= 0) {
return Collections.emptyList();
}
ClassPath cp = info.getClasspathInfo().getClassPath(ClasspathInfo.PathKind.SOURCE);
FileObject fileObject = info.getFileObject();
if (!fileObject.isValid()) {
Expand All @@ -125,9 +126,8 @@ public List<CodeAction> getCodeActions(NbCodeLanguageClient client, ResultIterat
List<CodeAction> result = new ArrayList<>();
for (Map.Entry<Object, List<String>> entrySet : validCombinations.entrySet()) {
Object location = entrySet.getKey();
for (String testingFramework : entrySet.getValue()) {
result.add((createCodeAction(client, Bundle.DN_GenerateTestClass(testingFramework, getLocationText(location)), CodeActionKind.Refactor, null, GENERATE_TEST_CLASS_COMMAND, Utils.toUri(fileObject), testingFramework, getTargetFolderUri(location))));
}
List<QuickPickItem> testingFrameworks = entrySet.getValue().stream().map(framework -> new QuickPickItem(framework)).collect(Collectors.toList());
result.add((createCodeAction(client, Bundle.DN_GenerateTestClass(), CodeActionKind.Source, null, GENERATE_TEST_CLASS_COMMAND, Utils.toUri(fileObject), getTargetFolderUri(location), testingFrameworks)));
}
return result;
}
Expand All @@ -138,61 +138,101 @@ public Set<String> getCommands() {
}

@Override
@NbBundle.Messages({
"DN_SelectFramework=Select a test framework to use",
"DN_ProvideClassName=Please type the target test class name"
})
public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
CompletableFuture<Object> future = new CompletableFuture<>();
try {
if (arguments.size() > 2) {
String uri = ((JsonPrimitive) arguments.get(0)).getAsString();
FileObject fileObject = Utils.fromUri(uri);
if (fileObject == null) {
throw new IllegalArgumentException(String.format("Cannot resolve source file from uri: %s", uri));
}
String testingFramework = ((JsonPrimitive) arguments.get(1)).getAsString();
String targetUri = ((JsonPrimitive) arguments.get(2)).getAsString();
String targetUri = ((JsonPrimitive) arguments.get(1)).getAsString();
FileObject targetFolder = getTargetFolder(targetUri);
if (targetFolder == null) {
throw new IllegalArgumentException(String.format("Cannot resolve target folder from uri: %s", targetUri));
}
Collection<? extends Lookup.Item<TestCreatorProvider>> providers = Lookup.getDefault().lookupResult(TestCreatorProvider.class).allItems();
for (final Lookup.Item<TestCreatorProvider> provider : providers) {
if (provider.getDisplayName().equals(testingFramework)) {
final TestCreatorProvider.Context context = new TestCreatorProvider.Context(new FileObject[]{fileObject});
context.setSingleClass(true);
context.setTargetFolder(targetFolder);
context.setTestClassName(getPreffiledName(fileObject, testingFramework));
FileChangeListener fcl = new FileChangeAdapter() {
@Override
public void fileDataCreated(FileEvent fe) {
RequestProcessor.getDefault().post(() -> {
client.showDocument(new ShowDocumentParams(Utils.toUri(fe.getFile())));
}, 1000);
List<QuickPickItem> testingFrameworks = Arrays.asList(gson.fromJson((JsonArray)arguments.get(2), QuickPickItem[].class));
InputService.Registry inputServiceRegistry = Lookup.getDefault().lookup(InputService.Registry.class);
if (inputServiceRegistry != null) {
int totalSteps = testingFrameworks.size() > 1 ? 2 : 1;
String inputId = inputServiceRegistry.registerInput(params -> {
CompletableFuture<Either<QuickPickStep, InputBoxStep>> f = new CompletableFuture<>();
if (params.getStep() < totalSteps) {
Either<List<QuickPickItem>,String> frameworkData = params.getData().get(FRAMEWORKS);
if (frameworkData != null) {
List<QuickPickItem> selectedFrameworks = frameworkData.getLeft();
for (QuickPickItem testingFramework : testingFrameworks) {
testingFramework.setPicked(selectedFrameworks.contains(testingFramework));
}
}
};
targetFolder.addRecursiveListener(fcl);
try {
provider.getInstance().createTests(context);
} finally {
RequestProcessor.getDefault().post(() -> {
targetFolder.removeRecursiveListener(fcl);
}, 1000);
f.complete(Either.forLeft(new QuickPickStep(totalSteps, FRAMEWORKS, Bundle.DN_SelectFramework(), testingFrameworks)));
} else if (params.getStep() == totalSteps) {
Either<List<QuickPickItem>,String> frameworkData = params.getData().get(FRAMEWORKS);
QuickPickItem selectedFramework = (frameworkData != null ? frameworkData.getLeft() : testingFrameworks).get(0);
f.complete(Either.forRight(new InputBoxStep(totalSteps, CLASS_NAME, Bundle.DN_ProvideClassName(), getPreffiledName(fileObject, selectedFramework.getLabel()))));
} else {
f.complete(null);
}
}
return f;
});
client.showMultiStepInput(new ShowMutliStepInputParams(inputId, Bundle.DN_GenerateDelegateMethod())).thenAccept(result -> {
Either<List<QuickPickItem>, String> frameworkData = result.get(FRAMEWORKS);
QuickPickItem selectedFramework = (frameworkData != null ? frameworkData.getLeft() : testingFrameworks).get(0);
Either<List<QuickPickItem>, String> classNameData = result.get(CLASS_NAME);
String className = classNameData != null ? classNameData.getRight() : null;
future.complete(selectedFramework != null && className != null ? generate(client, fileObject, targetFolder, className, selectedFramework.getLabel()) : null);
});
}
} else {
throw new IllegalArgumentException(String.format("Illegal number of arguments received for command: %s", command));
}
} catch (JsonSyntaxException | IllegalArgumentException | MalformedURLException ex) {
client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
future.completeExceptionally(ex);
}
return CompletableFuture.completedFuture(true);
return future;
}

private static String getLocationText(Object location) {
String text = location instanceof SourceGroup
? ((SourceGroup) location).getDisplayName()
: location instanceof FileObject
? FileUtil.getFileDisplayName((FileObject) location)
: location.toString();
return text;
private boolean generate(NbCodeLanguageClient client, FileObject fileObject, FileObject targetFolder, String className, String testingFramework) {
Collection<? extends Lookup.Item<TestCreatorProvider>> providers = Lookup.getDefault().lookupResult(TestCreatorProvider.class).allItems();
for (final Lookup.Item<TestCreatorProvider> provider : providers) {
if (provider.getDisplayName().equals(testingFramework)) {
final TestCreatorProvider.Context context = new TestCreatorProvider.Context(new FileObject[]{fileObject});
context.setSingleClass(true);
context.setTargetFolder(targetFolder);
context.setTestClassName(className);
AtomicReference<FileChangeListener> fcl = new AtomicReference<>();
fcl.set(new FileChangeAdapter() {
@Override
public void fileDataCreated(FileEvent fe) {
RequestProcessor.getDefault().post(() -> {
client.showDocument(new ShowDocumentParams(Utils.toUri(fe.getFile())));
}, 1000);
FileChangeListener l = fcl.getAndSet(null);
if (l != null) {
targetFolder.removeRecursiveListener(l);
}
}
});
targetFolder.addRecursiveListener(fcl.get());
try {
provider.getInstance().createTests(context);
} finally {
RequestProcessor.getDefault().post(() -> {
FileChangeListener l = fcl.getAndSet(null);
if (l != null) {
targetFolder.removeRecursiveListener(l);
}
}, 10000);
}
return true;
}
}
return false;
}

private static Map<Object, List<String>> getValidCombinations(CompilationInfo info) {
Expand Down
6 changes: 3 additions & 3 deletions java/java.lsp.server/vscode/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,9 @@ export class MultiStepInput {
input.items = items;
if (canSelectMany) {
input.canSelectMany = canSelectMany;
}
if (selectedItems) {
input.selectedItems = selectedItems;
if (selectedItems) {
input.selectedItems = selectedItems;
}
}
input.buttons = [
...(this.steps.length > 1 ? [vscode.QuickInputButtons.Back] : []),
Expand Down
4 changes: 2 additions & 2 deletions java/junit/src/org/netbeans/modules/junit/DefaultPlugin.java
Original file line number Diff line number Diff line change
Expand Up @@ -1457,8 +1457,8 @@ private boolean readProjectSettingsJUnitVer(Project project)
}

if (hasJUnit3 || hasJUnit4 || hasJUnit5) {
junitVer = hasJUnit3 ? JUnitVersion.JUNIT3
: hasJUnit4 ? JUnitVersion.JUNIT4 : JUnitVersion.JUNIT5;
junitVer = hasJUnit5 ? JUnitVersion.JUNIT5
: hasJUnit4 ? JUnitVersion.JUNIT4 : JUnitVersion.JUNIT3;
if (LOG_JUNIT_VER.isLoggable(FINEST)) {
LOG_JUNIT_VER.finest(" - detected version " + junitVer);//NOI18N
}
Expand Down

0 comments on commit 553bcec

Please sign in to comment.