Skip to content

Commit 4c73999

Browse files
authored
Move all test helper classes out of AbstractTransportTest so they can be used elsewhere (#12125)
1 parent 482dc5c commit 4c73999

File tree

7 files changed

+471
-316
lines changed

7 files changed

+471
-316
lines changed

binder/src/test/java/io/grpc/binder/internal/BinderServerTransportTest.java

Lines changed: 4 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package io.grpc.binder.internal;
1818

19-
import static com.google.common.base.Preconditions.checkState;
2019
import static com.google.common.truth.Truth.assertThat;
2120
import static org.mockito.ArgumentMatchers.any;
2221
import static org.mockito.ArgumentMatchers.anyInt;
@@ -29,11 +28,9 @@
2928
import android.os.Parcel;
3029
import com.google.common.collect.ImmutableList;
3130
import io.grpc.Attributes;
32-
import io.grpc.Metadata;
3331
import io.grpc.Status;
3432
import io.grpc.internal.FixedObjectPool;
35-
import io.grpc.internal.ServerStream;
36-
import io.grpc.internal.ServerTransportListener;
33+
import io.grpc.internal.MockServerTransportListener;
3734
import java.util.concurrent.ScheduledExecutorService;
3835
import org.junit.Before;
3936
import org.junit.Rule;
@@ -55,7 +52,7 @@ public final class BinderServerTransportTest {
5552
@Rule public MockitoRule mocks = MockitoJUnit.rule();
5653

5754
private final ScheduledExecutorService executorService = new MainThreadScheduledExecutorService();
58-
private final TestTransportListener transportListener = new TestTransportListener();
55+
private MockServerTransportListener transportListener;
5956

6057
@Mock IBinder mockBinder;
6158

@@ -70,6 +67,7 @@ public void setUp() throws Exception {
7067
ImmutableList.of(),
7168
OneWayBinderProxy.IDENTITY_DECORATOR,
7269
mockBinder);
70+
transportListener = new MockServerTransportListener(transport);
7371
}
7472

7573
@Test
@@ -82,34 +80,6 @@ public void testSetupTransactionFailureCausesMultipleShutdowns_b153460678() thro
8280
transport.shutdownNow(Status.UNKNOWN.withDescription("reasons"));
8381
shadowOf(Looper.getMainLooper()).idle();
8482

85-
assertThat(transportListener.terminated).isTrue();
86-
}
87-
88-
private static final class TestTransportListener implements ServerTransportListener {
89-
90-
public boolean ready;
91-
public boolean terminated;
92-
93-
/**
94-
* Called when a new stream was created by the remote client.
95-
*
96-
* @param stream the newly created stream.
97-
* @param method the fully qualified method name being called on the server.
98-
* @param headers containing metadata for the call.
99-
*/
100-
@Override
101-
public void streamCreated(ServerStream stream, String method, Metadata headers) {}
102-
103-
@Override
104-
public Attributes transportReady(Attributes attributes) {
105-
ready = true;
106-
return attributes;
107-
}
108-
109-
@Override
110-
public void transportTerminated() {
111-
checkState(!terminated, "Terminated twice");
112-
terminated = true;
113-
}
83+
assertThat(transportListener.isTerminated()).isTrue();
11484
}
11585
}

core/src/testFixtures/java/io/grpc/internal/AbstractTransportTest.java

Lines changed: 78 additions & 282 deletions
Large diffs are not rendered by default.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/*
2+
* Copyright 2025 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.internal;
18+
19+
import static org.junit.Assert.fail;
20+
21+
import com.google.common.collect.Lists;
22+
import com.google.common.util.concurrent.SettableFuture;
23+
import io.grpc.Metadata;
24+
import io.grpc.Status;
25+
import java.io.InputStream;
26+
import java.util.concurrent.BlockingQueue;
27+
import java.util.concurrent.LinkedBlockingQueue;
28+
import java.util.concurrent.TimeUnit;
29+
30+
public class ClientStreamListenerBase implements ClientStreamListener {
31+
public final BlockingQueue<InputStream> messageQueue = new LinkedBlockingQueue<>();
32+
// Would have used Void instead of Object, but null elements are not allowed
33+
private final BlockingQueue<Object> readyQueue = new LinkedBlockingQueue<>();
34+
private final SettableFuture<Metadata> headers = SettableFuture.create();
35+
private final SettableFuture<Metadata> trailers = SettableFuture.create();
36+
private final SettableFuture<Status> status = SettableFuture.create();
37+
38+
/**
39+
* Returns the stream's status or throws {@link java.util.concurrent.TimeoutException} if it isn't
40+
* closed before the timeout.
41+
*/
42+
public Status awaitClose(int timeout, TimeUnit unit) throws Exception {
43+
return status.get(timeout, unit);
44+
}
45+
46+
/**
47+
* Returns response headers from the server or throws {@link
48+
* java.util.concurrent.TimeoutException} if they aren't delivered before the timeout.
49+
*
50+
* <p>Callers must not modify the returned object.
51+
*/
52+
public Metadata awaitHeaders(int timeout, TimeUnit unit) throws Exception {
53+
return headers.get(timeout, unit);
54+
}
55+
56+
/**
57+
* Returns response trailers from the server or throws {@link
58+
* java.util.concurrent.TimeoutException} if they aren't delivered before the timeout.
59+
*
60+
* <p>Callers must not modify the returned object.
61+
*/
62+
public Metadata awaitTrailers(int timeout, TimeUnit unit) throws Exception {
63+
return trailers.get(timeout, unit);
64+
}
65+
66+
public boolean awaitOnReady(int timeout, TimeUnit unit) throws Exception {
67+
return readyQueue.poll(timeout, unit) != null;
68+
}
69+
70+
public boolean awaitOnReadyAndDrain(int timeout, TimeUnit unit) throws Exception {
71+
if (!awaitOnReady(timeout, unit)) {
72+
return false;
73+
}
74+
// Throw the rest away
75+
readyQueue.drainTo(Lists.newArrayList());
76+
return true;
77+
}
78+
79+
@Override
80+
public void messagesAvailable(MessageProducer producer) {
81+
if (status.isDone()) {
82+
fail("messagesAvailable invoked after closed");
83+
}
84+
InputStream message;
85+
while ((message = producer.next()) != null) {
86+
messageQueue.add(message);
87+
}
88+
}
89+
90+
@Override
91+
public void onReady() {
92+
if (status.isDone()) {
93+
fail("onReady invoked after closed");
94+
}
95+
readyQueue.add(new Object());
96+
}
97+
98+
@Override
99+
public void headersRead(Metadata headers) {
100+
if (status.isDone()) {
101+
fail("headersRead invoked after closed");
102+
}
103+
this.headers.set(headers);
104+
}
105+
106+
@Override
107+
public void closed(Status status, RpcProgress rpcProgress, Metadata trailers) {
108+
if (this.status.isDone()) {
109+
fail("headersRead invoked after closed");
110+
}
111+
this.status.set(status);
112+
this.trailers.set(trailers);
113+
}
114+
115+
/** Returns true iff response headers have been received from the server. */
116+
public boolean hasHeaders() {
117+
return headers.isDone();
118+
}
119+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2025 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.internal;
18+
19+
import static org.junit.Assert.assertTrue;
20+
import static org.junit.Assert.fail;
21+
22+
import com.google.common.util.concurrent.SettableFuture;
23+
import java.util.concurrent.BlockingQueue;
24+
import java.util.concurrent.LinkedBlockingQueue;
25+
import java.util.concurrent.TimeUnit;
26+
27+
/**
28+
* A {@link ServerListener} that helps you write blocking unit tests.
29+
*
30+
* <p>TODO: Rename, since this is not actually a mock:
31+
* https://testing.googleblog.com/2013/07/testing-on-toilet-know-your-test-doubles.html
32+
*/
33+
public class MockServerListener implements ServerListener {
34+
private final BlockingQueue<MockServerTransportListener> listeners = new LinkedBlockingQueue<>();
35+
private final SettableFuture<?> shutdown = SettableFuture.create();
36+
private final ServerTransportListenerFactory serverTransportListenerFactory;
37+
38+
/**
39+
* Lets you customize the {@link MockServerTransportListener} installed on newly created
40+
* {@link ServerTransport}s.
41+
*/
42+
public interface ServerTransportListenerFactory {
43+
MockServerTransportListener create(ServerTransport transport);
44+
}
45+
46+
public MockServerListener(ServerTransportListenerFactory serverTransportListenerFactory) {
47+
this.serverTransportListenerFactory = serverTransportListenerFactory;
48+
}
49+
50+
public MockServerListener() {
51+
this(MockServerTransportListener::new);
52+
}
53+
54+
@Override
55+
public ServerTransportListener transportCreated(ServerTransport transport) {
56+
MockServerTransportListener listener = serverTransportListenerFactory.create(transport);
57+
listeners.add(listener);
58+
return listener;
59+
}
60+
61+
@Override
62+
public void serverShutdown() {
63+
assertTrue(shutdown.set(null));
64+
}
65+
66+
public boolean waitForShutdown(long timeout, TimeUnit unit) throws InterruptedException {
67+
return AbstractTransportTest.waitForFuture(shutdown, timeout, unit);
68+
}
69+
70+
public MockServerTransportListener takeListenerOrFail(long timeout, TimeUnit unit)
71+
throws InterruptedException {
72+
MockServerTransportListener listener = listeners.poll(timeout, unit);
73+
if (listener == null) {
74+
fail("Timed out waiting for server transport");
75+
}
76+
return listener;
77+
}
78+
}
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
/*
2+
* Copyright 2025 The gRPC Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.grpc.internal;
18+
19+
import static org.junit.Assert.assertFalse;
20+
import static org.junit.Assert.assertTrue;
21+
import static org.junit.Assert.fail;
22+
23+
import com.google.common.util.concurrent.SettableFuture;
24+
import io.grpc.Attributes;
25+
import io.grpc.Metadata;
26+
import java.util.concurrent.BlockingQueue;
27+
import java.util.concurrent.LinkedBlockingQueue;
28+
import java.util.concurrent.TimeUnit;
29+
30+
/**
31+
* A {@link ServerTransportListener} that helps you write blocking unit tests.
32+
*
33+
* <p>TODO: Rename, since this is not actually a mock:
34+
* https://testing.googleblog.com/2013/07/testing-on-toilet-know-your-test-doubles.html
35+
*/
36+
public class MockServerTransportListener implements ServerTransportListener {
37+
public final ServerTransport transport;
38+
private final BlockingQueue<StreamCreation> streams = new LinkedBlockingQueue<>();
39+
private final SettableFuture<?> terminated = SettableFuture.create();
40+
41+
public MockServerTransportListener(ServerTransport transport) {
42+
this.transport = transport;
43+
}
44+
45+
@Override
46+
public void streamCreated(ServerStream stream, String method, Metadata headers) {
47+
ServerStreamListenerBase listener = new ServerStreamListenerBase();
48+
streams.add(new StreamCreation(stream, method, headers, listener));
49+
stream.setListener(listener);
50+
}
51+
52+
@Override
53+
public Attributes transportReady(Attributes attributes) {
54+
assertFalse(terminated.isDone());
55+
return attributes;
56+
}
57+
58+
@Override
59+
public void transportTerminated() {
60+
assertTrue(terminated.set(null));
61+
}
62+
63+
public boolean waitForTermination(long timeout, TimeUnit unit) throws InterruptedException {
64+
return AbstractTransportTest.waitForFuture(terminated, timeout, unit);
65+
}
66+
67+
public boolean isTerminated() {
68+
return terminated.isDone();
69+
}
70+
71+
public StreamCreation takeStreamOrFail(long timeout, TimeUnit unit) throws InterruptedException {
72+
StreamCreation stream = streams.poll(timeout, unit);
73+
if (stream == null) {
74+
fail("Timed out waiting for server stream");
75+
}
76+
return stream;
77+
}
78+
79+
public static class StreamCreation {
80+
public final ServerStream stream;
81+
public final String method;
82+
public final Metadata headers;
83+
public final ServerStreamListenerBase listener;
84+
85+
public StreamCreation(
86+
ServerStream stream, String method, Metadata headers, ServerStreamListenerBase listener) {
87+
this.stream = stream;
88+
this.method = method;
89+
this.headers = headers;
90+
this.listener = listener;
91+
}
92+
}
93+
}

0 commit comments

Comments
 (0)