This is an automated email from the ASF dual-hosted git repository.
guohao pushed a commit to branch 3.0
in repository https://gitbox.apache.org/repos/asf/dubbo.git
The following commit(s) were added to refs/heads/3.0 by this push:
new 3cceefa Add test case for TripleProtocolTest and add unit test for
[Unary]ClintStream, AbstractStream (#9236)
3cceefa is described below
commit 3cceefa23434e1a8f337290617cb5594241b3f16
Author: 灼华 <[email protected]>
AuthorDate: Thu Nov 18 22:27:29 2021 +0800
Add test case for TripleProtocolTest and add unit test for
[Unary]ClintStream, AbstractStream (#9236)
* Add test case for TripleProtocolTest and add unit test for
[Unary]ClintStream, AbstractStream
* Add unit test for WriteQueue and [Unary]ServerStream
* Add license
---
.../dubbo/rpc/protocol/tri/AbstractStream.java | 5 +-
.../rpc/protocol/tri/command/DataQueueCommand.java | 15 +
.../dubbo/rpc/protocol/tri/AbstractStreamTest.java | 118 +++++++
.../dubbo/rpc/protocol/tri/ClientStreamTest.java | 367 +++++++++++++++++++++
.../dubbo/rpc/protocol/tri/ServerStreamTest.java | 291 ++++++++++++++++
.../dubbo/rpc/protocol/tri/TripleProtocolTest.java | 56 ++--
.../dubbo/rpc/protocol/tri/WriteQueueTest.java | 113 +++++++
.../dubbo/rpc/protocol/tri/support/IGreeter.java | 4 +
.../rpc/protocol/tri/support/IGreeterImpl.java | 15 +-
.../tri/support/MockAbstractStreamImpl.java | 48 +++
.../{IGreeterImpl.java => MockStreamObserver.java} | 44 ++-
11 files changed, 1038 insertions(+), 38 deletions(-)
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java
b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java
index 1220ec7..5ba50e9 100644
---
a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java
+++
b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/AbstractStream.java
@@ -374,9 +374,9 @@ public abstract class AbstractStream implements Stream {
if (TripleHeaderEnum.containsExcludeAttachments(key)) {
continue;
}
- if (key.endsWith(TripleConstant.GRPC_BIN_SUFFIX) && key.length() >
4) {
+ if (key.endsWith(TripleConstant.GRPC_BIN_SUFFIX) && key.length() >
TripleConstant.GRPC_BIN_SUFFIX.length()) {
try {
- attachments.put(key.substring(0, key.length() - 4),
decodeASCIIByte(header.getValue()));
+ attachments.put(key.substring(0, key.length() -
TripleConstant.GRPC_BIN_SUFFIX.length()), decodeASCIIByte(header.getValue()));
} catch (Exception e) {
LOGGER.error("Failed to parse response attachment key=" +
key, e);
}
@@ -446,7 +446,6 @@ public abstract class AbstractStream implements Stream {
protected <T> T unpack(InputStream is, Class<T> clz) {
try {
final T req = SingleProtobufUtils.deserialize(is, clz);
- is.close();
return req;
} catch (IOException e) {
throw new RuntimeException("Failed to unpack req", e);
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/command/DataQueueCommand.java
b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/command/DataQueueCommand.java
index bc98842..c1bed7b 100644
---
a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/command/DataQueueCommand.java
+++
b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/command/DataQueueCommand.java
@@ -81,4 +81,19 @@ public class DataQueueCommand extends
QueuedCommand.AbstractQueuedCommand {
}
return 1;
}
+
+ // for test
+ public byte[] getData() {
+ return data;
+ }
+
+ // for test
+ public boolean isEndStream() {
+ return endStream;
+ }
+
+ // for test
+ public boolean isClient() {
+ return client;
+ }
}
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/AbstractStreamTest.java
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/AbstractStreamTest.java
new file mode 100644
index 0000000..e730a67
--- /dev/null
+++
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/AbstractStreamTest.java
@@ -0,0 +1,118 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri;
+
+import com.google.protobuf.ByteString;
+import io.netty.handler.codec.http2.Http2Headers;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.utils.ReflectUtils;
+import org.apache.dubbo.rpc.protocol.tri.support.MockAbstractStreamImpl;
+import org.apache.dubbo.triple.TripleWrapper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+
+import static org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum.MESSAGE_KEY;
+import static
org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum.STATUS_DETAIL_KEY;
+import static org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum.STATUS_KEY;
+
+/**
+ * {@link AbstractStream}
+ */
+public class AbstractStreamTest {
+
+ private URL url = URL.valueOf("test://127.0.0.1/test");
+ private AbstractStream stream = new MockAbstractStreamImpl(url);
+
+ @Test
+ public void testTransportError() {
+ Exception exception = getException();
+ OutboundTransportObserver transportObserver =
Mockito.mock(OutboundTransportObserver.class);
+ stream.subscribe(transportObserver);
+ GrpcStatus grpcStatus = GrpcStatus
+ .fromCode(GrpcStatus.Code.INTERNAL)
+ .withDescription("TEST")
+ .withCause(exception);
+ Map<String, Object> attachments = new HashMap<>();
+ attachments.put("strKey", "v1");
+ attachments.put("binKey", new byte[]{1});
+
attachments.put(String.valueOf(Http2Headers.PseudoHeaderName.PATH.value()),
"path");
+ attachments.put(CommonConstants.GROUP_KEY, "group");
+
+ stream.transportError(grpcStatus, attachments, false);
+
+ ArgumentCaptor<DefaultMetadata> metadataArgumentCaptor =
ArgumentCaptor.forClass(DefaultMetadata.class);
+ Mockito.verify(transportObserver,
Mockito.times(2)).onMetadata(metadataArgumentCaptor.capture(),
Mockito.anyBoolean());
+
+ DefaultMetadata defaultMetadata = metadataArgumentCaptor.getValue();
+ Assertions.assertEquals(defaultMetadata.get(STATUS_KEY.getHeader()),
String.valueOf(grpcStatus.code.code));
+ Assertions.assertEquals(defaultMetadata.get(MESSAGE_KEY.getHeader()),
grpcStatus.description);
+
Assertions.assertNotNull(defaultMetadata.get(STATUS_DETAIL_KEY.getHeader()));
+
Assertions.assertTrue(defaultMetadata.contains("strKey".toLowerCase(Locale.ROOT)));
+
Assertions.assertTrue(defaultMetadata.contains("binKey".toLowerCase(Locale.ROOT)
+ TripleConstant.GRPC_BIN_SUFFIX));
+
Assertions.assertFalse(defaultMetadata.contains(String.valueOf(Http2Headers.PseudoHeaderName.PATH.value())));
+
Assertions.assertFalse(defaultMetadata.contains(CommonConstants.GROUP_KEY));
+
+ // test parseMetadataToAttachmentMap
+ Map<String, Object> attachmentMap =
stream.parseMetadataToAttachmentMap(defaultMetadata);
+
Assertions.assertTrue(attachmentMap.containsKey("strKey".toLowerCase(Locale.ROOT)));
+
Assertions.assertTrue(attachmentMap.containsKey("binKey".toLowerCase(Locale.ROOT)));
+
+ }
+
+ @Test
+ public void testPackUnPack() {
+ TripleWrapper.TripleRequestWrapper requestWrapper =
TripleWrapper.TripleRequestWrapper.newBuilder()
+ .addArgTypes(ReflectUtils.getDesc(String.class))
+
.addArgs(ByteString.copyFrom("TEST_ARG".getBytes(StandardCharsets.UTF_8)))
+ .setSerializeType(TripleConstant.HESSIAN4)
+ .build();
+
+ byte[] bytes = stream.pack(requestWrapper);
+ TripleWrapper.TripleRequestWrapper unpackedData = stream.unpack(bytes,
TripleWrapper.TripleRequestWrapper.class);
+
+ Assertions.assertEquals(unpackedData.getArgTypes(0),
requestWrapper.getArgTypes(0));
+ Assertions.assertEquals(unpackedData.getArgs(0),
requestWrapper.getArgs(0));
+ Assertions.assertEquals(unpackedData.getArgs(0),
requestWrapper.getArgs(0));
+ Assertions.assertEquals(unpackedData.getSerializeType(),
requestWrapper.getSerializeType());
+ }
+
+ @Test
+ public void testCodec() {
+ String str = "BCN";
+ String base64ASCII =
stream.encodeBase64ASCII(str.getBytes(StandardCharsets.UTF_8));
+ byte[] bytes = stream.decodeASCIIByte(base64ASCII);
+ Assertions.assertEquals(str, new String(bytes,
StandardCharsets.UTF_8));
+ }
+
+ private Exception getException() {
+ Exception exception = null;
+ try {
+ int count = 1 / 0;
+ } catch (Exception e) {
+ exception = e;
+ }
+ return exception;
+ }
+}
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/ClientStreamTest.java
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/ClientStreamTest.java
new file mode 100644
index 0000000..b6e87bb
--- /dev/null
+++
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/ClientStreamTest.java
@@ -0,0 +1,367 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri;
+
+import com.google.protobuf.ByteString;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.DefaultEventLoop;
+import io.netty.channel.EventLoop;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2StreamChannel;
+import io.netty.util.Attribute;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.constants.CommonConstants;
+import org.apache.dubbo.common.serialize.DefaultMultipleSerialization;
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.remoting.Constants;
+import org.apache.dubbo.remoting.api.Connection;
+import org.apache.dubbo.remoting.exchange.Request;
+import org.apache.dubbo.remoting.exchange.support.DefaultFuture2;
+import org.apache.dubbo.rpc.AppResponse;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.RpcInvocation;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ConsumerModel;
+import org.apache.dubbo.rpc.model.ModuleServiceRepository;
+import org.apache.dubbo.rpc.model.ServiceDescriptor;
+import org.apache.dubbo.rpc.model.ServiceMetadata;
+import org.apache.dubbo.rpc.protocol.tri.command.DataQueueCommand;
+import org.apache.dubbo.rpc.protocol.tri.command.HeaderQueueCommand;
+import org.apache.dubbo.rpc.protocol.tri.command.QueuedCommand;
+import org.apache.dubbo.rpc.protocol.tri.support.IGreeter;
+import org.apache.dubbo.rpc.protocol.tri.support.MockStreamObserver;
+import org.apache.dubbo.triple.TripleWrapper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.dubbo.rpc.protocol.tri.Compressor.DEFAULT_COMPRESSOR;
+import static org.apache.dubbo.rpc.protocol.tri.TripleConstant.HTTP_SCHEME;
+import static org.apache.dubbo.rpc.protocol.tri.TripleHeaderEnum.GRPC_ENCODING;
+
+/**
+ * {@link ClientStream}
+ * {@link UnaryClientStream}
+ * {@link AbstractClientStream}
+ */
+public class ClientStreamTest {
+
+ private URL url;
+ private ConsumerModel consumerModel;
+ private Invoker<IGreeter> invoker;
+ private Connection connection;
+ private int timeout = 100000;
+ private AtomicInteger writeMethodCalledTimes = new AtomicInteger(0);
+
+
+ @BeforeEach
+ public void init() {
+ url = URL.valueOf("tri://127.0.0.1:9103/" + IGreeter.class.getName());
+ ModuleServiceRepository serviceRepository =
ApplicationModel.defaultModel().getDefaultModule().getServiceRepository();
+ ServiceDescriptor serviceDescriptor =
serviceRepository.registerService(IGreeter.class);
+ consumerModel = new ConsumerModel(url.getServiceKey(), null,
serviceDescriptor, null,
+ new ServiceMetadata(), null);
+ url = url.setServiceModel(consumerModel);
+
+ invoker = Mockito.mock(Invoker.class);
+ Mockito.when(invoker.getUrl()).thenReturn(url);
+
+ connection = Mockito.mock(Connection.class);
+
+ writeMethodCalledTimes.set(0);
+ }
+
+ @Test
+ public void testNewClientStream() throws Exception {
+
+ Method echoMethod = IGreeter.class.getDeclaredMethod("echo",
String.class);
+ RpcInvocation rpcInvocation = new RpcInvocation(consumerModel,
echoMethod, IGreeter.class.getName(), url.getProtocolServiceKey(), new
Object[]{"ECHO"});
+ rpcInvocation.setInvoker(invoker);
+
+ Request request = new Request(1);
+ request.setData(rpcInvocation);
+
+ // test UnaryClientStream
+ AbstractClientStream stream =
AbstractClientStream.newClientStream(request, connection);
+
+ Assertions.assertTrue(stream instanceof UnaryClientStream);
+ UnaryClientStream unaryClientStream = (UnaryClientStream) stream;
+ Assertions.assertEquals(unaryClientStream.getConnection(), connection);
+ Assertions.assertEquals(unaryClientStream.getConsumerModel(),
consumerModel);
+ Assertions.assertEquals(unaryClientStream.getMethodName(),
echoMethod.getName());
+ Assertions.assertEquals(unaryClientStream.getUrl(), url);
+ Assertions.assertEquals(unaryClientStream.getRequestId(),
request.getId());
+ Assertions.assertEquals(unaryClientStream.getRpcInvocation(),
rpcInvocation);
+ Assertions.assertEquals(unaryClientStream.getCompressor(),
Compressor.NONE);
+ Assertions.assertEquals(unaryClientStream.getDeCompressor(),
Compressor.NONE);
+ Assertions.assertEquals(stream.getScheme(), HTTP_SCHEME);
+ Assertions.assertEquals(stream.getAcceptEncoding(), "gzip,identity");
+
Assertions.assertFalse(stream.getCancellationContext().getListeners().isEmpty());
+ Assertions.assertTrue(stream.getMultipleSerialization() instanceof
DefaultMultipleSerialization);
+
+ // test ClientStream
+ Method serverStreamMethod =
IGreeter.class.getDeclaredMethod("serverStream", String.class,
StreamObserver.class);
+ rpcInvocation = new RpcInvocation(consumerModel, serverStreamMethod,
IGreeter.class.getName(), url.getProtocolServiceKey(), new Object[]{null,
null});
+ request.setData(rpcInvocation);
+ rpcInvocation.setInvoker(invoker);
+ stream = AbstractClientStream.newClientStream(request, connection);
+ Assertions.assertTrue(stream instanceof ClientStream);
+ }
+
+ @Test
+ public void testStartCall_UnaryClientStream() throws Throwable {
+ // 1. test startCall
+ Method echoMethod = IGreeter.class.getDeclaredMethod("echo",
String.class);
+ RpcInvocation rpcInvocation = new RpcInvocation(consumerModel,
echoMethod, IGreeter.class.getName(),
+ url.getProtocolServiceKey(), new Object[]{"ECHO"});
+ rpcInvocation.setInvoker(invoker);
+ rpcInvocation.setObjectAttachment(Constants.SERIALIZATION_KEY,
TripleConstant.HESSIAN2);
+ rpcInvocation.setObjectAttachment(CommonConstants.PATH_KEY,
url.getPath());
+ rpcInvocation.put(CommonConstants.TIMEOUT_KEY, timeout);
+ Request request = new Request(1);
+ request.setData(rpcInvocation);
+
+ ExecutorService executor = Mockito.mock(ExecutorService.class);
+ DefaultFuture2 future = DefaultFuture2.newFuture(connection, request,
timeout, executor);
+
+ AbstractClientStream stream =
AbstractClientStream.newClientStream(request, connection);
+
+ ChannelPromise promise = Mockito.mock(ChannelPromise.class);
+ Http2StreamChannel streamChannel = getHttp2StreamChannel(stream);
+
+ // startCall
+ WriteQueue writeQueue = new WriteQueue(streamChannel);
+ stream.startCall(writeQueue, promise);
+ // Wait for the asynchronous operation to complete
+ while (writeMethodCalledTimes.get() != 3) {
+ Thread.sleep(50);
+ }
+
+ Assertions.assertNotNull(stream.outboundTransportObserver());
+ ArgumentCaptor<QueuedCommand> commandArgumentCaptor =
ArgumentCaptor.forClass(QueuedCommand.class);
+ ArgumentCaptor<ChannelPromise> promiseArgumentCaptor =
ArgumentCaptor.forClass(ChannelPromise.class);
+ Mockito.verify(streamChannel,
Mockito.times(3)).write(commandArgumentCaptor.capture(),
promiseArgumentCaptor.capture());
+ List<QueuedCommand> queuedCommands =
commandArgumentCaptor.getAllValues();
+
+ HeaderQueueCommand headerQueueCommand = (HeaderQueueCommand)
queuedCommands.get(0);
+ Http2Headers headers = headerQueueCommand.getHeaders();
+
Assertions.assertEquals(headers.get(Http2Headers.PseudoHeaderName.SCHEME.value()),
HTTP_SCHEME);
+
Assertions.assertEquals(headers.get(Http2Headers.PseudoHeaderName.PATH.value()),
"/" + url.getPath() + "/" + rpcInvocation.getMethodName());
+
Assertions.assertEquals(headers.get(Http2Headers.PseudoHeaderName.AUTHORITY.value()),
url.getAddress());
+
Assertions.assertEquals(headers.get(Http2Headers.PseudoHeaderName.METHOD.value()),
HttpMethod.POST.asciiName());
+ Assertions.assertEquals(headers.get(HttpHeaderNames.TE),
HttpHeaderValues.TRAILERS);
+ Assertions.assertEquals(headers.get(GRPC_ENCODING.getHeader()),
DEFAULT_COMPRESSOR);
+
Assertions.assertEquals(headers.get(TripleHeaderEnum.GRPC_ACCEPT_ENCODING.getHeader()),
stream.getAcceptEncoding());
+
Assertions.assertEquals(headers.get(TripleHeaderEnum.CONTENT_TYPE_KEY.getHeader()),
TripleHeaderEnum.CONTENT_PROTO.getHeader());
+
Assertions.assertEquals(headers.get(TripleHeaderEnum.TIMEOUT.getHeader()),
timeout + "m");
+
+ DataQueueCommand dataQueueCommand1 = (DataQueueCommand)
queuedCommands.get(1);
+ Assertions.assertTrue(dataQueueCommand1.getData().length > 0);
+ Assertions.assertFalse(dataQueueCommand1.isEndStream());
+ Assertions.assertTrue(dataQueueCommand1.isClient());
+ TripleWrapper.TripleRequestWrapper requestWrapper =
stream.unpack(dataQueueCommand1.getData(),
TripleWrapper.TripleRequestWrapper.class);
+ ByteArrayInputStream bais = new
ByteArrayInputStream(requestWrapper.getArgs(0).toByteArray());
+ Object ret = stream.getMultipleSerialization().deserialize(url,
stream.getSerializeType(), requestWrapper.getArgTypes(0), bais);
+ bais.close();
+ Assertions.assertEquals(ret.toString(), "ECHO");
+
+ DataQueueCommand dataQueueCommand2 = (DataQueueCommand)
queuedCommands.get(2);
+ Assertions.assertNull(dataQueueCommand2.getData());
+ Assertions.assertTrue(dataQueueCommand2.isEndStream());
+ // 2. Verify the data from the server()
+ // NOTE: The onXX method of inboundTransportObserver is usually
triggered when receiving server data,
+ // here we manually call to simulate the behavior of the [server ->
client]
+ TransportObserver inboundTransportObserver =
stream.inboundTransportObserver();
+ headers = getHttp2Headers(stream);
+ inboundTransportObserver.onMetadata(new Http2HeaderMeta(headers),
false);
+ Object resp = "RESPONSE";
+ byte[] bytes = getPackedData(stream, resp);
+ inboundTransportObserver.onData(bytes, false);
+
inboundTransportObserver.onMetadata(TripleConstant.getSuccessResponseMeta(),
false); // trailers
+ inboundTransportObserver.onComplete();
+ Object result = future.get();
+ Assertions.assertEquals(((AppResponse) result).recreate(), resp);
+ // TODO onError case
+ }
+
+ @Test
+ public void testStartCall_ClientStream_ServerStream() throws Throwable {
+ // 1. test startCall
+ StreamObserver<String> outboundMessageSubscriber = new
MockStreamObserver();
+ Method serverStreamMethod =
IGreeter.class.getDeclaredMethod("serverStream", String.class,
StreamObserver.class);
+ RpcInvocation rpcInvocation = new RpcInvocation(consumerModel,
serverStreamMethod, IGreeter.class.getName(),
+ url.getProtocolServiceKey(), new Object[]{"stringData",
outboundMessageSubscriber});
+ rpcInvocation.setInvoker(invoker);
+ rpcInvocation.setObjectAttachment(Constants.SERIALIZATION_KEY,
TripleConstant.HESSIAN2);
+ Request request = new Request(1);
+ request.setData(rpcInvocation);
+ ExecutorService executor = Mockito.mock(ExecutorService.class);
+ DefaultFuture2 future = DefaultFuture2.newFuture(connection, request,
timeout, executor);
+ AbstractClientStream stream =
AbstractClientStream.newClientStream(request, connection);
+
+ ChannelPromise promise = Mockito.mock(ChannelPromise.class);
+ Http2StreamChannel streamChannel = getHttp2StreamChannel(stream);
+ // startCall
+ WriteQueue writeQueue = new WriteQueue(streamChannel);
+ stream.startCall(writeQueue, promise);
+ // Wait for the asynchronous operation to complete
+ while (writeMethodCalledTimes.get() != 3) {
+ Thread.sleep(50);
+ }
+ Assertions.assertNull(DefaultFuture2.getFuture(request.getId()));
+ Assertions.assertNull(((AppResponse) future.get()).recreate());
+ // NOTE: Send one header frame and two data frames. The previous test
case has been verified, so I won’t go into details here.
+ Mockito.verify(streamChannel, Mockito.times(3)).write(Mockito.any(),
Mockito.any());
+
+ // 2. Verify the data from the server()
+ // NOTE: The onXX method of inboundTransportObserver is usually
triggered when receiving server data,
+ // here we manually call to simulate the behavior of the [server ->
client]
+ TransportObserver inboundTransportObserver =
stream.inboundTransportObserver();
+ DefaultHttp2Headers headers = getHttp2Headers(stream);
+ inboundTransportObserver.onMetadata(new Http2HeaderMeta(headers),
false);
+ Object resp = "RESPONSE";
+ byte[] bytes = getPackedData(stream, resp);
+ inboundTransportObserver.onData(bytes, false);
+
inboundTransportObserver.onMetadata(TripleConstant.SUCCESS_RESPONSE_META,
false);
+ inboundTransportObserver.onComplete();
+ MockStreamObserver observer = (MockStreamObserver)
outboundMessageSubscriber;
+ observer.getLatch().await(1000, TimeUnit.MILLISECONDS); // Wait for
the asynchronous operation to complete
+ Assertions.assertEquals(observer.getOnNextData(), resp);
+ Assertions.assertTrue(observer.isOnCompleted());
+
+ }
+
+
+ @Test
+ public void testStartCall_ClientStream_BidirectionalStream() throws
Throwable {
+ // 1. test startCall
+ StreamObserver<String> outboundMessageSubscriber = new
MockStreamObserver();
+ Method bidirectionalStreamMethod =
IGreeter.class.getDeclaredMethod("bidirectionalStream", StreamObserver.class);
+ RpcInvocation rpcInvocation = new RpcInvocation(consumerModel,
bidirectionalStreamMethod, IGreeter.class.getName(),
+ url.getProtocolServiceKey(), new
Object[]{outboundMessageSubscriber});
+ rpcInvocation.setInvoker(invoker);
+ rpcInvocation.setObjectAttachment(Constants.SERIALIZATION_KEY,
TripleConstant.HESSIAN2);
+ Request request = new Request(1);
+ request.setData(rpcInvocation);
+ ExecutorService executor = Mockito.mock(ExecutorService.class);
+ DefaultFuture2 future = DefaultFuture2.newFuture(connection, request,
timeout, executor);
+ AbstractClientStream stream =
AbstractClientStream.newClientStream(request, connection);
+ ChannelPromise promise = Mockito.mock(ChannelPromise.class);
+ Http2StreamChannel streamChannel = getHttp2StreamChannel(stream);
+ // startCall
+ WriteQueue writeQueue = new WriteQueue(streamChannel);
+ stream.startCall(writeQueue, promise);
+ // Wait for the asynchronous operation to complete
+ while (DefaultFuture2.getFuture(request.getId()) != null) {
+ Thread.sleep(50);
+ }
+ Assertions.assertNull(DefaultFuture2.getFuture(request.getId()));
+ Assertions.assertEquals(stream.outboundMessageSubscriber(),
outboundMessageSubscriber);
+ Assertions.assertEquals(((AppResponse) future.get()).recreate(),
stream.inboundMessageObserver());
+ Mockito.verify(streamChannel, Mockito.times(0)).write(Mockito.any(),
Mockito.any());
+
+ // 2. Verify the data from the server()
+ // Simulate the client to initiate a call
+ StreamObserver<Object> inboundMessageObserver =
stream.inboundMessageObserver();
+ inboundMessageObserver.onNext("TEST");
+ inboundMessageObserver.onCompleted();
+ while (writeMethodCalledTimes.get() != 3) {
+ Thread.sleep(50);
+ }
+ Mockito.verify(streamChannel, Mockito.times(3)).write(Mockito.any(),
Mockito.any());
+
+ // NOTE: The onXX method of inboundTransportObserver is usually
triggered when receiving server data,
+ // here we manually call to simulate the behavior of the [server ->
client]
+ TransportObserver inboundTransportObserver =
stream.inboundTransportObserver();
+ DefaultHttp2Headers headers = getHttp2Headers(stream);
+ inboundTransportObserver.onMetadata(new Http2HeaderMeta(headers),
false);
+ Object resp = "RESPONSE";
+ byte[] bytes = getPackedData(stream, resp);
+ inboundTransportObserver.onData(bytes, false);
+
inboundTransportObserver.onMetadata(TripleConstant.SUCCESS_RESPONSE_META,
false);
+ inboundTransportObserver.onComplete();
+
+ MockStreamObserver observer = (MockStreamObserver)
outboundMessageSubscriber;
+ observer.getLatch().await(1000, TimeUnit.MILLISECONDS);
+ Assertions.assertEquals(observer.getOnNextData(), resp);
+ Assertions.assertTrue(observer.isOnCompleted());
+ }
+
+
+ private Http2StreamChannel getHttp2StreamChannel(AbstractClientStream
stream) {
+ ChannelFuture channelFuture = Mockito.mock(ChannelFuture.class);
+ Http2StreamChannel streamChannel =
Mockito.mock(Http2StreamChannel.class);
+ Attribute<AbstractClientStream> attribute =
Mockito.mock(Attribute.class);
+ ChannelPromise promise = Mockito.mock(ChannelPromise.class);
+ EventLoop eventLoop = new DefaultEventLoop();
+
+ Mockito.when(attribute.get()).thenReturn(stream);
+
Mockito.when(streamChannel.writeAndFlush(Mockito.any())).thenReturn(channelFuture);
+
Mockito.when(streamChannel.alloc()).thenReturn(ByteBufAllocator.DEFAULT);
+
Mockito.when(streamChannel.attr(TripleConstant.CLIENT_STREAM_KEY)).thenReturn(attribute);
+ Mockito.when(streamChannel.eventLoop()).thenReturn(eventLoop);
+ Mockito.when(streamChannel.newPromise()).thenReturn(promise);
+
+ Mockito.when(streamChannel.write(Mockito.any(),
Mockito.any())).thenAnswer(
+ (Answer<ChannelPromise>) invocationOnMock -> {
+ writeMethodCalledTimes.incrementAndGet();
+ return promise;
+ });
+ return streamChannel;
+ }
+
+ private DefaultHttp2Headers getHttp2Headers(AbstractClientStream stream) {
+ DefaultHttp2Headers headers = new DefaultHttp2Headers(true);
+ headers.set(Http2Headers.PseudoHeaderName.STATUS.value(),
HttpResponseStatus.OK.codeAsText());
+ headers.set(HttpHeaderNames.CONTENT_TYPE,
TripleConstant.CONTENT_PROTO);
+ headers.set(TripleHeaderEnum.GRPC_ENCODING.getHeader(),
stream.getCompressor().getMessageEncoding());
+ headers.set(TripleHeaderEnum.GRPC_ACCEPT_ENCODING.getHeader(),
stream.getAcceptEncoding());
+ return headers;
+ }
+
+ private byte[] getPackedData(AbstractClientStream stream, Object resp)
throws IOException {
+ final TripleWrapper.TripleResponseWrapper.Builder builder =
TripleWrapper.TripleResponseWrapper.newBuilder()
+ .setType(stream.getMethodDescriptor().getReturnClass().getName())
+ .setSerializeType(TripleConstant.HESSIAN4);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ stream.getMultipleSerialization().serialize(url,
TripleConstant.HESSIAN2,
stream.getMethodDescriptor().getReturnClass().getName(), resp, bos);
+ builder.setData(ByteString.copyFrom(bos.toByteArray()));
+ bos.close();
+ TripleWrapper.TripleResponseWrapper responseWrapper = builder.build();
+ byte[] bytes = stream.pack(responseWrapper);
+ return bytes;
+ }
+}
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/ServerStreamTest.java
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/ServerStreamTest.java
new file mode 100644
index 0000000..327fb45
--- /dev/null
+++
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/ServerStreamTest.java
@@ -0,0 +1,291 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri;
+
+import com.google.protobuf.ByteString;
+import io.netty.buffer.ByteBufAllocator;
+import io.netty.channel.ChannelFuture;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.DefaultEventLoop;
+import io.netty.channel.EventLoop;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpHeaderValues;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpResponseStatus;
+import io.netty.handler.codec.http2.DefaultHttp2Headers;
+import io.netty.handler.codec.http2.Http2Headers;
+import io.netty.handler.codec.http2.Http2StreamChannel;
+import io.netty.util.Attribute;
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.extension.ExtensionLoader;
+import org.apache.dubbo.common.serialize.MultipleSerialization;
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.remoting.Constants;
+import org.apache.dubbo.rpc.Invoker;
+import org.apache.dubbo.rpc.ProxyFactory;
+import org.apache.dubbo.rpc.filter.TokenHeaderFilter;
+import org.apache.dubbo.rpc.model.ApplicationModel;
+import org.apache.dubbo.rpc.model.ModuleServiceRepository;
+import org.apache.dubbo.rpc.model.ProviderModel;
+import org.apache.dubbo.rpc.model.ServiceDescriptor;
+import org.apache.dubbo.rpc.model.ServiceMetadata;
+import org.apache.dubbo.rpc.protocol.tri.command.DataQueueCommand;
+import org.apache.dubbo.rpc.protocol.tri.command.HeaderQueueCommand;
+import org.apache.dubbo.rpc.protocol.tri.command.QueuedCommand;
+import org.apache.dubbo.rpc.protocol.tri.support.IGreeter;
+import org.apache.dubbo.rpc.protocol.tri.support.IGreeterImpl;
+import org.apache.dubbo.rpc.protocol.tri.support.MockStreamObserver;
+import org.apache.dubbo.triple.TripleWrapper;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.dubbo.rpc.protocol.tri.Compressor.DEFAULT_COMPRESSOR;
+
+/**
+ * {@link ServerStream}
+ * {@link UnaryServerStream}
+ * {@link AbstractServerStream}
+ */
+public class ServerStreamTest {
+ private URL url;
+ private ProviderModel providerModel;
+ private Invoker invoker;
+ private IGreeter serviceImpl;
+ private int timeout = 10000;
+ private AtomicInteger writeMethodCalledTimes = new AtomicInteger(0);
+ private final String REQUEST_MSG = "TEST_DATA";
+
+ @BeforeEach
+ public void init() {
+ serviceImpl = new IGreeterImpl();
+ url = URL.valueOf("tri://127.0.0.1:9103/" + IGreeter.class.getName());
+ ModuleServiceRepository serviceRepository =
ApplicationModel.defaultModel().getDefaultModule().getServiceRepository();
+ ServiceDescriptor serviceDescriptor =
serviceRepository.registerService(IGreeter.class);
+ providerModel = new ProviderModel(
+ url.getServiceKey(),
+ serviceImpl,
+ serviceDescriptor,
+ null,
+ new ServiceMetadata());
+ serviceRepository.registerProvider(providerModel);
+ url = url.setServiceModel(providerModel);
+
+ ProxyFactory proxy =
ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+ invoker = proxy.getInvoker(serviceImpl, IGreeter.class, url);
+
+ writeMethodCalledTimes.set(0);
+ }
+
+ @Test
+ public void testNewServerStream() {
+ AbstractServerStream unaryServerStream =
AbstractServerStream.newServerStream(invoker.getUrl(), true);
+ Assertions.assertTrue(unaryServerStream instanceof UnaryServerStream);
+
+ AbstractServerStream serverStream =
AbstractServerStream.newServerStream(invoker.getUrl(), false);
+ Assertions.assertTrue(serverStream instanceof ServerStream);
+
+ Assertions.assertTrue(unaryServerStream.getHeaderFilters().get(0)
instanceof TokenHeaderFilter);
+ Assertions.assertEquals(unaryServerStream.getSerializeType(),
Constants.DEFAULT_REMOTING_SERIALIZATION);
+ }
+
+
+ @Test
+ public void testUnaryServerStream() throws Exception {
+
+ AbstractServerStream stream =
AbstractServerStream.newServerStream(invoker.getUrl(), true);
+ Http2StreamChannel channel = getHttp2StreamChannel(stream);
+ WriteQueue writeQueue = new WriteQueue(channel);
+ ServerOutboundTransportObserver outboundTransportObserver = new
ServerOutboundTransportObserver(writeQueue);
+ Method echoMethod = IGreeter.class.getDeclaredMethod("echo", new
Class[]{String.class});
+ String methodName = echoMethod.getName();
+ stream.service(providerModel.getServiceModel())
+ .invoker(invoker)
+ .methodName(methodName)
+ .setDeCompressor(Compressor.NONE)
+ .method(providerModel.getServiceModel().getMethod(methodName, new
Class[]{String.class}))
+ .subscribe(outboundTransportObserver);
+
+ Http2Headers headers = getHttp2Headers(methodName);
+
+ final TransportObserver inboundTransportObserver =
stream.inboundTransportObserver();
+ inboundTransportObserver.onMetadata(new Http2HeaderMeta(headers),
false); // 1
+ byte[] data = getPackedData(stream, new Object[]{REQUEST_MSG}, new
Class[]{String.class});
+ inboundTransportObserver.onData(data, false); // 2
+ inboundTransportObserver.onComplete(); // 3
+
+ // Wait for the asynchronous operation to complete
+ while (writeMethodCalledTimes.get() != 3) {
+ Thread.sleep(50);
+ }
+ ArgumentCaptor<QueuedCommand> commandArgumentCaptor =
ArgumentCaptor.forClass(QueuedCommand.class);
+ ArgumentCaptor<ChannelPromise> promiseArgumentCaptor =
ArgumentCaptor.forClass(ChannelPromise.class);
+ Mockito.verify(channel,
Mockito.times(3)).write(commandArgumentCaptor.capture(),
promiseArgumentCaptor.capture());
+ List<QueuedCommand> queuedCommands =
commandArgumentCaptor.getAllValues();
+
+ HeaderQueueCommand headerQueueCommand1 = (HeaderQueueCommand)
queuedCommands.get(0);
+ Http2Headers headers1 = headerQueueCommand1.getHeaders();
+
Assertions.assertEquals(headers1.get(Http2Headers.PseudoHeaderName.STATUS.value()),
HttpResponseStatus.OK.codeAsText());
+ Assertions.assertEquals(headers1.get(HttpHeaderNames.CONTENT_TYPE),
TripleConstant.CONTENT_PROTO);
+
Assertions.assertEquals(headers1.get(TripleHeaderEnum.GRPC_ENCODING.getHeader()),
stream.getCompressor().getMessageEncoding());
+
Assertions.assertEquals(headers1.get(TripleHeaderEnum.GRPC_ACCEPT_ENCODING.getHeader()),
stream.getAcceptEncoding());
+
+ DataQueueCommand dataQueueCommand = (DataQueueCommand)
queuedCommands.get(1);
+ Assertions.assertTrue(dataQueueCommand.getData().length > 0);
+ Assertions.assertFalse(dataQueueCommand.isEndStream());
+ Assertions.assertFalse(dataQueueCommand.isClient());
+ TripleWrapper.TripleResponseWrapper responseWrapper =
stream.unpack(dataQueueCommand.getData(),
TripleWrapper.TripleResponseWrapper.class);
+ ByteArrayInputStream bais = new
ByteArrayInputStream(responseWrapper.getData().toByteArray());
+ Object ret = stream.getMultipleSerialization().deserialize(url,
stream.getSerializeType(), responseWrapper.getType(), bais);
+ bais.close();
+ Assertions.assertEquals(ret.toString(), REQUEST_MSG);
+
+ HeaderQueueCommand headerQueueCommand2 = (HeaderQueueCommand)
queuedCommands.get(2);
+ Assertions.assertTrue(headerQueueCommand2.isEndStream());
+ Http2Headers headers2 = headerQueueCommand2.getHeaders();
+
Assertions.assertEquals(headers2.get(TripleHeaderEnum.MESSAGE_KEY.getHeader()),
TripleConstant.SUCCESS_RESPONSE_MESSAGE);
+
Assertions.assertEquals(headers2.get(TripleHeaderEnum.STATUS_KEY.getHeader()),
TripleConstant.SUCCESS_RESPONSE_STATUS);
+ }
+
+
+ @Test
+ public void testServerStream() throws Exception {
+ AbstractServerStream stream =
AbstractServerStream.newServerStream(invoker.getUrl(), false);
+ Http2StreamChannel channel = getHttp2StreamChannel(stream);
+ WriteQueue writeQueue = new WriteQueue(channel);
+ ServerOutboundTransportObserver outboundTransportObserver = new
ServerOutboundTransportObserver(writeQueue);
+ Method serverStreamMethod =
IGreeter.class.getDeclaredMethod("serverStream", new Class[]{String.class,
StreamObserver.class});
+ String methodName = serverStreamMethod.getName();
+ stream.service(providerModel.getServiceModel())
+ .invoker(invoker)
+ .methodName(methodName)
+ .setDeCompressor(Compressor.NONE)
+ .method(providerModel.getServiceModel().getMethod(methodName, new
Class[]{String.class, StreamObserver.class}))
+ .subscribe(outboundTransportObserver);
+
+
+ final TransportObserver inboundTransportObserver =
stream.inboundTransportObserver();
+ Http2Headers headers = getHttp2Headers(methodName);
+ inboundTransportObserver.onMetadata(new Http2HeaderMeta(headers),
false); // 1
+ byte[] data = getPackedData(stream, new Object[]{REQUEST_MSG}, new
Class[]{String.class});
+ inboundTransportObserver.onData(data, false); // 2
+ inboundTransportObserver.onComplete(); // 3
+
+ // Wait for the asynchronous operation to complete
+ while (writeMethodCalledTimes.get() != 3) {
+ Thread.sleep(50);
+ }
+ // NOTE: Send two header frames and one data frames. The previous test
case has been verified, so I won’t go into details here.
+ Mockito.verify(channel, Mockito.times(3)).write(Mockito.any(),
Mockito.any());
+ }
+
+ @Test
+ public void testServerStream_biDirectional() throws Exception {
+ AbstractServerStream stream =
AbstractServerStream.newServerStream(invoker.getUrl(), false);
+ Http2StreamChannel channel = getHttp2StreamChannel(stream);
+ WriteQueue writeQueue = new WriteQueue(channel);
+ ServerOutboundTransportObserver outboundTransportObserver = new
ServerOutboundTransportObserver(writeQueue);
+ Method bidirectionalStreamMethod =
IGreeter.class.getDeclaredMethod("bidirectionalStream", new
Class[]{StreamObserver.class});
+ String methodName = bidirectionalStreamMethod.getName();
+ stream.service(providerModel.getServiceModel())
+ .invoker(invoker)
+ .methodName(methodName)
+ .setDeCompressor(Compressor.NONE)
+ .method(providerModel.getServiceModel().getMethod(methodName, new
Class[]{String.class}))
+ .subscribe(outboundTransportObserver);
+
+ final TransportObserver inboundTransportObserver =
stream.inboundTransportObserver();
+ Http2Headers headers = getHttp2Headers(methodName);
+ inboundTransportObserver.onMetadata(new Http2HeaderMeta(headers),
false); // 1
+ byte[] data = getPackedData(stream, new Object[]{REQUEST_MSG}, new
Class[]{String.class});
+ inboundTransportObserver.onData(data, false); // 2
+ inboundTransportObserver.onComplete(); // 3
+
+ // Wait for the asynchronous operation to complete
+ while (writeMethodCalledTimes.get() != 3) {
+ Thread.sleep(50);
+ }
+ // NOTE: Send two header frames and one data frames. The previous test
case has been verified, so I won’t go into details here.
+ Mockito.verify(channel, Mockito.times(3)).write(Mockito.any(),
Mockito.any());
+
+ MockStreamObserver serverOutboundMessageSubscriber =
(MockStreamObserver) ((IGreeterImpl) serviceImpl).getMockStreamObserver();
+ serverOutboundMessageSubscriber.getLatch().await(1000,
TimeUnit.MILLISECONDS);
+
Assertions.assertEquals(serverOutboundMessageSubscriber.getOnNextData(),
REQUEST_MSG);
+ Assertions.assertTrue(serverOutboundMessageSubscriber.isOnCompleted());
+ }
+
+ private Http2Headers getHttp2Headers(String methodName) {
+ Http2Headers headers = new DefaultHttp2Headers();
+ headers.set(Http2Headers.PseudoHeaderName.PATH.value(), "/" +
url.getPath() + "/" + methodName);
+ headers.set(Http2Headers.PseudoHeaderName.METHOD.value(),
HttpMethod.POST.asciiName());
+ headers.set(HttpHeaderNames.TE, HttpHeaderValues.TRAILERS);
+ headers.set(TripleHeaderEnum.GRPC_ENCODING.getHeader(),
DEFAULT_COMPRESSOR);
+ headers.set(TripleHeaderEnum.CONTENT_TYPE_KEY.getHeader(),
TripleHeaderEnum.CONTENT_PROTO.getHeader());
+ headers.set(TripleHeaderEnum.TIMEOUT.getHeader(), timeout + "m");
+ return headers;
+ }
+
+ private Http2StreamChannel getHttp2StreamChannel(AbstractServerStream
stream) {
+ ChannelFuture channelFuture = Mockito.mock(ChannelFuture.class);
+ Http2StreamChannel streamChannel =
Mockito.mock(Http2StreamChannel.class);
+ Attribute<AbstractServerStream> attribute =
Mockito.mock(Attribute.class);
+ ChannelPromise promise = Mockito.mock(ChannelPromise.class);
+ EventLoop eventLoop = new DefaultEventLoop();
+
+ Mockito.when(attribute.get()).thenReturn(stream);
+
Mockito.when(streamChannel.writeAndFlush(Mockito.any())).thenReturn(channelFuture);
+
Mockito.when(streamChannel.alloc()).thenReturn(ByteBufAllocator.DEFAULT);
+
Mockito.when(streamChannel.attr(TripleConstant.SERVER_STREAM_KEY)).thenReturn(attribute);
+ Mockito.when(streamChannel.eventLoop()).thenReturn(eventLoop);
+ Mockito.when(streamChannel.newPromise()).thenReturn(promise);
+
+ Mockito.when(streamChannel.write(Mockito.any(),
Mockito.any())).thenAnswer(
+ (Answer<ChannelPromise>) invocationOnMock -> {
+ writeMethodCalledTimes.incrementAndGet();
+ return promise;
+ });
+ return streamChannel;
+ }
+
+ private byte[] getPackedData(AbstractServerStream stream, Object[] args,
Class[] argsTypes) throws IOException {
+ String serializationName = TripleConstant.HESSIAN2;
+ final TripleWrapper.TripleRequestWrapper.Builder builder =
TripleWrapper.TripleRequestWrapper.newBuilder()
+ .setSerializeType(TripleConstant.HESSIAN4);
+ MultipleSerialization serialization =
stream.getMultipleSerialization();
+ for (int i = 0; i < args.length; i++) {
+ final String clz = argsTypes[i].getName();
+ builder.addArgTypes(clz);
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ serialization.serialize(url, serializationName, clz, args[i], bos);
+ builder.addArgs(ByteString.copyFrom(bos.toByteArray()));
+ }
+ TripleWrapper.TripleRequestWrapper requestWrapper = builder.build();
+ byte[] bytes = stream.pack(requestWrapper);
+ return bytes;
+ }
+}
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocolTest.java
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocolTest.java
index 821d6e9..4c7f194 100644
---
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocolTest.java
+++
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/TripleProtocolTest.java
@@ -17,7 +17,6 @@
package org.apache.dubbo.rpc.protocol.tri;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.dubbo.common.URL;
@@ -35,6 +34,7 @@ import org.apache.dubbo.rpc.model.ServiceMetadata;
import org.apache.dubbo.rpc.protocol.tri.support.IGreeter;
import org.apache.dubbo.rpc.protocol.tri.support.IGreeterImpl;
+import org.apache.dubbo.rpc.protocol.tri.support.MockStreamObserver;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -42,6 +42,7 @@ import org.junit.jupiter.api.Test;
public class TripleProtocolTest {
private Protocol protocol =
ExtensionLoader.getExtensionLoader(Protocol.class).getAdaptiveExtension();
private ProxyFactory proxy =
ExtensionLoader.getExtensionLoader(ProxyFactory.class).getAdaptiveExtension();
+ private final String REQUEST_MSG = "hello world";
@Test
public void testDemoProtocol() throws Exception {
@@ -68,32 +69,35 @@ public class TripleProtocolTest {
ConsumerModel consumerModel = new ConsumerModel(url.getServiceKey(),
null, serviceDescriptor, null,
new ServiceMetadata(), null);
url = url.setServiceModel(consumerModel);
- serviceImpl = proxy.getProxy(protocol.refer(IGreeter.class, url));
+ IGreeter greeterProxy = proxy.getProxy(protocol.refer(IGreeter.class,
url));
Thread.sleep(1000);
- Assertions.assertEquals("hello world", serviceImpl.echo("hello
world"));
- // fixme will throw exception
- // Assertions.assertEquals("hello world", serviceImpl.echoAsync("hello
world").get());
- CountDownLatch latch = new CountDownLatch(1);
- serviceImpl.serverStream("hello world", new StreamObserver<String>() {
- @Override
- public void onNext(String data) {
- Assertions.assertEquals("hello world",data);
- }
-
- @Override
- public void onError(Throwable throwable) {
- throwable.printStackTrace();
- }
-
- @Override
- public void onCompleted() {
- latch.countDown();
- System.out.println("onCompleted");
- }
- });
-
- // release CPU to run StreamObserver methods.
- latch.await(1000, TimeUnit.MILLISECONDS);
+
+ // 1. test unaryStream
+ Assertions.assertEquals(REQUEST_MSG, greeterProxy.echo(REQUEST_MSG));
+ Assertions.assertEquals(REQUEST_MSG,
serviceImpl.echoAsync(REQUEST_MSG).get());
+
+ // 2. test serverStream
+ MockStreamObserver outboundMessageSubscriber1 = new
MockStreamObserver();
+ greeterProxy.serverStream(REQUEST_MSG, outboundMessageSubscriber1);
+ outboundMessageSubscriber1.getLatch().await(1000,
TimeUnit.MILLISECONDS);
+ Assertions.assertEquals(outboundMessageSubscriber1.getOnNextData(),
REQUEST_MSG);
+ Assertions.assertTrue(outboundMessageSubscriber1.isOnCompleted());
+
+ // 3. test bidirectionalStream
+ MockStreamObserver outboundMessageSubscriber2 = new
MockStreamObserver();
+ StreamObserver<String> inboundMessageObserver =
greeterProxy.bidirectionalStream(outboundMessageSubscriber2);
+ inboundMessageObserver.onNext(REQUEST_MSG);
+ inboundMessageObserver.onCompleted();
+ outboundMessageSubscriber2.getLatch().await(1000,
TimeUnit.MILLISECONDS);
+ // verify client
+ Assertions.assertEquals(outboundMessageSubscriber2.getOnNextData(),
IGreeter.SERVER_MSG);
+ Assertions.assertTrue(outboundMessageSubscriber2.isOnCompleted());
+ // verify server
+ MockStreamObserver serverOutboundMessageSubscriber =
(MockStreamObserver) ((IGreeterImpl) serviceImpl).getMockStreamObserver();
+ serverOutboundMessageSubscriber.getLatch().await(1000,
TimeUnit.MILLISECONDS);
+
Assertions.assertEquals(serverOutboundMessageSubscriber.getOnNextData(),
REQUEST_MSG);
+ Assertions.assertTrue(serverOutboundMessageSubscriber.isOnCompleted());
+
// resource recycle.
serviceRepository.destroy();
System.out.println("serviceRepository destroyed");
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/WriteQueueTest.java
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/WriteQueueTest.java
new file mode 100644
index 0000000..5d0bf30
--- /dev/null
+++
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/WriteQueueTest.java
@@ -0,0 +1,113 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri;
+
+import io.netty.channel.Channel;
+import io.netty.channel.ChannelPromise;
+import io.netty.channel.DefaultEventLoop;
+import io.netty.channel.EventLoop;
+import org.apache.dubbo.rpc.RpcException;
+import org.apache.dubbo.rpc.protocol.tri.command.CancelQueueCommand;
+import org.apache.dubbo.rpc.protocol.tri.command.DataQueueCommand;
+import org.apache.dubbo.rpc.protocol.tri.command.FlushQueueCommand;
+import org.apache.dubbo.rpc.protocol.tri.command.HeaderQueueCommand;
+import org.apache.dubbo.rpc.protocol.tri.command.QueuedCommand;
+import org.apache.dubbo.rpc.protocol.tri.command.TextDataQueueCommand;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.apache.dubbo.rpc.protocol.tri.WriteQueue.DEQUE_CHUNK_SIZE;
+
+
+/**
+ * {@link WriteQueue}
+ */
+public class WriteQueueTest {
+ private AtomicInteger writeMethodCalledTimes = new AtomicInteger(0);
+ private Channel channel;
+
+ @BeforeEach
+ public void init() {
+ channel = Mockito.mock(Channel.class);
+ ChannelPromise promise = Mockito.mock(ChannelPromise.class);
+ EventLoop eventLoop = new DefaultEventLoop();
+ Mockito.when(channel.eventLoop()).thenReturn(eventLoop);
+ Mockito.when(channel.newPromise()).thenReturn(promise);
+ Mockito.when(channel.write(Mockito.any(), Mockito.any())).thenAnswer(
+ (Answer<ChannelPromise>) invocationOnMock -> {
+ writeMethodCalledTimes.incrementAndGet();
+ return promise;
+ });
+
+ writeMethodCalledTimes.set(0);
+ }
+
+ @Test
+ public void test() throws Exception {
+
+ WriteQueue writeQueue = new WriteQueue(channel);
+ writeQueue.enqueue(HeaderQueueCommand.createHeaders(new
DefaultMetadata()), false);
+ writeQueue.enqueue(DataQueueCommand.createGrpcCommand(true), false);
+ GrpcStatus status = GrpcStatus.fromCode(GrpcStatus.Code.UNKNOWN)
+ .withCause(new RpcException())
+ .withDescription("Encode Response data error");
+ writeQueue.enqueue(CancelQueueCommand.createCommand(status), false);
+
writeQueue.enqueue(TextDataQueueCommand.createCommand(status.description,
true), false);
+ writeQueue.enqueue(new FlushQueueCommand(), true);
+
+ while (writeMethodCalledTimes.get() != 5) {
+ Thread.sleep(50);
+ }
+
+ ArgumentCaptor<QueuedCommand> commandArgumentCaptor =
ArgumentCaptor.forClass(QueuedCommand.class);
+ ArgumentCaptor<ChannelPromise> promiseArgumentCaptor =
ArgumentCaptor.forClass(ChannelPromise.class);
+ Mockito.verify(channel,
Mockito.times(5)).write(commandArgumentCaptor.capture(),
promiseArgumentCaptor.capture());
+ List<QueuedCommand> queuedCommands =
commandArgumentCaptor.getAllValues();
+ Assertions.assertEquals(queuedCommands.size(), 5);
+ Assertions.assertTrue(queuedCommands.get(0) instanceof
HeaderQueueCommand);
+ Assertions.assertTrue(queuedCommands.get(1) instanceof
DataQueueCommand);
+ Assertions.assertTrue(queuedCommands.get(2) instanceof
CancelQueueCommand);
+ Assertions.assertTrue(queuedCommands.get(3) instanceof
TextDataQueueCommand);
+ Assertions.assertTrue(queuedCommands.get(4) instanceof
FlushQueueCommand);
+
+ Mockito.verify(channel, Mockito.times(1)).flush();
+ }
+
+ @Test
+ public void testChunk() throws Exception {
+ WriteQueue writeQueue = new WriteQueue(channel);
+ // test deque chunk size
+ writeMethodCalledTimes.set(0);
+ for (int i = 0; i < DEQUE_CHUNK_SIZE; i++) {
+ writeQueue.enqueue(HeaderQueueCommand.createHeaders(new
DefaultMetadata()), false);
+ }
+ writeQueue.enqueue(HeaderQueueCommand.createHeaders(new
DefaultMetadata()), true);
+ while (writeMethodCalledTimes.get() != (DEQUE_CHUNK_SIZE + 1)) {
+ Thread.sleep(50);
+ }
+ Mockito.verify(channel, Mockito.times(DEQUE_CHUNK_SIZE +
1)).write(Mockito.any(), Mockito.any());
+ Mockito.verify(channel, Mockito.times(2)).flush();
+ }
+
+}
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/IGreeter.java
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/IGreeter.java
index 55a5a7c..42a89af 100644
---
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/IGreeter.java
+++
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/IGreeter.java
@@ -22,6 +22,9 @@ import org.apache.dubbo.common.stream.StreamObserver;
import java.util.concurrent.CompletableFuture;
public interface IGreeter {
+
+ String SERVER_MSG = "HELLO WORLD";
+
/**
* Use request to respond
*/
@@ -33,5 +36,6 @@ public interface IGreeter {
void serverStream(String str, StreamObserver<String> observer);
+ StreamObserver<String> bidirectionalStream(StreamObserver<String>
observer);
}
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/IGreeterImpl.java
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/IGreeterImpl.java
index daeeb0b..c4f14f4 100644
---
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/IGreeterImpl.java
+++
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/IGreeterImpl.java
@@ -21,6 +21,8 @@ import org.apache.dubbo.common.stream.StreamObserver;
public class IGreeterImpl implements IGreeter {
+ private StreamObserver<String> mockStreamObserver = new
MockStreamObserver();
+
@Override
public String echo(String request) {
return request;
@@ -28,8 +30,19 @@ public class IGreeterImpl implements IGreeter {
@Override
public void serverStream(String str, StreamObserver<String> observer) {
- System.out.println("srt="+str);
+ System.out.println("srt=" + str);
observer.onNext(str);
observer.onCompleted();
}
+
+ @Override
+ public StreamObserver<String> bidirectionalStream(StreamObserver<String>
observer) {
+ observer.onNext(SERVER_MSG);
+ observer.onCompleted();
+ return mockStreamObserver; // This will serve as the server's
outboundMessageSubscriber
+ }
+
+ public StreamObserver<String> getMockStreamObserver() {
+ return mockStreamObserver;
+ }
}
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/MockAbstractStreamImpl.java
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/MockAbstractStreamImpl.java
new file mode 100644
index 0000000..4270e05
--- /dev/null
+++
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/MockAbstractStreamImpl.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.dubbo.rpc.protocol.tri.support;
+
+import org.apache.dubbo.common.URL;
+import org.apache.dubbo.common.stream.StreamObserver;
+import org.apache.dubbo.rpc.protocol.tri.AbstractStream;
+import org.apache.dubbo.rpc.protocol.tri.InboundTransportObserver;
+
+public class MockAbstractStreamImpl extends AbstractStream {
+ public MockAbstractStreamImpl(URL url) {
+ super(url);
+ }
+
+ @Override
+ protected void cancelByRemoteReset() {
+
+ }
+
+ @Override
+ protected void cancelByLocal(Throwable throwable) {
+
+ }
+
+ @Override
+ protected StreamObserver<Object> createStreamObserver() {
+ return null;
+ }
+
+ @Override
+ protected InboundTransportObserver createInboundTransportObserver() {
+ return null;
+ }
+}
diff --git
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/IGreeterImpl.java
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/MockStreamObserver.java
similarity index 51%
copy from
dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/IGreeterImpl.java
copy to
dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/MockStreamObserver.java
index daeeb0b..f21686d 100644
---
a/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/IGreeterImpl.java
+++
b/dubbo-rpc/dubbo-rpc-triple/src/test/java/org/apache/dubbo/rpc/protocol/tri/support/MockStreamObserver.java
@@ -14,22 +14,50 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.apache.dubbo.rpc.protocol.tri.support;
import org.apache.dubbo.common.stream.StreamObserver;
-public class IGreeterImpl implements IGreeter {
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Usually use it to simulate a outboundMessageSubscriber
+ */
+public class MockStreamObserver implements StreamObserver<String> {
+ private String onNextData;
+ private Throwable onErrorThrowable;
+ private boolean onCompleted;
+ private CountDownLatch latch = new CountDownLatch(1);
@Override
- public String echo(String request) {
- return request;
+ public void onNext(String data) {
+ onNextData = data;
}
@Override
- public void serverStream(String str, StreamObserver<String> observer) {
- System.out.println("srt="+str);
- observer.onNext(str);
- observer.onCompleted();
+ public void onError(Throwable throwable) {
+ onErrorThrowable = throwable;
+ }
+
+ @Override
+ public void onCompleted() {
+ onCompleted = true;
+ latch.countDown();
+ }
+
+ public String getOnNextData() {
+ return onNextData;
+ }
+
+ public Throwable getOnErrorThrowable() {
+ return onErrorThrowable;
+ }
+
+ public boolean isOnCompleted() {
+ return onCompleted;
+ }
+
+ public CountDownLatch getLatch() {
+ return latch;
}
}