Start adding some tests for 'InjectedInto' hover provider
This commit is contained in:
@@ -34,6 +34,7 @@ import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.livehover.InjectedIntoProvider;
|
||||
import org.springframework.ide.vscode.boot.java.profiles.ActiveProfilesProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.LiveAppURLSymbolProvider;
|
||||
import org.springframework.ide.vscode.boot.java.requestmapping.RequestMappingHoverProvider;
|
||||
@@ -261,7 +262,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer {
|
||||
providers.put(ActiveProfilesProvider.ANNOTATION, new ActiveProfilesProvider());
|
||||
|
||||
providers.put(org.springframework.ide.vscode.boot.java.autowired.Constants.SPRING_AUTOWIRED, new AutowiredHoverProvider());
|
||||
providers.put(org.springframework.ide.vscode.boot.java.beans.Constants.SPRING_COMPONENT, new ComponentHoverProvider());
|
||||
providers.put(org.springframework.ide.vscode.boot.java.beans.Constants.SPRING_COMPONENT, new InjectedIntoProvider());
|
||||
|
||||
providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL, new ConditionalsLiveHoverProvider());
|
||||
providers.put(org.springframework.ide.vscode.boot.java.conditionals.Constants.CONDITIONAL_ON_BEAN, new ConditionalsLiveHoverProvider());
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.livehover;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.eclipse.jdt.core.dom.ASTNode;
|
||||
import org.eclipse.jdt.core.dom.Annotation;
|
||||
import org.eclipse.jdt.core.dom.ITypeBinding;
|
||||
import org.eclipse.jdt.core.dom.TypeDeclaration;
|
||||
import org.eclipse.lsp4j.Hover;
|
||||
import org.eclipse.lsp4j.Range;
|
||||
import org.eclipse.lsp4j.jsonrpc.messages.Either;
|
||||
import org.springframework.ide.vscode.boot.java.handlers.HoverProvider;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.SpringBootApp;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
|
||||
import org.springframework.ide.vscode.commons.util.Log;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
public class InjectedIntoProvider implements HoverProvider {
|
||||
|
||||
@Override
|
||||
public Collection<Range> getLiveHoverHints(Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) {
|
||||
//Highlight if any running app contains an instance of this component
|
||||
try {
|
||||
if (runningApps.length > 0) {
|
||||
String beanType = getAnnotatedType(annotation);
|
||||
if (beanType!=null) {
|
||||
if (Stream.of(runningApps).anyMatch(app -> !app.getBeans().getBeansOfType(beanType).isEmpty())) {
|
||||
return ImmutableList.of(doc.toRange(annotation.getStartPosition(), annotation.getLength()));
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Log.log(e);
|
||||
}
|
||||
return ImmutableList.of();
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Hover> provideHover(ASTNode node, Annotation annotation, ITypeBinding type, int offset,
|
||||
TextDocument doc, SpringBootApp[] runningApps) {
|
||||
if (runningApps.length>0) {
|
||||
String beanType = getAnnotatedType(annotation);
|
||||
if (beanType!=null) {
|
||||
StringBuilder hover = new StringBuilder();
|
||||
hover.append("**Injection report for `"+beanType+"'**\n\n");
|
||||
boolean hasInterestingApp = false;
|
||||
for (SpringBootApp app : runningApps) {
|
||||
LiveBeansModel beans = app.getBeans();
|
||||
boolean isInteresting = !app.getBeans().getBeansOfType(beanType).isEmpty();
|
||||
if (isInteresting) {
|
||||
hasInterestingApp = true;
|
||||
hover.append(niceAppName(app)+":");
|
||||
for (LiveBean bean : beans.getBeansOfType(beanType)) {
|
||||
hover.append("\n\n");
|
||||
hover.append("Bean with id: `"+bean.getId()+"`\n");
|
||||
List<LiveBean> dependers = beans.getBeansDependingOn(bean.getId());
|
||||
if (dependers.isEmpty()) {
|
||||
hover.append("**Not injected anywhere**\n");
|
||||
} else {
|
||||
hover.append("injected into:\n\n");
|
||||
for (LiveBean dependingBean : dependers) {
|
||||
hover.append("- "+dependingBean.getType()+"\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (hasInterestingApp) {
|
||||
System.out.println(hover);
|
||||
return CompletableFuture.completedFuture(new Hover(
|
||||
ImmutableList.of(Either.forLeft(hover.toString()))
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getAnnotatedType(Annotation annotation) {
|
||||
ASTNode parent = annotation.getParent();
|
||||
if (parent instanceof TypeDeclaration) {
|
||||
TypeDeclaration typeDecl = (TypeDeclaration) parent;
|
||||
ITypeBinding binding = typeDecl.resolveBinding();
|
||||
if (binding!=null) {
|
||||
return binding.getQualifiedName();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String niceAppName(SpringBootApp app) {
|
||||
return "Process [PID="+app.getProcessID()+", name=`"+app.getProcessName()+"`]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.profile;
|
||||
package org.springframework.ide.vscode.boot.java.livehover.test;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2017 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
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.java.livehover.test;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBean;
|
||||
import org.springframework.ide.vscode.commons.boot.app.cli.livebean.LiveBeansModel;
|
||||
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
|
||||
import org.springframework.ide.vscode.commons.util.text.LanguageId;
|
||||
import org.springframework.ide.vscode.languageserver.testharness.Editor;
|
||||
import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
|
||||
import org.springframework.ide.vscode.project.harness.MockRunningAppProvider;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness.CustomizableProjectContent;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness.ProjectCustomizer;
|
||||
|
||||
public class InjectedIntoProviderTest {
|
||||
|
||||
private static final ProjectCustomizer FOO_INTERFACE = (CustomizableProjectContent p) -> {
|
||||
p.createType("com.examle.Foo",
|
||||
"package com.example;\n" +
|
||||
"\n" +
|
||||
"public interface Foo {\n" +
|
||||
" void doSomeFoo();\n" +
|
||||
"}\n"
|
||||
);
|
||||
};
|
||||
|
||||
private BootLanguageServerHarness harness;
|
||||
private ProjectsHarness projects = ProjectsHarness.INSTANCE;
|
||||
|
||||
private MockRunningAppProvider mockAppProvider;
|
||||
|
||||
@Before
|
||||
public void setup() throws Exception {
|
||||
mockAppProvider = new MockRunningAppProvider();
|
||||
harness = BootLanguageServerHarness.builder()
|
||||
.mockDefaults()
|
||||
.runningAppProvider(mockAppProvider.provider)
|
||||
.watchDogInterval(Duration.ofMillis(100))
|
||||
.build();
|
||||
|
||||
MavenJavaProject jp = projects.mavenProject("empty-boot-15-web-app", FOO_INTERFACE);
|
||||
assertTrue(jp.getClasspath().findType("com.example.Foo").exists());
|
||||
harness.useProject(projects.mavenProject("empty-boot-15-web-app"));
|
||||
harness.intialize(null);
|
||||
}
|
||||
|
||||
@Test public void componentWithNoInjections() throws Exception {
|
||||
LiveBeansModel beans = LiveBeansModel.builder()
|
||||
.add(LiveBean.builder()
|
||||
.id("fooImplementation")
|
||||
.type("com.example.FooImplementation")
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
mockAppProvider.builder()
|
||||
.isSpringBootApp(true)
|
||||
.processId("111")
|
||||
.processName("the-app")
|
||||
.beans(beans)
|
||||
.build();
|
||||
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA,
|
||||
"package com.example;\n" +
|
||||
"\n" +
|
||||
"import org.springframework.stereotype.Component;\n" +
|
||||
"\n" +
|
||||
"@Component\n" +
|
||||
"public class FooImplementation implements Foo {\n" +
|
||||
"\n" +
|
||||
" @Override\n" +
|
||||
" public void doSomeFoo() {\n" +
|
||||
" System.out.println(\"Foo do do do!\");\n" +
|
||||
" }\n" +
|
||||
"}\n"
|
||||
);
|
||||
editor.assertHighlights("@Component");
|
||||
editor.assertHoverExactText("@Component",
|
||||
"**Injection report for `com.example.FooImplementation'**\n" +
|
||||
"\n" +
|
||||
"Process [PID=111, name=`the-app`]:\n" +
|
||||
"\n" +
|
||||
"Bean with id: `fooImplementation`\n" +
|
||||
"**Not injected anywhere**\n"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void componentWithOneInjection() throws Exception {
|
||||
//Like componentWithNoInjections but checks whether we are properly using the bean id
|
||||
// to determine injections.
|
||||
LiveBeansModel beans = LiveBeansModel.builder()
|
||||
.add(LiveBean.builder()
|
||||
.id("fooImplementation")
|
||||
.type("com.example.FooImplementation")
|
||||
.build()
|
||||
)
|
||||
.add(LiveBean.builder()
|
||||
.id("myController")
|
||||
.type("com.example.MyController")
|
||||
.dependencies("fooImplementation")
|
||||
.build()
|
||||
)
|
||||
.build();
|
||||
mockAppProvider.builder()
|
||||
.isSpringBootApp(true)
|
||||
.processId("111")
|
||||
.processName("the-app")
|
||||
.beans(beans)
|
||||
.build();
|
||||
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA,
|
||||
"package com.example;\n" +
|
||||
"\n" +
|
||||
"import org.springframework.stereotype.Component;\n" +
|
||||
"\n" +
|
||||
"@Component\n" +
|
||||
"public class FooImplementation implements Foo {\n" +
|
||||
"\n" +
|
||||
" @Override\n" +
|
||||
" public void doSomeFoo() {\n" +
|
||||
" System.out.println(\"Foo do do do!\");\n" +
|
||||
" }\n" +
|
||||
"}\n"
|
||||
);
|
||||
editor.assertHighlights("@Component");
|
||||
editor.assertHoverExactText("@Component",
|
||||
"**Injection report for `com.example.FooImplementation'**\n" +
|
||||
"\n" +
|
||||
"Process [PID=111, name=`the-app`]:\n" +
|
||||
"\n" +
|
||||
"Bean with id: `fooImplementation`\n" +
|
||||
"injected into:\n" +
|
||||
"\n" +
|
||||
"- com.example.MyController\n"
|
||||
);
|
||||
}
|
||||
|
||||
@Test public void hoHoversWhenNoRunningApps() throws Exception {
|
||||
Editor editor = harness.newEditor(LanguageId.JAVA,
|
||||
"package com.example;\n" +
|
||||
"\n" +
|
||||
"import org.springframework.stereotype.Component;\n" +
|
||||
"\n" +
|
||||
"@Component\n" +
|
||||
"public class FooImplementation implements Foo {\n" +
|
||||
"\n" +
|
||||
" @Override\n" +
|
||||
" public void doSomeFoo() {\n" +
|
||||
" System.out.println(\"Foo do do do!\");\n" +
|
||||
" }\n" +
|
||||
"}\n"
|
||||
);
|
||||
editor.assertHighlights(/*MONE*/);
|
||||
editor.assertNoHover("@Component");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -68,7 +68,11 @@ public class MockRunningAppProvider {
|
||||
}
|
||||
|
||||
public MockAppBuilder beans(String beans) throws Exception {
|
||||
when(app.getBeans()).thenReturn(LiveBeansModel.parse(beans));
|
||||
return beans(LiveBeansModel.parse(beans));
|
||||
}
|
||||
|
||||
public MockAppBuilder beans(LiveBeansModel beans) {
|
||||
when(app.getBeans()).thenReturn(beans);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,65 +10,131 @@
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.project.harness;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.maven.MavenBuilder;
|
||||
import org.springframework.ide.vscode.commons.maven.MavenCore;
|
||||
import org.springframework.ide.vscode.commons.maven.java.MavenJavaProject;
|
||||
import org.springframework.ide.vscode.commons.maven.java.classpathfile.JavaProjectWithClasspathFile;
|
||||
import org.springframework.ide.vscode.project.harness.ProjectsHarness.CustomizableProjectContent;
|
||||
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import reactor.util.function.Tuple3;
|
||||
import reactor.util.function.Tuples;
|
||||
|
||||
import org.springframework.ide.vscode.commons.util.IOUtil;
|
||||
|
||||
/**
|
||||
* Test projects harness
|
||||
*
|
||||
*
|
||||
* @author Alex Boyko
|
||||
* @author Kris De Volder
|
||||
*/
|
||||
public class ProjectsHarness {
|
||||
|
||||
public static final ProjectsHarness INSTANCE = new ProjectsHarness();;
|
||||
|
||||
public Cache<String, IJavaProject> cache = CacheBuilder.newBuilder().concurrencyLevel(1).build();
|
||||
|
||||
|
||||
public static final ProjectsHarness INSTANCE = new ProjectsHarness();;
|
||||
|
||||
public Cache<Object, IJavaProject> cache = CacheBuilder.newBuilder().concurrencyLevel(1).build();
|
||||
|
||||
/**
|
||||
* A callback that is given a chance to make changes to test project contents before the test project
|
||||
* is created from it.
|
||||
*/
|
||||
public interface ProjectCustomizer {
|
||||
void customize(CustomizableProjectContent projectContent) throws Exception;
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides convenience apis to make changes to project content from a {@link ProjectCustomizer}
|
||||
*/
|
||||
public static class CustomizableProjectContent {
|
||||
private final File projectRoot;
|
||||
|
||||
public CustomizableProjectContent(File projectRoot) {
|
||||
this.projectRoot = projectRoot;
|
||||
}
|
||||
|
||||
public void createFile(String path, String content) throws Exception {
|
||||
File target = new File(projectRoot, path);
|
||||
IOUtil.pipe(new ByteArrayInputStream(content.getBytes("UTF8")), target);
|
||||
}
|
||||
|
||||
public void createType(String fqName, String sourceCode) throws Exception {
|
||||
String sourceFile = sourceFolder()+"/"+fqName.replace('.', '/')+".java";
|
||||
createFile(sourceFile, sourceCode);
|
||||
}
|
||||
|
||||
private String sourceFolder() {
|
||||
return "src/main/java";
|
||||
}
|
||||
}
|
||||
|
||||
private enum ProjectType {
|
||||
MAVEN,
|
||||
CLASSPATH_TXT
|
||||
}
|
||||
|
||||
|
||||
private ProjectsHarness() {
|
||||
}
|
||||
|
||||
|
||||
public IJavaProject project(ProjectType type, String name, ProjectCustomizer customizer) throws Exception {
|
||||
Tuple3<ProjectType, String, ProjectCustomizer> key = Tuples.of(type, name, customizer);
|
||||
return cache.get(key, () -> {
|
||||
Path baseProjectPath = getProjectPath(name);
|
||||
File testProjectRoot = Files.createTempDir();
|
||||
FileUtils.copyDirectory(baseProjectPath.toFile(), testProjectRoot);
|
||||
customizer.customize(new CustomizableProjectContent(testProjectRoot));
|
||||
return createProject(type, testProjectRoot.toPath());
|
||||
});
|
||||
}
|
||||
|
||||
private IJavaProject createProject(ProjectType type, Path testProjectPath) throws Exception {
|
||||
switch (type) {
|
||||
case MAVEN:
|
||||
MavenBuilder.newBuilder(testProjectPath).clean().pack().javadoc().skipTests().execute();
|
||||
return new MavenJavaProject(MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile());
|
||||
case CLASSPATH_TXT:
|
||||
MavenBuilder.newBuilder(testProjectPath).clean().pack().skipTests().execute();
|
||||
return new JavaProjectWithClasspathFile(testProjectPath.resolve(MavenCore.CLASSPATH_TXT).toFile());
|
||||
default:
|
||||
throw new IllegalStateException("Bug!!! Missing case");
|
||||
}
|
||||
}
|
||||
|
||||
public IJavaProject project(ProjectType type, String name) throws Exception {
|
||||
return cache.get(type + "/" + name, () -> {
|
||||
Path testProjectPath = getProjectPath(name);
|
||||
switch (type) {
|
||||
case MAVEN:
|
||||
MavenBuilder.newBuilder(testProjectPath).clean().pack().javadoc().skipTests().execute();
|
||||
return new MavenJavaProject(MavenCore.getDefault(), testProjectPath.resolve(MavenCore.POM_XML).toFile());
|
||||
case CLASSPATH_TXT:
|
||||
MavenBuilder.newBuilder(testProjectPath).clean().pack().skipTests().execute();
|
||||
return new JavaProjectWithClasspathFile(testProjectPath.resolve(MavenCore.CLASSPATH_TXT).toFile());
|
||||
default:
|
||||
throw new IllegalStateException("Bug!!! Missing case");
|
||||
}
|
||||
return createProject(type, testProjectPath);
|
||||
});
|
||||
}
|
||||
|
||||
protected Path getProjectPath(String name) throws URISyntaxException, IOException {
|
||||
return getProjectPathFromClasspath(name);
|
||||
}
|
||||
|
||||
|
||||
private Path getProjectPathFromClasspath(String name) throws URISyntaxException, IOException {
|
||||
URI resource = ProjectsHarness.class.getResource("/test-projects/" + name).toURI();
|
||||
return Paths.get(resource);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public MavenJavaProject mavenProject(String name, ProjectCustomizer customizer) throws Exception {
|
||||
return (MavenJavaProject) project(ProjectType.MAVEN, name, customizer);
|
||||
}
|
||||
|
||||
public MavenJavaProject mavenProject(String name) throws Exception {
|
||||
return (MavenJavaProject) project(ProjectType.MAVEN, name);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user