From 56f6f373badb94aa42f788ee1c3ade33024d2bc7 Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Mon, 6 Jul 2020 15:48:34 +0200 Subject: [PATCH] added test cases for spel syntax checking via reconcile engine and improved testability a bit --- .../BootJavaLanguageServerComponents.java | 7 +- .../handlers/BootJavaReconcileEngine.java | 25 +- .../ValueSpelExpressionValidationTest.java | 230 ++++++++++++++++++ 3 files changed, 251 insertions(+), 11 deletions(-) create mode 100644 headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index 7f9c1ad78..0ecc4a4f6 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -122,6 +122,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent private BootJavaHoverProvider hoverProvider; private CodeLensHandler codeLensHandler; private DocumentHighlightHandler highlightsEngine; + private BootJavaReconcileEngine reconcileEngine; private SpringProcessTracker liveProcessTracker; @@ -203,6 +204,8 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent highlightsEngine = createDocumentHighlightEngine(indexer); documents.onDocumentHighlight(highlightsEngine); + reconcileEngine = new BootJavaReconcileEngine(cuCache, projectFinder); + config.addListener(ignore -> { log.info("update live process tracker settings - start"); @@ -222,6 +225,8 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent liveChangeDetectionWatchdog.disableHighlights(); } + reconcileEngine.setSpelExpressionSyntaxValidationEnabled(config.isSpelExpressionValidationEnabled()); + log.info("update live process tracker settings - done"); }); @@ -253,7 +258,7 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent @Override public Optional getReconcileEngine() { - return Optional.of(new BootJavaReconcileEngine(this, config)); + return Optional.of(reconcileEngine); } private void initialized() { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java index 08072c6ba..2bb98f4d5 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaReconcileEngine.java @@ -23,8 +23,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.expression.ParseException; import org.springframework.expression.spel.standard.SpelExpressionParser; -import org.springframework.ide.vscode.boot.app.BootJavaConfig; -import org.springframework.ide.vscode.boot.java.BootJavaLanguageServerComponents; +import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; import org.springframework.ide.vscode.boot.java.value.Constants; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; @@ -44,13 +43,19 @@ public class BootJavaReconcileEngine implements IReconcileEngine { private static final Logger log = LoggerFactory.getLogger(BootJavaReconcileEngine.class); private final JavaProjectFinder projectFinder; - private final BootJavaLanguageServerComponents server; - private final BootJavaConfig config; + private final CompilationUnitCache compilationUnitCache; + + private boolean spelExpressionValidationEnabled; - public BootJavaReconcileEngine(BootJavaLanguageServerComponents server, BootJavaConfig config) { - this.server = server; - this.config = config; - this.projectFinder = this.server.getProjectFinder(); + public BootJavaReconcileEngine(CompilationUnitCache compilationUnitCache, JavaProjectFinder projectFinder) { + this.compilationUnitCache = compilationUnitCache; + this.projectFinder = projectFinder; + + this.spelExpressionValidationEnabled = true; + } + + public void setSpelExpressionSyntaxValidationEnabled(boolean spelExpressionValidationEnabled) { + this.spelExpressionValidationEnabled = spelExpressionValidationEnabled; } @Override @@ -65,7 +70,7 @@ public class BootJavaReconcileEngine implements IReconcileEngine { try { problemCollector.beginCollecting(); - server.getCompilationUnitCache().withCompilationUnit(project, uri, cu -> { + compilationUnitCache.withCompilationUnit(project, uri, cu -> { if (cu != null) { reconcileAST(cu, problemCollector); } @@ -95,7 +100,7 @@ public class BootJavaReconcileEngine implements IReconcileEngine { } protected void visitAnnotation(SingleMemberAnnotation node, IProblemCollector problemCollector) { - if (!config.isSpelExpressionValidationEnabled()) { + if (!spelExpressionValidationEnabled) { return; } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java new file mode 100644 index 000000000..bb519bd57 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/value/test/ValueSpelExpressionValidationTest.java @@ -0,0 +1,230 @@ +/******************************************************************************* + * Copyright (c) 2020 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.value.test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.File; +import java.net.URI; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +import org.apache.commons.io.IOUtils; +import org.eclipse.lsp4j.DidCloseTextDocumentParams; +import org.eclipse.lsp4j.DidOpenTextDocumentParams; +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.eclipse.lsp4j.TextDocumentItem; +import org.gradle.internal.impldep.com.google.common.collect.ImmutableList; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.app.BootJavaConfig; +import org.springframework.ide.vscode.boot.app.BootLanguageServerParams; +import org.springframework.ide.vscode.boot.bootiful.AdHocPropertyHarnessTestConf; +import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness; +import org.springframework.ide.vscode.boot.java.handlers.BootJavaReconcileEngine; +import org.springframework.ide.vscode.boot.java.links.SourceLinkFactory; +import org.springframework.ide.vscode.boot.java.links.SourceLinks; +import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; +import org.springframework.ide.vscode.boot.java.utils.SymbolCache; +import org.springframework.ide.vscode.boot.java.utils.SymbolCacheVoid; +import org.springframework.ide.vscode.boot.metadata.ValueProviderRegistry; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; +import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; +import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; +import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject; +import org.springframework.ide.vscode.commons.util.text.LanguageId; +import org.springframework.ide.vscode.commons.util.text.TextDocument; +import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Martin Lippert + */ +@RunWith(SpringRunner.class) +@BootLanguageServerTest +@Import({AdHocPropertyHarnessTestConf.class, ValueSpelExpressionValidationTest.TestConf.class}) +public class ValueSpelExpressionValidationTest { + + @Autowired private BootLanguageServerHarness harness; + @Autowired private IJavaProject testProject; + @Autowired private JavaProjectFinder projectFinder; + @Autowired private CompilationUnitCache compilationUnitCache; + @Autowired private SimpleLanguageServer server; + + private File directory; + private String docUri; + private TestProblemCollector problemCollector; + private BootJavaReconcileEngine reconcileEngine; + + @Configuration + static class TestConf { + + //Somewhat strange test setup, test provides a specific test project. + //The project finder finds this test project, + //But it is not used in the indexProvider/harness. + //this is a bit odd... but we preserved the strangeness how it was. + + @Bean MavenJavaProject testProject() throws Exception { + return ProjectsHarness.INSTANCE.mavenProject("test-annotations"); + } + + @Bean PropertyIndexHarness indexHarness(ValueProviderRegistry valueProviders) { + return new PropertyIndexHarness(valueProviders); + } + + @Bean JavaProjectFinder projectFinder(MavenJavaProject testProject) { + return new JavaProjectFinder() { + + @Override + public Optional find(TextDocumentIdentifier doc) { + return Optional.ofNullable(testProject); + } + + @Override + public Collection all() { + // TODO Auto-generated method stub + return testProject == null ? Collections.emptyList() : ImmutableList.of(testProject); + } + }; + } + + @Bean BootLanguageServerHarness harness(SimpleLanguageServer server, BootLanguageServerParams serverParams, PropertyIndexHarness indexHarness, JavaProjectFinder projectFinder) throws Exception { + return new BootLanguageServerHarness(server, serverParams, indexHarness, projectFinder, LanguageId.JAVA, ".java"); + } + + @Bean BootLanguageServerParams serverParams(SimpleLanguageServer server, JavaProjectFinder projectFinder, ValueProviderRegistry valueProviders, PropertyIndexHarness indexHarness) { + BootLanguageServerParams testDefaults = BootLanguageServerHarness.createTestDefault(server, valueProviders); + return new BootLanguageServerParams( + projectFinder, + ProjectObserver.NULL, + indexHarness.getIndexProvider(), + testDefaults.typeUtilProvider + ); + } + + @Bean SymbolCache symbolCache() { + return new SymbolCacheVoid(); + } + + @Bean SourceLinks sourceLinks(SimpleTextDocumentService documents, CompilationUnitCache cuCache) { + return SourceLinkFactory.NO_SOURCE_LINKS; + } + + } + + @Before + public void setup() throws Exception { + harness.intialize(null); + + directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotations/").toURI()); + docUri = directory.toPath().resolve("src/main/java/org/test/TestValueCompletion.java").toUri().toString(); + + problemCollector = new TestProblemCollector(); + reconcileEngine = new BootJavaReconcileEngine(compilationUnitCache, projectFinder); + } + + @After + public void closeDoc() throws Exception { + TextDocumentIdentifier identifier = new TextDocumentIdentifier(docUri); + DidCloseTextDocumentParams closeParams = new DidCloseTextDocumentParams(identifier); + server.getTextDocumentService().didClose(closeParams); + server.getAsync().waitForAll(); + } + + @Test + public void testNoSpelExpressionFound() throws Exception { + TextDocument doc = prepareDocument("@Value(\"onField\")", "@Value(\"something\")"); + assertNotNull(doc); + + reconcileEngine.reconcile(doc, problemCollector); + + List problems = problemCollector.getCollectedProblems(); + assertEquals(0, problems.size()); + } + + @Test + public void testCorrectSpelExpressionFound() throws Exception { + TextDocument doc = prepareDocument("@Value(\"onField\")", "@Value(\"#{new String('hello world').toUpperCase()}\")"); + assertNotNull(doc); + + reconcileEngine.reconcile(doc, problemCollector); + + List problems = problemCollector.getCollectedProblems(); + assertEquals(0, problems.size()); + } + + @Test + public void testIncorrectSpelExpressionFound() throws Exception { + TextDocument doc = prepareDocument("@Value(\"onField\")", "@Value(\"#{new String('hello world).toUpperCase()}\")"); + assertNotNull(doc); + + reconcileEngine.reconcile(doc, problemCollector); + + List problems = problemCollector.getCollectedProblems(); + assertEquals(1, problems.size()); + } + + private TextDocument prepareDocument(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception { + String content = IOUtils.toString(new URI(docUri)); + + TextDocumentItem docItem = new TextDocumentItem(docUri, LanguageId.JAVA.toString(), 0, content); + DidOpenTextDocumentParams openParams = new DidOpenTextDocumentParams(docItem); + server.getTextDocumentService().didOpen(openParams); + server.getAsync().waitForAll(); + + TextDocument doc = server.getTextDocumentService().get(docUri); + + int position = content.indexOf(selectedAnnotation); + doc.replace(position, selectedAnnotation.length(), annotationStatementBeforeTest); + + return doc; + } + + public static class TestProblemCollector implements IProblemCollector { + + private List problems = new ArrayList<>(); + + @Override + public void beginCollecting() { + } + + @Override + public void endCollecting() { + } + + @Override + public void accept(ReconcileProblem problem) { + problems.add(problem); + } + + protected List getCollectedProblems() { + return problems; + } + + } + +}