Skip to content

Commit 454f1c5

Browse files
authored
binder: Create a Robolectric version of BinderTransportTest (#12057)
This version runs way faster than BinderTransportTest and doesn't require an actual Android device/emulator. It'll allow future tests to simulate things that are difficult/impossible on real Android, at the price of some realism.
1 parent 64fe061 commit 454f1c5

File tree

1 file changed

+137
-0
lines changed

1 file changed

+137
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
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.binder.internal;
18+
19+
import static org.robolectric.Shadows.shadowOf;
20+
21+
import android.app.Application;
22+
import android.content.Intent;
23+
import androidx.test.core.app.ApplicationProvider;
24+
import io.grpc.ServerStreamTracer;
25+
import io.grpc.binder.AndroidComponentAddress;
26+
import io.grpc.internal.AbstractTransportTest;
27+
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
28+
import io.grpc.internal.GrpcUtil;
29+
import io.grpc.internal.InternalServer;
30+
import io.grpc.internal.ManagedClientTransport;
31+
import io.grpc.internal.ObjectPool;
32+
import io.grpc.internal.SharedResourcePool;
33+
import java.util.List;
34+
import java.util.concurrent.Executor;
35+
import java.util.concurrent.ScheduledExecutorService;
36+
import org.junit.Ignore;
37+
import org.junit.Test;
38+
import org.junit.runner.RunWith;
39+
import org.robolectric.RobolectricTestRunner;
40+
import org.robolectric.annotation.LooperMode;
41+
import org.robolectric.annotation.LooperMode.Mode;
42+
43+
/**
44+
* All of the AbstractTransportTest cases applied to {@link BinderTransport} running in a
45+
* Robolectric environment.
46+
*
47+
* <p>Runs much faster than BinderTransportTest and doesn't require an Android device/emulator.
48+
* Somewhat less realistic but allows simulating behavior that would be difficult or impossible with
49+
* real Android.
50+
*
51+
* <p>NB: Unlike most robolectric tests, we run in {@link LooperMode.Mode#INSTRUMENTATION_TEST},
52+
* meaning test cases don't run on the main thread. This supports the AbstractTransportTest approach
53+
* where the test thread frequently blocks waiting for transport state changes to take effect.
54+
*/
55+
@RunWith(RobolectricTestRunner.class)
56+
@LooperMode(Mode.INSTRUMENTATION_TEST)
57+
public final class RobolectricBinderTransportTest extends AbstractTransportTest {
58+
59+
private final Application application = ApplicationProvider.getApplicationContext();
60+
private final ObjectPool<ScheduledExecutorService> executorServicePool =
61+
SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE);
62+
private final ObjectPool<Executor> offloadExecutorPool =
63+
SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
64+
private final ObjectPool<Executor> serverExecutorPool =
65+
SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
66+
67+
private int nextServerAddress;
68+
69+
@Override
70+
protected InternalServer newServer(List<ServerStreamTracer.Factory> streamTracerFactories) {
71+
AndroidComponentAddress listenAddr = AndroidComponentAddress.forBindIntent(
72+
new Intent()
73+
.setClassName(application.getPackageName(), "HostService")
74+
.setAction("io.grpc.action.BIND." + nextServerAddress++));
75+
76+
BinderServer binderServer =
77+
new BinderServer.Builder()
78+
.setListenAddress(listenAddr)
79+
.setExecutorPool(serverExecutorPool)
80+
.setExecutorServicePool(executorServicePool)
81+
.setStreamTracerFactories(streamTracerFactories)
82+
.build();
83+
84+
shadowOf(application)
85+
.setComponentNameAndServiceForBindServiceForIntent(
86+
listenAddr.asBindIntent(), listenAddr.getComponent(), binderServer.getHostBinder());
87+
return binderServer;
88+
}
89+
90+
@Override
91+
protected InternalServer newServer(
92+
int port, List<ServerStreamTracer.Factory> streamTracerFactories) {
93+
if (port > 0) {
94+
// TODO: TCP ports have no place in an *abstract* transport test. Replace with SocketAddress.
95+
throw new UnsupportedOperationException();
96+
}
97+
return newServer(streamTracerFactories);
98+
}
99+
100+
@Override
101+
protected ManagedClientTransport newClientTransport(InternalServer server) {
102+
BinderClientTransportFactory.Builder builder =
103+
new BinderClientTransportFactory.Builder()
104+
.setSourceContext(application)
105+
.setScheduledExecutorPool(executorServicePool)
106+
.setOffloadExecutorPool(offloadExecutorPool);
107+
108+
ClientTransportOptions options = new ClientTransportOptions();
109+
options.setEagAttributes(eagAttrs());
110+
options.setChannelLogger(transportLogger());
111+
112+
return new BinderTransport.BinderClientTransport(
113+
builder.buildClientTransportFactory(),
114+
(AndroidComponentAddress) server.getListenSocketAddress(),
115+
options);
116+
}
117+
118+
@Override
119+
protected String testAuthority(InternalServer server) {
120+
return ((AndroidComponentAddress) server.getListenSocketAddress()).getAuthority();
121+
}
122+
123+
@Test
124+
@Ignore("See BinderTransportTest#socketStats.")
125+
@Override
126+
public void socketStats() {}
127+
128+
@Test
129+
@Ignore("See BinderTransportTest#flowControlPushBack")
130+
@Override
131+
public void flowControlPushBack() {}
132+
133+
@Test
134+
@Ignore("See BinderTransportTest#serverAlreadyListening")
135+
@Override
136+
public void serverAlreadyListening() {}
137+
}

0 commit comments

Comments
 (0)