Dealing with project deleted on file system by user.

This commit is contained in:
Kris De Volder
2018-05-10 17:20:17 -07:00
parent 564a211929
commit 27551f6762
3 changed files with 96 additions and 33 deletions

View File

@@ -10,6 +10,7 @@
*******************************************************************************/
package org.springframework.tooling.jdt.ls.commons.test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
@@ -21,11 +22,11 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IProjectDescription;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.resources.WorkspaceJob;
import org.eclipse.core.runtime.CoreException;
@@ -38,26 +39,29 @@ import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jdt.core.JavaCore;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.springframework.tooling.jdt.ls.commons.Logger;
import org.springframework.tooling.jdt.ls.commons.classpath.Classpath;
import org.springframework.tooling.jdt.ls.commons.classpath.Classpath.CPE;
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;
import org.springsource.ide.eclipse.commons.frameworks.test.util.Asserter;
import junit.framework.AssertionFailedError;
public class ClasspathListenerHandlerTest {
private MockClasspathCache classpaths = new MockClasspathCache();
private ReusableClasspathListenerHandler service = new ReusableClasspathListenerHandler(classpaths);
@Test public void classpathIsSentForExistingProject() throws Exception {
String projectName = "classpath-test-simple-java-project";
IProject project = createTestProject(projectName);
File loc = project.getLocation().toFile();
service.addClasspathListener(classpaths.commandId);
ACondition.waitFor("Project with classpath to appear", Duration.ofSeconds(50), () -> {
Classpath cp = classpaths.getFor(loc).classpath;
@@ -69,7 +73,7 @@ public class ClasspathListenerHandlerTest {
@Test public void classpathIsSentForNewProject_and_removedForDeletedProject() throws Exception {
service.addClasspathListener(classpaths.commandId);
String projectName = "classpath-test-simple-java-project";
IProject project = createTestProject(projectName);
File loc = project.getLocation().toFile();
@@ -84,15 +88,37 @@ public class ClasspathListenerHandlerTest {
Info cp = classpaths.getFor(loc);
assertNull(cp);
});
}
@Test public void classpathIsRemovedWhenProjectDeletedFromFileSystem() throws Exception {
String projectName = "classpath-test-simple-java-project";
IProject project = createTestProject(projectName);
File loc = project.getLocation().toFile();
service.addClasspathListener(classpaths.commandId);
ACondition.waitFor("Project with classpath to appear", Duration.ofSeconds(50), () -> {
Classpath cp = classpaths.getFor(loc).classpath;
assertTrue(cp.getEntries().stream().filter(cpe -> Classpath.isSource(cpe)).count()==1); //has 1 source entry
assertClasspath(cp, cp.getEntries().stream().filter(cpe -> Classpath.isBinary(cpe) && cpe.isSystem()).count()>=1); //has some system libraries
});
FileUtils.deleteQuietly(loc);
safe(() -> project.refreshLocal(IResource.DEPTH_INFINITE, null));
ACondition.waitFor("Project to disapear", Duration.ofSeconds(50), () -> {
Info cp = classpaths.getFor(loc);
assertNull(cp);
});
}
///////////// harness stuff below ///////////////////////////////////////////////
@Rule public TemporaryFolder tmp = new TemporaryFolder();
static class Info {
public final String name;
public final Classpath classpath;
private Info(String name, Classpath cp) {
super();
this.name = name;
@@ -103,9 +129,9 @@ public class ClasspathListenerHandlerTest {
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)) {
@@ -131,17 +157,17 @@ public class ClasspathListenerHandlerTest {
public void dispose() throws Exception {
service.removeClasspathListener(commandId);
deleteAllProjects();
}
}
@After
public void tearDown() throws Exception {
classpaths.dispose();
deleteAllProjects();
assertTrue(service.hasNoActiveSubscriptions());
}
private static void assertClasspath(Classpath cp, boolean b) {
if (!b) {
StringBuilder buf = new StringBuilder();
@@ -154,40 +180,56 @@ public class ClasspathListenerHandlerTest {
}
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());
File testProjectSourceLocation = new File(FileLocator.toFileURL(Platform.getBundle("org.springframework.tooling.jdt.ls.commons.test").getEntry("test-projects/"+name)).toURI());
File testProjectLocation = tmp.newFolder(name);
FileUtils.copyDirectory(testProjectSourceLocation, testProjectLocation);
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;
}
}
public static void deleteAllProjects() throws Exception {
CompletableFuture<Void> done = new CompletableFuture<Void>();
WorkspaceJob job = new WorkspaceJob("Delete projects") {
@Override public IStatus runInWorkspace(IProgressMonitor arg0) throws CoreException {
try {
ResourcesPlugin.getWorkspace().getRuleFactory().buildRule();
IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
for (IProject project : allProjects) {
project.refreshLocal(IResource.DEPTH_INFINITE, null);
project.close(null);
project.delete(false, true, new NullProgressMonitor());
}
ACondition.waitFor("Deleting all projects", Duration.ofMinutes(1), () -> {
ResourcesPlugin.getWorkspace().getRuleFactory().buildRule();
IProject[] allProjects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
for (IProject project : allProjects) {
project.refreshLocal(IResource.DEPTH_INFINITE, null);
safe(() -> project.close(null));
project.delete(false, true, new NullProgressMonitor());
assertFalse(project.exists());
}
});
done.complete(null);
} catch (Throwable e) {
done.completeExceptionally(e);
}
return Status.OK_STATUS;
}
};
job.setRule(ResourcesPlugin.getWorkspace().getRuleFactory().buildRule());
job.schedule();
done.get();
}
private static void safe(Asserter doit) {
try {
doit.execute();
} catch (Throwable e) {
System.err.println("Ignore exception: "+e.getMessage());
}
}
}

View File

@@ -17,6 +17,7 @@ import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
@@ -54,6 +55,26 @@ public class ReusableClasspathListenerHandler {
}
}
private boolean projectExists(IJavaProject jp) {
//We can't really deal with projects that don't exist in disk. So using this more strict 'exists' check
//makes sure anything that looks like it doesn't exist on disk is treated as if it simply doesn't exist
//at all. This kind of addresses a issue caused by Eclipse's idiotic behavior when it comes to deleting
//a project's files from the file system... Eclipse recreates a 'vanilla' project in the workspace,
//simply refusing to accept the fact that the project is actually gone.
if (jp.exists()) {
try {
URI loc = getProjectLocation(jp);
if (loc!=null) {
File f = new File(loc);
return f.isDirectory();
}
} catch (Exception e) {
//Something bogus about this project... so just pretend it doesn't exist.
}
}
return false;
}
class Subscribptions {
@@ -86,7 +107,7 @@ public class ReusableClasspathListenerHandler {
if (projectLoc==null) {
Logger.log("Could not send event for project because no project location: "+jp.getElementName());
} else {
boolean exsits = jp.exists();
boolean exsits = projectExists(jp);
boolean open = true; // WARNING: calling is jp.isOpen is unreliable and subject to race condition. After a POST_CHAGE project open event
// this should be true but it typically is not unless you wait for some time. No idea how you would know
// how long you should wait (200ms is not enough, and that seems pretty long). Isn't it kind of the point

View File

@@ -220,12 +220,12 @@
<key>1</key>
<dict>
<key>name</key>
<string>punctuation.definition.block.scalar.literal.yaml</string>
<string>keyword.control.flow.block-scalar.literal.yaml</string>
</dict>
<key>2</key>
<dict>
<key>name</key>
<string>punctuation.definition.block.scalar.folded.yaml</string>
<string>keyword.control.flow.block-scalar.folded.yaml</string>
</dict>
<key>3</key>
<dict>
@@ -235,7 +235,7 @@
<key>4</key>
<dict>
<key>name</key>
<string>support.other.chomping-indicator.yaml</string>
<string>storage.modifier.chomping-indicator.yaml</string>
</dict>
<key>5</key>
<dict>
@@ -271,7 +271,7 @@
<key>block-sequence</key>
<dict>
<key>match</key>
<string>(-)( |\t|$)</string>
<string>(-)(?!\S)</string>
<key>name</key>
<string>punctuation.definition.block.sequence.item.yaml</string>
</dict>
@@ -370,8 +370,8 @@
(?:[ \t]+
((?:!(?:[0-9A-Za-z\-]*!)?))
(?:[ \t]+ (
! (?x: %\p{XDigit}{2} | [0-9A-Za-z\-#;/?:@&amp;=+$,_.!~*'()\[\]] )*
| (?![,!\[\]{}]) (?x: %\p{XDigit}{2} | [0-9A-Za-z\-#;/?:@&amp;=+$,_.!~*'()\[\]] )+
! (?x: %[0-9A-Fa-f]{2} | [0-9A-Za-z\-#;/?:@&amp;=+$,_.!~*'()\[\]] )*
| (?![,!\[\]{}]) (?x: %[0-9A-Fa-f]{2} | [0-9A-Za-z\-#;/?:@&amp;=+$,_.!~*'()\[\]] )+
)
)?
)?
@@ -1124,8 +1124,8 @@
<string>(?x)
\G
(?:
! &lt; (?: %\p{XDigit}{2} | [0-9A-Za-z\-#;/?:@&amp;=+$,_.!~*'()\[\]] )+ &gt;
| (?:!(?:[0-9A-Za-z\-]*!)?) (?: %\p{XDigit}{2} | [0-9A-Za-z\-#;/?:@&amp;=+$_.~*'()] )+
! &lt; (?: %[0-9A-Fa-f]{2} | [0-9A-Za-z\-#;/?:@&amp;=+$,_.!~*'()\[\]] )+ &gt;
| (?:!(?:[0-9A-Za-z\-]*!)?) (?: %[0-9A-Fa-f]{2} | [0-9A-Za-z\-#;/?:@&amp;=+$_.~*'()] )+
| !
)
(?=\ |\t|$)