From 9e580b235774a51ebbf7e494e4edd59647b948db Mon Sep 17 00:00:00 2001 From: Dusan Balek Date: Wed, 17 Apr 2024 14:57:46 +0200 Subject: [PATCH] LSP: Generate Tests converted to Source Action. --- .../modules/java/testrunner/Bundle.properties | 21 +++ .../server/protocol/TestClassGenerator.java | 152 +++++++++++------- java/java.lsp.server/vscode/src/utils.ts | 6 +- .../netbeans/modules/junit/DefaultPlugin.java | 4 +- 4 files changed, 122 insertions(+), 61 deletions(-) create mode 100644 java/java.lsp.server/nbcode/branding/modules/org-netbeans-modules-java-testrunner.jar/org/netbeans/modules/java/testrunner/Bundle.properties diff --git a/java/java.lsp.server/nbcode/branding/modules/org-netbeans-modules-java-testrunner.jar/org/netbeans/modules/java/testrunner/Bundle.properties b/java/java.lsp.server/nbcode/branding/modules/org-netbeans-modules-java-testrunner.jar/org/netbeans/modules/java/testrunner/Bundle.properties new file mode 100644 index 000000000000..f630844e1ea4 --- /dev/null +++ b/java/java.lsp.server/nbcode/branding/modules/org-netbeans-modules-java-testrunner.jar/org/netbeans/modules/java/testrunner/Bundle.properties @@ -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 diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TestClassGenerator.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TestClassGenerator.java index 10dfa28bfd66..68a48021daba 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TestClassGenerator.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/TestClassGenerator.java @@ -18,10 +18,10 @@ */ 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; @@ -29,6 +29,7 @@ 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; @@ -36,12 +37,13 @@ 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; @@ -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; @@ -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 getCodeActions(NbCodeLanguageClient client, ResultIterator resultIterator, CodeActionParams params) throws Exception { + List 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(); @@ -97,18 +110,6 @@ public List 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()) { @@ -125,9 +126,8 @@ public List getCodeActions(NbCodeLanguageClient client, ResultIterat List result = new ArrayList<>(); for (Map.Entry> 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 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; } @@ -138,7 +138,12 @@ public Set getCommands() { } @Override + @NbBundle.Messages({ + "DN_SelectFramework=Select a test framework to use", + "DN_ProvideClassName=Please type the target test class name" + }) public CompletableFuture processCommand(NbCodeLanguageClient client, String command, List arguments) { + CompletableFuture future = new CompletableFuture<>(); try { if (arguments.size() > 2) { String uri = ((JsonPrimitive) arguments.get(0)).getAsString(); @@ -146,53 +151,88 @@ public CompletableFuture processCommand(NbCodeLanguageClient client, Str 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> providers = Lookup.getDefault().lookupResult(TestCreatorProvider.class).allItems(); - for (final Lookup.Item 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 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> f = new CompletableFuture<>(); + if (params.getStep() < totalSteps) { + Either,String> frameworkData = params.getData().get(FRAMEWORKS); + if (frameworkData != null) { + List 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,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, String> frameworkData = result.get(FRAMEWORKS); + QuickPickItem selectedFramework = (frameworkData != null ? frameworkData.getLeft() : testingFrameworks).get(0); + Either, 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> providers = Lookup.getDefault().lookupResult(TestCreatorProvider.class).allItems(); + for (final Lookup.Item 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 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> getValidCombinations(CompilationInfo info) { diff --git a/java/java.lsp.server/vscode/src/utils.ts b/java/java.lsp.server/vscode/src/utils.ts index 29f2009e8a18..ade6a9c68095 100644 --- a/java/java.lsp.server/vscode/src/utils.ts +++ b/java/java.lsp.server/vscode/src/utils.ts @@ -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] : []), diff --git a/java/junit/src/org/netbeans/modules/junit/DefaultPlugin.java b/java/junit/src/org/netbeans/modules/junit/DefaultPlugin.java index b9e077ba356a..60db07cc0f4a 100644 --- a/java/junit/src/org/netbeans/modules/junit/DefaultPlugin.java +++ b/java/junit/src/org/netbeans/modules/junit/DefaultPlugin.java @@ -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 }