Start adding some tests for 'InjectedInto' hover provider

This commit is contained in:
Kris De Volder
2017-10-27 13:19:41 -07:00
parent c7c1ddc692
commit fc9c84f58f
9 changed files with 424 additions and 31 deletions

View File

@@ -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());

View File

@@ -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()+"`]";
}
}

View File

@@ -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;

View File

@@ -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");
}
}

View File

@@ -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;
}

View File

@@ -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);
}