blob: f62d93291ad880ed47cfa10f4a5b3c5ea6c7b874 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2016 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
******************************************************************************/
package org.eclipse.lsp4j.jsonrpc.test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.logging.Level;
import org.eclipse.lsp4j.jsonrpc.Endpoint;
import org.eclipse.lsp4j.jsonrpc.JsonRpcException;
import org.eclipse.lsp4j.jsonrpc.MessageConsumer;
import org.eclipse.lsp4j.jsonrpc.RemoteEndpoint;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.jsonrpc.messages.Message;
import org.eclipse.lsp4j.jsonrpc.messages.MessageIssue;
import org.eclipse.lsp4j.jsonrpc.messages.NotificationMessage;
import org.eclipse.lsp4j.jsonrpc.messages.RequestMessage;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseError;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseErrorCode;
import org.eclipse.lsp4j.jsonrpc.messages.ResponseMessage;
import org.junit.Assert;
import org.junit.Test;
public class RemoteEndpointTest {
private static final long TIMEOUT = 2000;
static class TestEndpoint implements Endpoint {
List<NotificationMessage> notifications = new ArrayList<>();
Map<RequestMessage, CompletableFuture<Object>> requests = new LinkedHashMap<>();
public void notify(String method, Object parameter) {
notifications.add(init(new NotificationMessage(), it -> {
it.setMethod(method);
it.setParams(parameter);
}));
}
@Override
public CompletableFuture<Object> request(String method, Object parameter) {
CompletableFuture<Object> completableFuture = new CompletableFuture<Object>();
requests.put(init(new RequestMessage(), it -> {
it.setMethod(method);
it.setParams(parameter);
}), completableFuture);
return completableFuture;
}
}
static class TestMessageConsumer implements MessageConsumer {
List<Message> messages = new ArrayList<>();
@Override
public void consume(Message message) {
messages.add(message);
}
}
static <T> T init(T value, Consumer<T> initializer) {
initializer.accept(value);
return value;
}
@Test
public void testNotification() {
TestEndpoint endp = new TestEndpoint();
TestMessageConsumer consumer = new TestMessageConsumer();
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp);
endpoint.consume(init(new NotificationMessage(), it -> {
it.setMethod("foo");
it.setParams("myparam");
}));
NotificationMessage notificationMessage = endp.notifications.get(0);
assertEquals("foo", notificationMessage.getMethod());
assertEquals("myparam", notificationMessage.getParams());
assertTrue(consumer.messages.isEmpty());
}
@Test
public void testRequest1() {
TestEndpoint endp = new TestEndpoint();
TestMessageConsumer consumer = new TestMessageConsumer();
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp);
endpoint.consume(init(new RequestMessage(), it -> {
it.setId("1");
it.setMethod("foo");
it.setParams("myparam");
}));
Entry<RequestMessage, CompletableFuture<Object>> entry = endp.requests.entrySet().iterator().next();
entry.getValue().complete("success");
assertEquals("foo", entry.getKey().getMethod());
assertEquals("myparam", entry.getKey().getParams());
ResponseMessage responseMessage = (ResponseMessage) consumer.messages.get(0);
assertEquals("success", responseMessage.getResult());
assertEquals(Either.forLeft("1"), responseMessage.getRawId());
}
@Test
public void testRequest2() {
TestEndpoint endp = new TestEndpoint();
TestMessageConsumer consumer = new TestMessageConsumer();
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp);
endpoint.consume(init(new RequestMessage(), it -> {
it.setId(1);
it.setMethod("foo");
it.setParams("myparam");
}));
Entry<RequestMessage, CompletableFuture<Object>> entry = endp.requests.entrySet().iterator().next();
entry.getValue().complete("success");
assertEquals("foo", entry.getKey().getMethod());
assertEquals("myparam", entry.getKey().getParams());
ResponseMessage responseMessage = (ResponseMessage) consumer.messages.get(0);
assertEquals("success", responseMessage.getResult());
assertEquals(Either.forRight(1), responseMessage.getRawId());
}
@Test
public void testHandleRequestIssues() {
TestEndpoint endp = new TestEndpoint();
TestMessageConsumer consumer = new TestMessageConsumer();
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp);
endpoint.handle(init(new RequestMessage(), it -> {
it.setId("1");
it.setMethod("foo");
it.setParams("myparam");
}), Collections.singletonList(new MessageIssue("bar")));
ResponseMessage responseMessage = (ResponseMessage) consumer.messages.get(0);
assertNotNull(responseMessage.getError());
assertEquals("bar", responseMessage.getError().getMessage());
}
@Test
public void testCancellation() {
TestEndpoint endp = new TestEndpoint();
TestMessageConsumer consumer = new TestMessageConsumer();
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp);
endpoint.consume(init(new RequestMessage(), it -> {
it.setId("1");
it.setMethod("foo");
it.setParams("myparam");
}));
Entry<RequestMessage, CompletableFuture<Object>> entry = endp.requests.entrySet().iterator().next();
entry.getValue().cancel(true);
ResponseMessage message = (ResponseMessage) consumer.messages.get(0);
assertNotNull(message);
ResponseError error = message.getError();
assertNotNull(error);
assertEquals(error.getCode(), ResponseErrorCode.RequestCancelled.getValue());
assertEquals(error.getMessage(), "The request (id: 1, method: 'foo') has been cancelled");
}
@Test
public void testExceptionInEndpoint() {
LogMessageAccumulator logMessages = new LogMessageAccumulator();
try {
// Don't show the exception in the test execution log
logMessages.registerTo(RemoteEndpoint.class);
TestEndpoint endp = new TestEndpoint() {
@Override
public CompletableFuture<Object> request(String method, Object parameter) {
throw new RuntimeException("BAAZ");
}
};
TestMessageConsumer consumer = new TestMessageConsumer();
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp);
endpoint.consume(init(new RequestMessage(), it -> {
it.setId("1");
it.setMethod("foo");
it.setParams("myparam");
}));
ResponseMessage response = (ResponseMessage) consumer.messages.get(0);
assertEquals("Internal error.", response.getError().getMessage());
assertEquals(ResponseErrorCode.InternalError.getValue(), response.getError().getCode());
String exception = (String) response.getError().getData();
String expected = "java.lang.RuntimeException: BAAZ\n\tat org.eclipse.lsp4j.jsonrpc.test.RemoteEndpointTest";
assertEquals(expected, exception.replaceAll("\\r", "").substring(0, expected.length()));
} finally {
logMessages.unregister();
}
}
@Test
public void testExceptionInConsumer() throws Exception {
TestEndpoint endp = new TestEndpoint();
MessageConsumer consumer = message -> {
throw new RuntimeException("BAAZ");
};
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp);
CompletableFuture<Object> future = endpoint.request("foo", "myparam");
future.whenComplete((result, exception) -> {
assertNull(result);
assertNotNull(exception);
assertEquals("BAAZ", exception.getMessage());
});
try {
future.get(TIMEOUT, TimeUnit.MILLISECONDS);
Assert.fail("Expected an ExecutionException.");
} catch (ExecutionException exception) {
assertEquals("java.lang.RuntimeException: BAAZ", exception.getMessage());
}
}
@Test
public void testExceptionInCompletableFuture() throws Exception {
TestEndpoint endp = new TestEndpoint();
TestMessageConsumer consumer = new TestMessageConsumer();
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp);
CompletableFuture<Object> future = endpoint.request("foo", "myparam");
CompletableFuture<Void> chained = future.thenAccept(result -> {
throw new RuntimeException("BAAZ");
});
endpoint.consume(init(new ResponseMessage(), it -> {
it.setId("1");
it.setResult("Bar");
}));
try {
chained.get(TIMEOUT, TimeUnit.MILLISECONDS);
Assert.fail("Expected an ExecutionException.");
} catch (ExecutionException exception) {
assertEquals("java.lang.RuntimeException: BAAZ", exception.getMessage());
}
}
@Test
public void testExceptionInOutputStream() throws Exception {
LogMessageAccumulator logMessages = new LogMessageAccumulator();
try {
logMessages.registerTo(RemoteEndpoint.class);
TestEndpoint endp = new TestEndpoint();
MessageConsumer consumer = new MessageConsumer() {
@Override
public void consume(Message message) throws JsonRpcException {
throw new JsonRpcException(new SocketException("Permission denied: connect"));
}
};
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp);
endpoint.notify("foo", null);
logMessages.await(Level.WARNING, "Failed to send notification message.");
} finally {
logMessages.unregister();
}
}
@Test
public void testOutputStreamClosed() throws Exception {
LogMessageAccumulator logMessages = new LogMessageAccumulator();
try {
logMessages.registerTo(RemoteEndpoint.class);
TestEndpoint endp = new TestEndpoint();
MessageConsumer consumer = new MessageConsumer() {
@Override
public void consume(Message message) throws JsonRpcException {
throw new JsonRpcException(new SocketException("Socket closed"));
}
};
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp);
endpoint.notify("foo", null);
logMessages.await(Level.INFO, "Failed to send notification message.");
} finally {
logMessages.unregister();
}
}
@Test
public void testExceptionHandlerMisbehaving1() {
LogMessageAccumulator logMessages = new LogMessageAccumulator();
try {
// Don't show the exception in the test execution log
logMessages.registerTo(RemoteEndpoint.class);
TestEndpoint endp = new TestEndpoint() {
@Override
public CompletableFuture<Object> request(String method, Object parameter) {
throw new RuntimeException("BAAZ");
}
};
TestMessageConsumer consumer = new TestMessageConsumer();
// Misbehaving exception handler that returns null
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp, (e) -> null);
endpoint.consume(init(new RequestMessage(), it -> {
it.setId("1");
it.setMethod("foo");
it.setParams("myparam");
}));
assertEquals("Check some response received", 1, consumer.messages.size());
ResponseMessage response = (ResponseMessage) consumer.messages.get(0);
assertEquals(ResponseErrorCode.InternalError.getValue(), response.getError().getCode());
} finally {
logMessages.unregister();
}
}
static class TestMessageConsumer2 implements MessageConsumer {
boolean sentException = false;
List<Message> messages = new ArrayList<>();
@Override
public void consume(Message message) {
if (sentException) {
messages.add(message);
} else {
// throw an exception only for the first message
sentException = true;
throw new RuntimeException("Exception in consumer");
}
}
}
@Test
public void testExceptionHandlerMisbehaving2() throws Exception {
LogMessageAccumulator logMessages = new LogMessageAccumulator();
try {
// Don't show the exception in the test execution log
logMessages.registerTo(RemoteEndpoint.class);
TestEndpoint endp = new TestEndpoint() {
@Override
public CompletableFuture<Object> request(String method, Object parameter) {
return CompletableFuture.supplyAsync(() -> "baz");
}
};
TestMessageConsumer2 consumer = new TestMessageConsumer2();
// Misbehaving exception handler that returns null
RemoteEndpoint endpoint = new RemoteEndpoint(consumer, endp, (e) -> null);
endpoint.consume(init(new RequestMessage(), it -> {
it.setId("1");
it.setMethod("foo");
it.setParams("myparam");
}));
long timeout = System.currentTimeMillis() + TIMEOUT;
while (consumer.messages.isEmpty()) {
Thread.sleep(20);
if (System.currentTimeMillis() > timeout) {
fail("Timedout waiting for messages");
}
}
assertEquals("Check some response received", 1, consumer.messages.size());
ResponseMessage response = (ResponseMessage) consumer.messages.get(0);
assertNotNull("Check response has error", response.getError());
assertEquals(ResponseErrorCode.InternalError.getValue(), response.getError().getCode());
} finally {
logMessages.unregister();
}
}
}