Skip to content

Commit 9da0bc1

Browse files
committed
Add RPC unit tests
1 parent d236dfd commit 9da0bc1

9 files changed

Lines changed: 233 additions & 16 deletions

File tree

network/common/src/main/java/org/apache/spark/network/client/SluiceClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ public SluiceClient(Channel channel, SluiceResponseHandler handler) {
7171
}
7272

7373
public boolean isActive() {
74-
return channel.isOpen() || channel.isRegistered() || channel.isActive();
74+
return channel.isOpen() || channel.isActive();
7575
}
7676

7777
/**

network/common/src/main/java/org/apache/spark/network/client/SluiceResponseHandler.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,9 @@ public void handle(ResponseMessage message) {
151151
}
152152
}
153153

154+
/** Returns total number of outstanding requests (fetch requests + rpcs) */
154155
@VisibleForTesting
155156
public int numOutstandingRequests() {
156-
return outstandingFetches.size();
157+
return outstandingFetches.size() + outstandingRpcs.size();
157158
}
158159
}

network/common/src/main/java/org/apache/spark/network/protocol/response/ChunkFetchFailure.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public static ChunkFetchFailure decode(ByteBuf buf) {
5757
int numErrorStringBytes = buf.readInt();
5858
byte[] errorBytes = new byte[numErrorStringBytes];
5959
buf.readBytes(errorBytes);
60-
return new ChunkFetchFailure(streamChunkId, new String(errorBytes));
60+
return new ChunkFetchFailure(streamChunkId, new String(errorBytes, Charsets.UTF_8));
6161
}
6262

6363
@Override

network/common/src/main/java/org/apache/spark/network/protocol/response/RpcFailure.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public static RpcFailure decode(ByteBuf buf) {
5252
int numErrorStringBytes = buf.readInt();
5353
byte[] errorBytes = new byte[numErrorStringBytes];
5454
buf.readBytes(errorBytes);
55-
return new RpcFailure(tag, new String(errorBytes));
55+
return new RpcFailure(tag, new String(errorBytes, Charsets.UTF_8));
5656
}
5757

5858
@Override

network/common/src/test/java/org/apache/spark/network/IntegrationSuite.java renamed to network/common/src/test/java/org/apache/spark/network/ChunkFetchIntegrationSuite.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
import org.apache.spark.network.util.DefaultConfigProvider;
4949
import org.apache.spark.network.util.SluiceConfig;
5050

51-
public class IntegrationSuite {
51+
public class ChunkFetchIntegrationSuite {
5252
static final long STREAM_ID = 1;
5353
static final int BUFFER_CHUNK_INDEX = 0;
5454
static final int FILE_CHUNK_INDEX = 1;

network/common/src/test/java/org/apache/spark/network/NoOpRpcHandler.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.apache.spark.network.server.RpcHandler;
2020
import org.apache.spark.network.client.SluiceClient;
2121

22+
/** Test RpcHandler which always returns a zero-sized success. */
2223
public class NoOpRpcHandler implements RpcHandler {
2324
@Override
2425
public void receive(SluiceClient client, byte[] message, RpcResponseCallback callback) {

network/common/src/test/java/org/apache/spark/network/ProtocolSuite.java

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,13 @@
2525
import org.apache.spark.network.protocol.Message;
2626
import org.apache.spark.network.protocol.StreamChunkId;
2727
import org.apache.spark.network.protocol.request.ChunkFetchRequest;
28+
import org.apache.spark.network.protocol.request.RpcRequest;
2829
import org.apache.spark.network.protocol.response.ChunkFetchFailure;
2930
import org.apache.spark.network.protocol.response.ChunkFetchSuccess;
3031
import org.apache.spark.network.protocol.response.MessageDecoder;
3132
import org.apache.spark.network.protocol.response.MessageEncoder;
33+
import org.apache.spark.network.protocol.response.RpcFailure;
34+
import org.apache.spark.network.protocol.response.RpcResponse;
3235
import org.apache.spark.network.util.NettyUtils;
3336

3437
public class ProtocolSuite {
@@ -63,19 +66,21 @@ private void testClientToServer(Message msg) {
6366
}
6467

6568
@Test
66-
public void s2cChunkFetchSuccess() {
67-
testServerToClient(new ChunkFetchSuccess(new StreamChunkId(1, 2), new TestManagedBuffer(10)));
68-
testServerToClient(new ChunkFetchSuccess(new StreamChunkId(1, 2), new TestManagedBuffer(0)));
69+
public void requests() {
70+
testClientToServer(new ChunkFetchRequest(new StreamChunkId(1, 2)));
71+
testClientToServer(new RpcRequest(12345, new byte[0]));
72+
testClientToServer(new RpcRequest(12345, new byte[100]));
6973
}
7074

7175
@Test
72-
public void s2cBlockFetchFailure() {
76+
public void responses() {
77+
testServerToClient(new ChunkFetchSuccess(new StreamChunkId(1, 2), new TestManagedBuffer(10)));
78+
testServerToClient(new ChunkFetchSuccess(new StreamChunkId(1, 2), new TestManagedBuffer(0)));
7379
testServerToClient(new ChunkFetchFailure(new StreamChunkId(1, 2), "this is an error"));
7480
testServerToClient(new ChunkFetchFailure(new StreamChunkId(1, 2), ""));
81+
testServerToClient(new RpcResponse(12345, new byte[0]));
82+
testServerToClient(new RpcResponse(12345, new byte[1000]));
83+
testServerToClient(new RpcFailure(0, "this is an error"));
84+
testServerToClient(new RpcFailure(0, ""));
7585
}
76-
77-
@Test
78-
public void c2sChunkFetchRequest() {
79-
testClientToServer(new ChunkFetchRequest(new StreamChunkId(1, 2)));
80-
}
81-
}
86+
}
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.spark.network;
19+
20+
import java.util.Collections;
21+
import java.util.HashSet;
22+
import java.util.Iterator;
23+
import java.util.Set;
24+
import java.util.concurrent.Semaphore;
25+
import java.util.concurrent.TimeUnit;
26+
27+
import com.google.common.base.Charsets;
28+
import com.google.common.collect.Sets;
29+
import org.junit.AfterClass;
30+
import org.junit.BeforeClass;
31+
import org.junit.Test;
32+
33+
import static org.junit.Assert.*;
34+
35+
import org.apache.spark.network.client.RpcResponseCallback;
36+
import org.apache.spark.network.client.SluiceClient;
37+
import org.apache.spark.network.client.SluiceClientFactory;
38+
import org.apache.spark.network.server.DefaultStreamManager;
39+
import org.apache.spark.network.server.RpcHandler;
40+
import org.apache.spark.network.server.SluiceServer;
41+
import org.apache.spark.network.util.DefaultConfigProvider;
42+
import org.apache.spark.network.util.SluiceConfig;
43+
44+
public class RpcIntegrationSuite {
45+
static SluiceServer server;
46+
static SluiceClientFactory clientFactory;
47+
static RpcHandler rpcHandler;
48+
49+
@BeforeClass
50+
public static void setUp() throws Exception {
51+
SluiceConfig conf = new SluiceConfig(new DefaultConfigProvider());
52+
rpcHandler = new RpcHandler() {
53+
@Override
54+
public void receive(SluiceClient client, byte[] message, RpcResponseCallback callback) {
55+
String msg = new String(message, Charsets.UTF_8);
56+
String[] parts = msg.split("/");
57+
if (parts[0].equals("hello")) {
58+
callback.onSuccess(("Hello, " + parts[1] + "!").getBytes(Charsets.UTF_8));
59+
} else if (parts[0].equals("return error")) {
60+
callback.onFailure(new RuntimeException("Returned: " + parts[1]));
61+
} else if (parts[0].equals("throw error")) {
62+
throw new RuntimeException("Thrown: " + parts[1]);
63+
}
64+
}
65+
};
66+
SluiceContext context = new SluiceContext(conf, new DefaultStreamManager(), rpcHandler);
67+
server = context.createServer();
68+
clientFactory = context.createClientFactory();
69+
}
70+
71+
@AfterClass
72+
public static void tearDown() {
73+
server.close();
74+
clientFactory.close();
75+
}
76+
77+
class RpcResult {
78+
public Set<String> successMessages;
79+
public Set<String> errorMessages;
80+
}
81+
82+
private RpcResult sendRPC(String ... commands) throws Exception {
83+
SluiceClient client = clientFactory.createClient(TestUtils.getLocalHost(), server.getPort());
84+
final Semaphore sem = new Semaphore(0);
85+
86+
final RpcResult res = new RpcResult();
87+
res.successMessages = Collections.synchronizedSet(new HashSet<String>());
88+
res.errorMessages = Collections.synchronizedSet(new HashSet<String>());
89+
90+
RpcResponseCallback callback = new RpcResponseCallback() {
91+
@Override
92+
public void onSuccess(byte[] message) {
93+
res.successMessages.add(new String(message, Charsets.UTF_8));
94+
sem.release();
95+
}
96+
97+
@Override
98+
public void onFailure(Throwable e) {
99+
res.errorMessages.add(e.getMessage());
100+
sem.release();
101+
}
102+
};
103+
104+
for (String command : commands) {
105+
client.sendRpc(command.getBytes(Charsets.UTF_8), callback);
106+
}
107+
108+
if (!sem.tryAcquire(commands.length, 5, TimeUnit.SECONDS)) {
109+
fail("Timeout getting response from the server");
110+
}
111+
client.close();
112+
return res;
113+
}
114+
115+
@Test
116+
public void singleRPC() throws Exception {
117+
RpcResult res = sendRPC("hello/Aaron");
118+
assertEquals(res.successMessages, Sets.newHashSet("Hello, Aaron!"));
119+
assertTrue(res.errorMessages.isEmpty());
120+
}
121+
122+
@Test
123+
public void doubleRPC() throws Exception {
124+
RpcResult res = sendRPC("hello/Aaron", "hello/Reynold");
125+
assertEquals(res.successMessages, Sets.newHashSet("Hello, Aaron!", "Hello, Reynold!"));
126+
assertTrue(res.errorMessages.isEmpty());
127+
}
128+
129+
@Test
130+
public void returnErrorRPC() throws Exception {
131+
RpcResult res = sendRPC("return error/OK");
132+
assertTrue(res.successMessages.isEmpty());
133+
assertErrorsContain(res.errorMessages, Sets.newHashSet("Returned: OK"));
134+
}
135+
136+
@Test
137+
public void throwErrorRPC() throws Exception {
138+
RpcResult res = sendRPC("throw error/uh-oh");
139+
assertTrue(res.successMessages.isEmpty());
140+
assertErrorsContain(res.errorMessages, Sets.newHashSet("Thrown: uh-oh"));
141+
}
142+
143+
@Test
144+
public void doubleTrouble() throws Exception {
145+
RpcResult res = sendRPC("return error/OK", "throw error/uh-oh");
146+
assertTrue(res.successMessages.isEmpty());
147+
assertErrorsContain(res.errorMessages, Sets.newHashSet("Returned: OK", "Thrown: uh-oh"));
148+
}
149+
150+
@Test
151+
public void sendSuccessAndFailure() throws Exception {
152+
RpcResult res = sendRPC("hello/Bob", "throw error/the", "hello/Builder", "return error/!");
153+
assertEquals(res.successMessages, Sets.newHashSet("Hello, Bob!", "Hello, Builder!"));
154+
assertErrorsContain(res.errorMessages, Sets.newHashSet("Thrown: the", "Returned: !"));
155+
}
156+
157+
private void assertErrorsContain(Set<String> errors, Set<String> contains) {
158+
assertEquals(contains.size(), errors.size());
159+
160+
Set<String> remainingErrors = Sets.newHashSet(errors);
161+
for (String contain : contains) {
162+
Iterator<String> it = remainingErrors.iterator();
163+
boolean foundMatch = false;
164+
while (it.hasNext()) {
165+
if (it.next().contains(contain)) {
166+
it.remove();
167+
foundMatch = true;
168+
break;
169+
}
170+
}
171+
assertTrue("Could not find error containing " + contain + "; errors: " + errors, foundMatch);
172+
}
173+
174+
assertTrue(remainingErrors.isEmpty());
175+
}
176+
}

network/common/src/test/java/org/apache/spark/network/SluiceClientHandlerSuite.java renamed to network/common/src/test/java/org/apache/spark/network/SluiceResponseHandlerSuite.java

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@
2828

2929
import org.apache.spark.network.buffer.ManagedBuffer;
3030
import org.apache.spark.network.client.ChunkReceivedCallback;
31+
import org.apache.spark.network.client.RpcResponseCallback;
3132
import org.apache.spark.network.client.SluiceResponseHandler;
3233
import org.apache.spark.network.protocol.StreamChunkId;
3334
import org.apache.spark.network.protocol.response.ChunkFetchFailure;
3435
import org.apache.spark.network.protocol.response.ChunkFetchSuccess;
36+
import org.apache.spark.network.protocol.response.RpcFailure;
37+
import org.apache.spark.network.protocol.response.RpcResponse;
3538

36-
public class SluiceClientHandlerSuite {
39+
public class SluiceResponseHandlerSuite {
3740
@Test
3841
public void handleSuccessfulFetch() {
3942
StreamChunkId streamChunkId = new StreamChunkId(1, 0);
@@ -79,4 +82,35 @@ public void clearAllOutstandingRequests() {
7982
verify(callback, times(1)).onFailure(eq(2), (Throwable) any());
8083
assertEquals(0, handler.numOutstandingRequests());
8184
}
85+
86+
@Test
87+
public void handleSuccessfulRPC() {
88+
SluiceResponseHandler handler = new SluiceResponseHandler(new LocalChannel());
89+
RpcResponseCallback callback = mock(RpcResponseCallback.class);
90+
handler.addRpcRequest(12345, callback);
91+
assertEquals(1, handler.numOutstandingRequests());
92+
93+
handler.handle(new RpcResponse(54321, new byte[7])); // should be ignored
94+
assertEquals(1, handler.numOutstandingRequests());
95+
96+
byte[] arr = new byte[10];
97+
handler.handle(new RpcResponse(12345, arr));
98+
verify(callback, times(1)).onSuccess(eq(arr));
99+
assertEquals(0, handler.numOutstandingRequests());
100+
}
101+
102+
@Test
103+
public void handleFailedRPC() {
104+
SluiceResponseHandler handler = new SluiceResponseHandler(new LocalChannel());
105+
RpcResponseCallback callback = mock(RpcResponseCallback.class);
106+
handler.addRpcRequest(12345, callback);
107+
assertEquals(1, handler.numOutstandingRequests());
108+
109+
handler.handle(new RpcFailure(54321, "uh-oh!")); // should be ignored
110+
assertEquals(1, handler.numOutstandingRequests());
111+
112+
handler.handle(new RpcFailure(12345, "oh no"));
113+
verify(callback, times(1)).onFailure((Throwable) any());
114+
assertEquals(0, handler.numOutstandingRequests());
115+
}
82116
}

0 commit comments

Comments
 (0)