Add a first test for jdt.ls.extension

This commit is contained in:
Kris De Volder
2018-05-04 07:47:58 -07:00
parent a6d5e3c64f
commit 689da33d90
19 changed files with 603 additions and 3 deletions

3
.gitignore vendored
View File

@@ -24,6 +24,9 @@ eclipse-language-servers/**/*.jar
# Exceptions
!**/.mvn/wrapper/maven-wrapper.jar
!**/jdt-ls-extension/**/test-projects/**/.classpath
!**/jdt-ls-extension/**/test-projects/**/.project
!**/jdt-ls-extension/**/test-projects/**/.settings
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.springframework.tooling.jdt.ls.commons.test</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.ManifestBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.pde.SchemaBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.pde.PluginNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,7 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8

View File

@@ -0,0 +1,16 @@
Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: Commons
Bundle-SymbolicName: org.springframework.tooling.jdt.ls.commons.test
Bundle-Version: 1.0.0.qualifier
Automatic-Module-Name: org.springframework.tooling.jdt.ls.commons.test
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Require-Bundle: org.springframework.tooling.jdt.ls.commons,
org.eclipse.core.runtime,
org.eclipse.jdt.core,
org.eclipse.core.resources,
org.apache.commons.lang3,
org.junit,
org.apache.commons.io,
org.eclipse.swt

View File

@@ -0,0 +1,4 @@
source.. = src/
output.. = bin/
bin.includes = META-INF/,\
.

View File

@@ -0,0 +1,18 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<packaging>eclipse-test-plugin</packaging>
<groupId>org.springframework.ide.vscode</groupId>
<artifactId>org.springframework.tooling.jdt.ls.commons.test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>org.springframework.tooling.jdt.ls.commons</name>
<parent>
<groupId>org.springframework.tooling</groupId>
<artifactId>jdt-ls-extension-parent</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
</project>

View File

@@ -0,0 +1,120 @@
/*******************************************************************************
* Copyright (c) 2018 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.tooling.jdt.ls.commons;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.net.URI;
import java.time.Duration;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.FileLocator;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.jdt.core.JavaCore;
import org.junit.After;
import org.junit.Test;
import org.springframework.tooling.jdt.ls.commons.classpath.Classpath;
import org.springframework.tooling.jdt.ls.commons.classpath.ClientCommandExecutor;
import org.springframework.tooling.jdt.ls.commons.classpath.ReusableClasspathListenerHandler;
import org.springsource.ide.eclipse.commons.frameworks.test.util.ACondition;
public class ClasspathListenerHandlerTest {
static class Info {
public final String name;
public final Classpath classpath;
private Info(String name, Classpath cp) {
super();
this.name = name;
this.classpath = cp;
}
}
public class MockClasspathCache implements ClientCommandExecutor {
String commandId = RandomStringUtils.randomAlphabetic(8);
Map<File, Info> classpaths = new HashMap<>();
@Override
public synchronized Object executeClientCommand(String id, Object... params) throws Exception {
if (id.equals(commandId)) {
System.out.println("received: "+Arrays.asList(params));
File projectLoc = new File(new URI((String) params[0]));
String name = (String) params[1];
boolean deleted = (boolean) params[2];
if (deleted) {
classpaths.remove(projectLoc);
} else {
Classpath cp = (Classpath) params[3];
classpaths.put(projectLoc, new Info(name, cp));
}
}
return "whatever";
}
public synchronized Info getFor(IProject project) {
File location = project.getLocation().toFile();
return classpaths.get(location);
}
public void dispose() {
service.removeClasspathListener(commandId);
}
}
private MockClasspathCache classpaths = new MockClasspathCache();
private ReusableClasspathListenerHandler service = new ReusableClasspathListenerHandler(classpaths);
@After
public void tearDown() {
classpaths.dispose();
assertTrue(service.hasNoActiveSubscriptions());
}
@Test
public void classpathIsSentForExistingProject() throws Exception {
String projectName = "classpath-test-simple-java-project";
IProject project = createTestProject(projectName);
service.addClasspathListener(classpaths.commandId);
ACondition.waitFor("Project with classpath to appear", Duration.ofSeconds(5), () -> {
Classpath cp = classpaths.getFor(project).classpath;
assertTrue(cp.getEntries().stream().filter(cpe -> Classpath.isSource(cpe)).count()==1); //has 1 source entry
assertTrue(cp.getEntries().stream().filter(cpe -> Classpath.isBinary(cpe) && cpe.isSystem()).count()>=1); //has some system libraries
});
}
private IProject createTestProject(String name) throws Exception {
File testProjectLocation = new File(FileLocator.toFileURL(Platform.getBundle("org.springframework.tooling.jdt.ls.commons.test").getEntry("test-projects/"+name)).toURI());
IProject project = ResourcesPlugin.getWorkspace().getRoot().getProject(name);
IProjectDescription desc = ResourcesPlugin.getWorkspace().newProjectDescription(null);
desc.setName(name);
desc.setLocation(Path.fromOSString(testProjectLocation.toString()));
project.create(desc, null);
project.open(null);
assertTrue(project.hasNature(JavaCore.NATURE_ID));
return project;
}
}

View File

@@ -0,0 +1,264 @@
/*******************************************************************************
* Copyright (c) 2012, 2018 Pivotal Software, 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 Software, Inc. - initial API and implementation
*******************************************************************************/
package org.springsource.ide.eclipse.commons.frameworks.test.util;
import java.time.Duration;
import java.util.Map;
import org.eclipse.core.runtime.jobs.IJobManager;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.swt.widgets.Display;
import junit.framework.AssertionFailedError;
/**
* Abstract class to create a "waitable" condition. This is to be used in implementing
* tests where we want some condition to be true eventually, but cannot be sure it will be
* true immediately.
* <p>
* Typical use would like so:
* <code>
* new ACondition() {
* public boolean test() throws Exception {
* ... some asserts ...
* return ...something to test...;
* }
* }.waitFor(4000); // wait for 4 seconds or until test passes.
* </code>
* An exception will be thrown if the test method does not return true before the
* timeout limit.
* <p>
* If the test method throws an exception this is treated the same as returning false.
* If condition fails, we will try to rethrow a pertinent exception (typically the
* exception thrown by the test method, the last time we tried to run it.
*
* @author Kris De Volder
*/
public abstract class ACondition {
private String description = null;
/**
* This method is deprecated because it is a common problem that client calls it
* and then forgets to call the waitFor method on the created instance. This results
* in test code that looks like it is testing something but doesn't because the
* asserts inside the ACondition are never actually executed.
* <p>
* Clients should instead call one of the constructor that takes a timeout
* as a parameter (which does the to waitFor automatically).
*/
@Deprecated
public ACondition() {
}
/**
* This method is deprecated because it is a common problem that client calls it
* and then forgets to call the waitFor method on the created instance. This results
* in test code that looks like it is testing something but doesn't because the
* asserts inside the ACondition are never actually executed.
* <p>
* Clients should instead call one of the constructor that takes a timeout
* as a parameter (which does the to waitFor automatically).
*/
@Deprecated
public ACondition(String description) {
this.description = description;
}
public ACondition(long timeout) throws Exception {
this();
waitFor(timeout);
}
public ACondition(String description, long timeout) throws Exception {
this(description);
waitFor(timeout);
}
Throwable e = null;
/**
* Deprecated. Use one of the constructors that accepts a timeout value and automatically
* calls waitFor.
*/
@Deprecated
public void waitFor(long timeout) throws Exception {
long SLEEP_FOR = Math.min(2000, timeout/5);
long startTime = System.currentTimeMillis();
long endTime = startTime + timeout;
boolean result = false;
waitForDisplay(); //Make sure this gets called at least once to avoid 'UI thread starvation'.
while (!(result = doTest()) && System.currentTimeMillis() < endTime) {
waitForDisplay(); // Avoids UI deadlock by allowing UI to process events.
try {
Thread.sleep(SLEEP_FOR);
} catch (InterruptedException e) {
}
}
if (!result) {
//Try our best to create a 'nice' exception reflecting the reason for the test failure
System.err.println("ACondition "+describe()+" timed out. Dumping current Thread stacks...\n" +
getStackDumps()
);
if (e!=null) {
if (e instanceof Exception) {
throw (Exception)e;
} else if (e instanceof Error) {
throw (Error)e;
} else {
throw new RuntimeException(e);
}
} else {
throw new RuntimeException(getMessage());
}
}
if (description!=null) {
System.out.println(description + " succeeded after: " + (System.currentTimeMillis() - startTime));
}
}
private String describe() {
if (description!=null) {
return "["+description+"]";
}
return "";
}
private boolean doTest() {
boolean result = false;
try {
e = null;
result = test();
} catch (Throwable e) {
this.e = e;
}
return result;
}
/**
* Test something. If the method returns true, the test passes.
* If it returns false or throws an exception the test fails (and will be
* retried until it passes or timeout is reached).
*/
public abstract boolean test() throws Exception;
/**
* Message used when time out reached without an exception
*/
public String getMessage() {
return "timed out";
}
public static void assertJobManagerIdle() {
final IJobManager jm = Job.getJobManager();
if (jm.isIdle()) {
return; //OK!
}
//Make a nice message listing all the jobs and their present state.
Job[] allJobs = jm.find(null);
StringBuffer msg = new StringBuffer("JobManager not idle: \n");
for (Job job : allJobs) {
msg.append(" Job: "+job.getName() + " State: " + stateString(job) +"\n");
}
throw new AssertionFailedError(msg.toString());
}
public static String stateString(Job job) {
int state = job.getState();
switch (state) {
case Job.RUNNING:
return "RUNNING";
case Job.SLEEPING:
return "SLEEPING";
case Job.WAITING:
return "WAITING";
case Job.NONE:
return "NONE";
default:
return ""+state;
}
}
/**
* A Java 8 friendlier way of using this class with a lambda.
* <p>
* Waits for some asserts to pass by periodically checking them until either:
* a) the asserts all pass (i.e. asserter returns without throwing); or b)
* the timeout is exceeded.
*/
public static void waitFor(String name, Duration timeout, final Asserter asserter) throws Exception {
waitFor(name, timeout.toMillis(), asserter);
}
/**
* A Java 8 friendlier way of using this class with a lambda.
* <p>
* Waits for some asserts to pass by periodically checking them until either:
* a) the asserts all pass (i.e. asserter returns without throwing); or b)
* the timeout is exceeded.
*/
public static void waitFor(String name, long timeout, final Asserter asserter) throws Exception {
new ACondition("Wait for "+name, timeout) {
@Override
public boolean test() throws Exception {
asserter.execute();
return true;
}
};
}
/**
* Allows Display to process events, so UI can make progress. Tests running
* in the UI thread may need to call this to avoid UI deadlocks.
* <p>
* For convenience, it is allowed to call this method from a non UI thread,
* but such calls have no effect.
*/
public static void waitForDisplay() {
if (inUIThread()) {
try {
while (Display.getDefault().readAndDispatch()) {
// do nothing
}
} catch (Throwable e) {
// in e 44 this is throwing exceptions... a lot. Log them in case they contain
// some valuable hints... but move along. These errors happen because some component
// probably unrelated to our tests misbehaved. I suspect GTK3 may be causing
// NPEs and other errors in the Eclipse UI code, for example. These errors seem
// to propagate out of the 'readAndDispatch' call on e44.
e.printStackTrace();
}
}
}
public static boolean inUIThread() {
return Display.getDefault().getThread() == Thread.currentThread();
}
public static StringBuffer getStackDumps() {
StringBuffer sb = new StringBuffer();
Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
for (Map.Entry<Thread, StackTraceElement[]> entry : traces.entrySet()) {
sb.append(entry.getKey().toString());
sb.append("\n");
for (StackTraceElement element : entry.getValue()) {
sb.append(" ");
sb.append(element.toString());
sb.append("\n");
}
sb.append("\n");
}
return sb;
}
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* Copyright (c) 2016 Pivotal Software, 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 Software, Inc. - initial API and implementation
*******************************************************************************/
package org.springsource.ide.eclipse.commons.frameworks.test.util;
/**
* An {@link Asserter} provides a single method which is expected
* to check some stuff during a test (i.e. typically execute a bunch
* of asserts).
*
* @author Kris De Volder
*/
@FunctionalInterface
public interface Asserter {
/**
* Called on to check some conditions during a test. Should return
* normally to indicate 'success' and throw an exception of any type
* to indicate 'failure'.
*/
void execute() throws Exception;
}

View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="src" path="src"/>
<classpathentry kind="output" path="bin"/>
</classpath>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>classpath-test-simple-java-project</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,11 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8

View File

@@ -125,10 +125,19 @@ public class Classpath {
public void setSystem(boolean isSystem) {
this.isSystem = isSystem;
}
@Override
public String toString() {
return "CPE [kind=" + kind + ", path=" + path + "]";
}
}
public static boolean isSource(CPE e) {
return e!=null && Classpath.ENTRY_KIND_SOURCE.equals(e.getKind());
}
public static boolean isBinary(CPE e) {
return e!=null && Classpath.ENTRY_KIND_BINARY.equals(e.getKind());
}
}

View File

@@ -10,11 +10,15 @@
*******************************************************************************/
package org.springframework.tooling.jdt.ls.commons.classpath;
import static org.springframework.tooling.jdt.ls.commons.Logger.log;
import static org.springframework.tooling.jdt.ls.commons.classpath.Classpath.ENTRY_KIND_BINARY;
import static org.springframework.tooling.jdt.ls.commons.classpath.Classpath.ENTRY_KIND_SOURCE;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
@@ -22,17 +26,48 @@ import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.internal.core.JavaProject;
import org.springframework.tooling.jdt.ls.commons.classpath.Classpath.CPE;
import static org.springframework.tooling.jdt.ls.commons.Logger.*;
public class ClasspathUtil {
private static final Object JRE_CONTAINER_ID = "org.eclipse.jdt.launching.JRE_CONTAINER";
private static Set<String> getSystemLibraryPaths(IJavaProject javaProject) {
try {
IClasspathEntry jreContainer = getJreContainer(javaProject.getRawClasspath());
IClasspathEntry[] resolvedJreEntries = ((JavaProject)javaProject).resolveClasspath(new IClasspathEntry[] {jreContainer});
Set<String> paths = new HashSet<>();
for (IClasspathEntry systemEntry : resolvedJreEntries) {
paths.add(systemEntry.getPath().toString());
}
return paths;
} catch (Exception e) {
e.printStackTrace();
}
return Collections.emptySet();
}
private static IClasspathEntry getJreContainer(IClasspathEntry[] rawClasspath) {
for (IClasspathEntry cpe : rawClasspath) {
if (cpe.getEntryKind()==IClasspathEntry.CPE_CONTAINER) {
if (cpe.getPath().segment(0).equals(JRE_CONTAINER_ID)) {
return cpe;
}
}
}
return null;
}
public static Classpath resolve(IJavaProject javaProject) throws Exception {
//log("resolving classpath " + javaProject.getElementName() +" ...");
List<CPE> cpEntries = new ArrayList<>();
IClasspathEntry[] entries = javaProject.getResolvedClasspath(true);
Set<String> systemLibs = getSystemLibraryPaths(javaProject);
if (entries != null) {
for (IClasspathEntry entry : entries) {
@@ -41,6 +76,9 @@ public class ClasspathUtil {
case Classpath.ENTRY_KIND_BINARY: {
String path = entry.getPath().toString();
CPE cpe = CPE.binary(path);
if (systemLibs.contains(path)) {
cpe.setSystem(true);
}
cpEntries.add(cpe);
break;
}

View File

@@ -1,3 +1,13 @@
/*******************************************************************************
* Copyright (c) 2018 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.tooling.jdt.ls.commons.classpath;
public interface ClientCommandExecutor {

View File

@@ -98,7 +98,11 @@ public class ReusableClasspathListenerHandler {
subscribers = null;
}
}
Logger.log("subsribers = " + subscribers.keySet());
Logger.log("subsribers = " + (subscribers == null ? "null" : subscribers.keySet()));
}
public boolean isEmpty() {
return subscribers == null || subscribers.isEmpty();
}
}
@@ -118,4 +122,8 @@ public class ReusableClasspathListenerHandler {
return "ok";
}
public boolean hasNoActiveSubscriptions() {
return subscribptions.isEmpty();
}
}

View File

@@ -13,6 +13,7 @@
<modules>
<module>org.springframework.tooling.jdt.ls.extension</module>
<module>org.springframework.tooling.jdt.ls.commons</module>
<module>org.springframework.tooling.jdt.ls.commons.test</module>
</modules>
<properties>