blob: 2e779aa7794cf6e14ed267d6949f1716b98c7aa8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016, 2018 Rogue Wave Software Inc. and others.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Michał Niewrzał (Rogue Wave Software Inc.) - initial implementation
* Mickael Istria (Red Hat Inc.) - added test for Run config
* Martin Lippert (Pivotal Inc.) - added tests for multi-root folders, wrapper re-use, and more
*******************************************************************************/
package org.eclipse.lsp4e.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.internal.core.IInternalDebugCoreConstants;
import org.eclipse.debug.internal.core.Preferences;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.lsp4e.LSPEclipseUtils;
import org.eclipse.lsp4e.LanguageServerPlugin;
import org.eclipse.lsp4e.LanguageServerWrapper;
import org.eclipse.lsp4e.LanguageServiceAccessor;
import org.eclipse.lsp4e.LanguageServiceAccessor.LSPDocumentInfo;
import org.eclipse.lsp4e.tests.mock.MockLanguageServer;
import org.eclipse.lsp4e.tests.mock.MockLanguageServerMultiRootFolders;
import org.eclipse.lsp4j.HoverOptions;
import org.eclipse.lsp4j.ServerCapabilities;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageServer;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.IDE;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.ITextEditor;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
public class LanguageServiceAccessorTest {
@Rule public AllCleanRule clear = new AllCleanRule();
private IProject project;
@Before
public void setUp() throws CoreException {
project = TestUtils.createProject("LanguageServiceAccessorTest" + System.currentTimeMillis());
}
@Test
public void testGetLSPDocumentInfoForInvalidDocument() {
Collection<LSPDocumentInfo> infos = LanguageServiceAccessor.getLSPDocumentInfosFor(new Document(), null);
assertTrue(infos.isEmpty());
}
@Test
public void testGetLSPDocumentInfoForInvalidTextEditor() throws CoreException, InvocationTargetException {
IFile testFile = TestUtils.createFile(project, "not_associated_with_ls.abc", "");
ITextViewer textViewer = TestUtils.openTextViewer(testFile);
Collection<LSPDocumentInfo> infos = LanguageServiceAccessor.getLSPDocumentInfosFor(textViewer.getDocument(), capabilities -> Boolean.TRUE);
assertTrue(infos.isEmpty());
}
@Test
public void testGetLanguageServerInvalidFile() throws Exception {
IFile testFile = TestUtils.createFile(project, "not_associated_with_ls.abc", "");
Collection<LanguageServer> servers = new ArrayList<>();
for (CompletableFuture<LanguageServer> future :LanguageServiceAccessor.getInitializedLanguageServers(testFile, capabilites -> Boolean.TRUE)){
servers.add(future.get(1, TimeUnit.SECONDS));
}
assertTrue(servers.isEmpty());
}
@Test
public void testLSAsExtension() throws Exception {
IFile testFile = TestUtils.createFile(project, "shouldUseExtension.lspt", "");
LanguageServer info = LanguageServiceAccessor
.getInitializedLanguageServers(testFile, capabilites -> Boolean.TRUE).iterator().next()
.get(1, TimeUnit.SECONDS);
assertNotNull(info);
}
@Test
public void testLSAsRunConfiguration() throws Exception {
IFile testFile = TestUtils.createFile(project, "shouldUseRunConfiguration.lspt2", "");
LanguageServer info = LanguageServiceAccessor
.getInitializedLanguageServers(testFile, capabilites -> Boolean.TRUE).iterator().next()
.get(1, TimeUnit.SECONDS);
assertNotNull(info);
}
@Test
public void testLSAsExtensionForDifferentLanguageId() throws Exception {
IFile testFile = TestUtils.createFile(project, "shouldUseExtension.lspt-different", ""); @NonNull
Collection<LanguageServerWrapper> lsWrappers = LanguageServiceAccessor.getLSWrappers(testFile,
capabilites -> Boolean.TRUE);
assertEquals(1, lsWrappers.size());
LanguageServerWrapper wrapper = lsWrappers.iterator().next();
assertNotNull(wrapper);
IContentType contentType = Platform.getContentTypeManager().getContentType("org.eclipse.lsp4e.test.content-type-different");
assertEquals("differentLanguageId", wrapper.getLanguageId(new IContentType[] {contentType}));
}
@Test
public void testGetLSWrappersInitializationFailed() throws Exception {
IFile testFile = TestUtils.createFile(project, "fileWithFailedServer.lsptWithException", "");
Collection<LanguageServerWrapper> wrappers = LanguageServiceAccessor.getLSWrappers(testFile,
capabilites -> Boolean.TRUE);
assertEquals(1, wrappers.size());
}
@Test
public void testReuseSameLSforMultiContentType() throws Exception {
IFile testFile1 = TestUtils.createUniqueTestFile(project, "");
IFile testFile2 = TestUtils.createUniqueTestFileMultiLS(project, "");
// wrap in HashSet as a workaround of https://github.com/eclipse/lsp4j/issues/106
Collection<LanguageServer> file1LanguageServers = new ArrayList<>();
for (CompletableFuture<LanguageServer> future : LanguageServiceAccessor.getInitializedLanguageServers(testFile1,
capabilites -> Boolean.TRUE)) {
file1LanguageServers.add(future.get(1, TimeUnit.SECONDS));
}
assertEquals(1, file1LanguageServers.size());
LanguageServer file1LS = file1LanguageServers.iterator().next();
Collection<LanguageServer> file2LanguageServers = new ArrayList<>();
for (CompletableFuture<LanguageServer> future : LanguageServiceAccessor.getInitializedLanguageServers(testFile2,
capabilites -> Boolean.TRUE)) {
file2LanguageServers.add(future.get(1, TimeUnit.SECONDS));
}
assertEquals(2, file2LanguageServers.size());
assertTrue(file2LanguageServers.contains(file1LS)); // LS from file1 is reused
assertEquals("Not right amount of language servers bound to project", 2, LanguageServiceAccessor.getLanguageServers(project, c -> Boolean.TRUE).size());
}
@Test
public void testGetOnlyRunningLanguageServers() throws Exception {
Display display = Display.getCurrent();
IFile testFile1 = TestUtils.createUniqueTestFile(project, "");
IFile testFile2 = TestUtils.createUniqueTestFile(project, "lspt-different", "");
IEditorPart editor1 = TestUtils.openEditor(testFile1);
IEditorPart editor2 = TestUtils.openEditor(testFile2);
LanguageServiceAccessor.getInitializedLanguageServers(testFile1, capabilities -> Boolean.TRUE).iterator()
.next();
LanguageServiceAccessor.getInitializedLanguageServers(testFile2, capabilities -> Boolean.TRUE).iterator()
.next();
List<LanguageServer> runningServers = LanguageServiceAccessor.getActiveLanguageServers(capabilities -> Boolean.TRUE);
assertEquals(2, runningServers.size());
((AbstractTextEditor) editor1).close(false);
((AbstractTextEditor) editor2).close(false);
new LSDisplayHelper(() -> LanguageServiceAccessor.getActiveLanguageServers(capabilities -> Boolean.TRUE).size() == 0)
.waitForCondition(display, 5000);
assertEquals(0, LanguageServiceAccessor.getActiveLanguageServers(capabilities -> Boolean.TRUE).size());
editor1 = TestUtils.openEditor(testFile1);
LanguageServiceAccessor.getInitializedLanguageServers(testFile1, capabilities -> Boolean.TRUE).iterator()
.next();
new LSDisplayHelper(() -> LanguageServiceAccessor.getActiveLanguageServers(capabilities -> Boolean.TRUE).size() == 1)
.waitForCondition(display, 5000);
assertEquals(1, LanguageServiceAccessor.getActiveLanguageServers(capabilities -> Boolean.TRUE).size());
}
@Test
public void testCreateNewLSAfterInitialProjectGotDeleted() throws Exception {
IFile testFile1 = TestUtils.createUniqueTestFile(project, "");
TestUtils.openEditor(testFile1);
LanguageServiceAccessor.getInitializedLanguageServers(testFile1, capabilities -> Boolean.TRUE).iterator()
.next();
new LSDisplayHelper(() -> MockLanguageServer.INSTANCE.isRunning()).waitForCondition(Display.getCurrent(), 5000,
300);
Collection<LanguageServerWrapper> wrappers = LanguageServiceAccessor.getLSWrappers(testFile1,
c -> Boolean.TRUE);
LanguageServerWrapper wrapper1 = wrappers.iterator().next();
assertTrue(wrapper1.isActive());
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().closeAllEditors(false);
new LSDisplayHelper(() -> !MockLanguageServer.INSTANCE.isRunning()).waitForCondition(Display.getCurrent(), 5000,
300);
project.delete(true, true, new NullProgressMonitor());
project = TestUtils.createProject("LanguageServiceAccessorTest2" + System.currentTimeMillis());
IFile testFile2 = TestUtils.createUniqueTestFile(project, "");
TestUtils.openEditor(testFile2);
LanguageServiceAccessor.getInitializedLanguageServers(testFile2, capabilities -> Boolean.TRUE).iterator()
.next();
new LSDisplayHelper(() -> MockLanguageServer.INSTANCE.isRunning()).waitForCondition(Display.getCurrent(), 5000,
300);
wrappers = LanguageServiceAccessor.getLSWrappers(testFile2, c -> Boolean.TRUE);
LanguageServerWrapper wrapper2 = wrappers.iterator().next();
assertTrue(wrapper2.isActive());
assertTrue(wrapper1 != wrapper2);
}
@Test
public void testReuseMultirootFolderLSAfterInitialProjectGotDeleted() throws Exception {
IFile testFile1 = TestUtils.createUniqueTestFile(project, "lsptWithMultiRoot", "");
TestUtils.openEditor(testFile1);
LanguageServiceAccessor.getInitializedLanguageServers(testFile1, capabilities -> Boolean.TRUE).iterator()
.next();
new LSDisplayHelper(() -> MockLanguageServerMultiRootFolders.INSTANCE.isRunning())
.waitForCondition(Display.getCurrent(), 5000, 300);
Collection<LanguageServerWrapper> wrappers = LanguageServiceAccessor.getLSWrappers(testFile1,
c -> Boolean.TRUE);
LanguageServerWrapper wrapper1 = wrappers.iterator().next();
assertTrue(wrapper1.isActive());
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().closeAllEditors(false);
new LSDisplayHelper(() -> !MockLanguageServerMultiRootFolders.INSTANCE.isRunning())
.waitForCondition(Display.getCurrent(), 5000, 300);
project.delete(true, true, new NullProgressMonitor());
project = TestUtils.createProject("LanguageServiceAccessorTest2" + System.currentTimeMillis());
IFile testFile2 = TestUtils.createUniqueTestFile(project, "lsptWithMultiRoot", "");
TestUtils.openEditor(testFile2);
LanguageServiceAccessor.getInitializedLanguageServers(testFile2, capabilities -> Boolean.TRUE).iterator()
.next();
new LSDisplayHelper(() -> MockLanguageServerMultiRootFolders.INSTANCE.isRunning())
.waitForCondition(Display.getCurrent(), 5000, 300);
wrappers = LanguageServiceAccessor.getLSWrappers(testFile2, c -> Boolean.TRUE);
LanguageServerWrapper wrapper2 = wrappers.iterator().next();
assertTrue(wrapper2.isActive());
assertTrue(wrapper1 == wrapper2);
}
@Test
public void testDontRestartUnrelatedLSForFileFromSameProject() throws Exception {
IFile testFile1 = TestUtils.createUniqueTestFile(project, "");
IFile testFile2 = TestUtils.createUniqueTestFile(project, "lspt-different", "");
Collection<LanguageServerWrapper> wrappers1 = LanguageServiceAccessor.getLSWrappers(testFile1,
c -> Boolean.TRUE);
assertEquals(1, wrappers1.size());
LanguageServerWrapper wrapper1 = wrappers1.iterator().next();
assertTrue(wrapper1.isActive());
wrapper1.disconnect(testFile1.getLocationURI());
assertFalse(wrapper1.isActive());
Collection<LanguageServerWrapper> wrappers2 = LanguageServiceAccessor.getLSWrappers(testFile2,
c -> Boolean.TRUE);
assertEquals(1, wrappers2.size());
LanguageServerWrapper wrapper2 = wrappers2.iterator().next();
assertTrue(wrapper2.isActive());
// make sure the language server for testFile1 (which is unrelated to testFile2 is not started again)
assertFalse(wrapper1.isActive());
wrapper2.disconnect(testFile2.getLocationURI());
}
@Test
public void testLanguageServerHierarchy_moreSpecializedFirst() throws Exception {
// file with a content-type and a parent, each associated to one LS
IFile testFile = TestUtils.createUniqueTestFile(project, "lsptchild", "");
@NonNull Collection<LanguageServerWrapper> servers = LanguageServiceAccessor.getLSWrappers(testFile,
c -> Boolean.TRUE);
Iterator<LanguageServerWrapper> iterator = servers.iterator();
assertEquals("org.eclipse.lsp4e.test.server2", iterator.next().serverDefinition.id);
assertEquals("org.eclipse.lsp4e.test.server", iterator.next().serverDefinition.id);
}
@Test
public void testLanguageServerHierarchy_parentContentTypeUsed() throws Exception {
// file with a content-type whose parent (only) is associated to one LS
IFile testFile = TestUtils.createUniqueTestFile(project, "lsptchildNoLS", "");
@NonNull Collection<LanguageServerWrapper> servers = LanguageServiceAccessor.getLSWrappers(testFile,
c -> Boolean.TRUE);
Iterator<LanguageServerWrapper> iterator = servers.iterator();
assertEquals("org.eclipse.lsp4e.test.server", iterator.next().serverDefinition.id);
assertFalse("Should only be a single LS", iterator.hasNext());
}
@Test
public void testLanguageServerEnablement() throws Exception {
LanguageServerPlugin.getDefault().getPreferenceStore().setValue(
"org.eclipse.lsp4e.test.server.disable" + "/" + "org.eclipse.lsp4e.test.content-type-disabled",
"false");
IFile disabledFile = TestUtils.createUniqueTestFile(project, "lspt-disabled", "");
IFile enabledFile = TestUtils.createUniqueTestFile(project, "lspt-enabled", "");
LanguageServiceAccessor.getLSWrappers(disabledFile, capabilities -> true).stream().forEach(
wrapper -> assertFalse(wrapper.serverDefinition.id.equals("org.eclipse.lsp4e.test.server.disable")));
assertTrue(LanguageServiceAccessor.getLSWrappers(enabledFile, capabilities -> true).stream()
.filter(wrapper -> wrapper.serverDefinition.id.equals("org.eclipse.lsp4e.test.server.disable"))
.findFirst().isPresent());
LanguageServerPlugin.getDefault().getPreferenceStore().setValue(
"org.eclipse.lsp4e.test.server.disable" + "/" + "org.eclipse.lsp4e.test.content-type-disabled",
"true");
assertTrue(LanguageServiceAccessor.getLSWrappers(disabledFile, capabilities -> true).stream()
.filter(wrapper -> wrapper.serverDefinition.id.equals("org.eclipse.lsp4e.test.server.disable"))
.findFirst().isPresent());
assertTrue(LanguageServiceAccessor.getLSWrappers(enabledFile, capabilities -> true).stream()
.filter(wrapper -> wrapper.serverDefinition.id.equals("org.eclipse.lsp4e.test.server.disable"))
.findFirst().isPresent());
}
@Test
public void testLanguageServerEnablementTester() throws Exception {
IFile file = TestUtils.createUniqueTestFile(project, "lspt-tester", "");
assertTrue(LanguageServiceAccessor.getLSWrappers(file, capabilities -> true).isEmpty());
MappingEnablementTester.enabled = true;
Collection<LanguageServerWrapper> wrappers = LanguageServiceAccessor.getLSWrappers(file, capabilities -> true);
assertEquals(1, wrappers.size());
assertEquals("org.eclipse.lsp4e.test.server.disable", wrappers.iterator().next().serverDefinition.id);
}
@Test
public void testStatusHandlerLSAsRunConfiguration() throws Exception {
// test which checks that status handler preferences is kept after the launch is
// done.
IFile testFile = TestUtils.createFile(project, "shouldUseRunConfiguration.lspt2", "");
// Test with default status handler (see DebugPlugin#getStatusHandler)
boolean oldStatusHandler = getStatusHandler();
LanguageServiceAccessor.getLanguageServers(LSPEclipseUtils.getDocument(testFile), null).get(1, TimeUnit.SECONDS);
assertEquals(getStatusHandler(), oldStatusHandler);
// Test with status handler set to false
setStatusHandler(false);
oldStatusHandler = getStatusHandler();
LanguageServiceAccessor.getLanguageServers(LSPEclipseUtils.getDocument(testFile), null).get(1, TimeUnit.SECONDS);
assertEquals(getStatusHandler(), false);
// Test with status handler set to true
setStatusHandler(true);
oldStatusHandler = getStatusHandler();
LanguageServiceAccessor.getLanguageServers(LSPEclipseUtils.getDocument(testFile), null).get(1, TimeUnit.SECONDS);
assertEquals(getStatusHandler(), true);
}
@Test
public void testLSforExternalThenLocalFile() throws Exception {
File local = File.createTempFile("testLSforExternalThenLocalFile", ".lspt");
try {
ITextEditor editor = (ITextEditor) IDE.openEditorOnFileStore(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(), EFS.getStore(local.toURI()));
Assert.assertEquals(1, LanguageServiceAccessor.getLanguageServers(
TestUtils.getTextViewer(editor).getDocument(), this::hasHoverCapabilities).get().size());
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().closeAllEditors(false);
// opening another file should either reuse the LS or spawn another one, but not
// both
Assert.assertEquals(1,
LanguageServiceAccessor.getLanguageServers(
TestUtils.openTextViewer(TestUtils.createUniqueTestFile(project, "")).getDocument(),
this::hasHoverCapabilities).get().size());
} finally {
Files.deleteIfExists(local.toPath());
}
}
private boolean hasHoverCapabilities(ServerCapabilities capabilities) {
Either<Boolean, HoverOptions> hoverProvider = capabilities.getHoverProvider();
if(hoverProvider.isLeft()) {
return hoverProvider.getLeft();
} else {
return hoverProvider.getRight() != null;
}
}
@Test
public void testSingletonLS() throws Exception {
IFile testFile1 = TestUtils.createFile(project, "shouldUseSingletonLS.lsp-singletonLS", "");
IProject project2 = TestUtils.createProject("project2");
IFile testFile2 = TestUtils.createFile(project2, "shouldUseSingletonLS2.lsp-singletonLS", "");
@NonNull CompletableFuture<List<@NonNull LanguageServer>> languageServers = LanguageServiceAccessor.getLanguageServers(LSPEclipseUtils.getDocument(testFile1), cap -> true);
@NonNull CompletableFuture<List<@NonNull LanguageServer>> languageServers2 = LanguageServiceAccessor.getLanguageServers(LSPEclipseUtils.getDocument(testFile2), cap -> true);
Assert.assertEquals(1, languageServers.get().size());
Assert.assertEquals(languageServers.get(), languageServers2.get());
}
private static boolean getStatusHandler() {
return Platform.getPreferencesService().getBoolean(DebugPlugin.getUniqueIdentifier(),
IInternalDebugCoreConstants.PREF_ENABLE_STATUS_HANDLERS, true, null);
}
/**
* Update the the status handler preferences
*
* @param enabled
* the status handler preferences
*/
private static void setStatusHandler(boolean enabled) {
Preferences.setBoolean(DebugPlugin.getUniqueIdentifier(),
IInternalDebugCoreConstants.PREF_ENABLE_STATUS_HANDLERS, enabled, null);
}
}