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.
This commit is contained in:
John Cormie 2025-05-09 14:57:59 -07:00 committed by GitHub
parent 64fe061ccd
commit 454f1c5c6a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 137 additions and 0 deletions

View File

@ -0,0 +1,137 @@
/*
* Copyright 2025 The gRPC Authors
*
* Licensed 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 io.grpc.binder.internal;
import static org.robolectric.Shadows.shadowOf;
import android.app.Application;
import android.content.Intent;
import androidx.test.core.app.ApplicationProvider;
import io.grpc.ServerStreamTracer;
import io.grpc.binder.AndroidComponentAddress;
import io.grpc.internal.AbstractTransportTest;
import io.grpc.internal.ClientTransportFactory.ClientTransportOptions;
import io.grpc.internal.GrpcUtil;
import io.grpc.internal.InternalServer;
import io.grpc.internal.ManagedClientTransport;
import io.grpc.internal.ObjectPool;
import io.grpc.internal.SharedResourcePool;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledExecutorService;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.LooperMode.Mode;
/**
* All of the AbstractTransportTest cases applied to {@link BinderTransport} running in a
* Robolectric environment.
*
* <p>Runs much faster than BinderTransportTest and doesn't require an Android device/emulator.
* Somewhat less realistic but allows simulating behavior that would be difficult or impossible with
* real Android.
*
* <p>NB: Unlike most robolectric tests, we run in {@link LooperMode.Mode#INSTRUMENTATION_TEST},
* meaning test cases don't run on the main thread. This supports the AbstractTransportTest approach
* where the test thread frequently blocks waiting for transport state changes to take effect.
*/
@RunWith(RobolectricTestRunner.class)
@LooperMode(Mode.INSTRUMENTATION_TEST)
public final class RobolectricBinderTransportTest extends AbstractTransportTest {
private final Application application = ApplicationProvider.getApplicationContext();
private final ObjectPool<ScheduledExecutorService> executorServicePool =
SharedResourcePool.forResource(GrpcUtil.TIMER_SERVICE);
private final ObjectPool<Executor> offloadExecutorPool =
SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
private final ObjectPool<Executor> serverExecutorPool =
SharedResourcePool.forResource(GrpcUtil.SHARED_CHANNEL_EXECUTOR);
private int nextServerAddress;
@Override
protected InternalServer newServer(List<ServerStreamTracer.Factory> streamTracerFactories) {
AndroidComponentAddress listenAddr = AndroidComponentAddress.forBindIntent(
new Intent()
.setClassName(application.getPackageName(), "HostService")
.setAction("io.grpc.action.BIND." + nextServerAddress++));
BinderServer binderServer =
new BinderServer.Builder()
.setListenAddress(listenAddr)
.setExecutorPool(serverExecutorPool)
.setExecutorServicePool(executorServicePool)
.setStreamTracerFactories(streamTracerFactories)
.build();
shadowOf(application)
.setComponentNameAndServiceForBindServiceForIntent(
listenAddr.asBindIntent(), listenAddr.getComponent(), binderServer.getHostBinder());
return binderServer;
}
@Override
protected InternalServer newServer(
int port, List<ServerStreamTracer.Factory> streamTracerFactories) {
if (port > 0) {
// TODO: TCP ports have no place in an *abstract* transport test. Replace with SocketAddress.
throw new UnsupportedOperationException();
}
return newServer(streamTracerFactories);
}
@Override
protected ManagedClientTransport newClientTransport(InternalServer server) {
BinderClientTransportFactory.Builder builder =
new BinderClientTransportFactory.Builder()
.setSourceContext(application)
.setScheduledExecutorPool(executorServicePool)
.setOffloadExecutorPool(offloadExecutorPool);
ClientTransportOptions options = new ClientTransportOptions();
options.setEagAttributes(eagAttrs());
options.setChannelLogger(transportLogger());
return new BinderTransport.BinderClientTransport(
builder.buildClientTransportFactory(),
(AndroidComponentAddress) server.getListenSocketAddress(),
options);
}
@Override
protected String testAuthority(InternalServer server) {
return ((AndroidComponentAddress) server.getListenSocketAddress()).getAuthority();
}
@Test
@Ignore("See BinderTransportTest#socketStats.")
@Override
public void socketStats() {}
@Test
@Ignore("See BinderTransportTest#flowControlPushBack")
@Override
public void flowControlPushBack() {}
@Test
@Ignore("See BinderTransportTest#serverAlreadyListening")
@Override
public void serverAlreadyListening() {}
}