mirror of https://github.com/grpc/grpc-java.git
Simplify RobolectricBinderSecurityTest (#12058)
- Use INSTRUMENTATION_TEST @LooperMode to avoid custom Executors and idleLoopers() toil. - No android.app.Service is actually needed with Robolectric.
This commit is contained in:
parent
80cc988b3c
commit
64fe061ccd
|
@ -22,11 +22,6 @@ import static com.google.common.util.concurrent.MoreExecutors.directExecutor;
|
|||
import static org.robolectric.Shadows.shadowOf;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.os.Looper;
|
||||
import androidx.lifecycle.LifecycleService;
|
||||
import androidx.test.core.app.ApplicationProvider;
|
||||
import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
|
@ -42,124 +37,35 @@ import io.grpc.ServerMethodDefinition;
|
|||
import io.grpc.ServerServiceDefinition;
|
||||
import io.grpc.Status;
|
||||
import io.grpc.StatusRuntimeException;
|
||||
import io.grpc.binder.internal.MainThreadScheduledExecutorService;
|
||||
import io.grpc.protobuf.lite.ProtoLiteUtils;
|
||||
import io.grpc.stub.ClientCalls;
|
||||
import io.grpc.stub.ServerCalls;
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import javax.annotation.Nullable;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.Robolectric;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.android.controller.ServiceController;
|
||||
import org.robolectric.annotation.LooperMode;
|
||||
import org.robolectric.annotation.LooperMode.Mode;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
@LooperMode(Mode.INSTRUMENTATION_TEST)
|
||||
public final class RobolectricBinderSecurityTest {
|
||||
|
||||
private static final String SERVICE_NAME = "fake_service";
|
||||
private static final String FULL_METHOD_NAME = "fake_service/fake_method";
|
||||
private final Application context = ApplicationProvider.getApplicationContext();
|
||||
private ServiceController<SomeService> controller;
|
||||
private SomeService service;
|
||||
private final ArrayBlockingQueue<SettableFuture<Status>> statusesToSet =
|
||||
new ArrayBlockingQueue<>(128);
|
||||
private ManagedChannel channel;
|
||||
private Server server;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
controller = Robolectric.buildService(SomeService.class);
|
||||
service = controller.create().get();
|
||||
|
||||
AndroidComponentAddress listenAddress = AndroidComponentAddress.forContext(service);
|
||||
ScheduledExecutorService executor = service.getExecutor();
|
||||
channel =
|
||||
BinderChannelBuilder.forAddress(listenAddress, context)
|
||||
.executor(executor)
|
||||
.scheduledExecutorService(executor)
|
||||
.offloadExecutor(executor)
|
||||
.build();
|
||||
idleLoopers();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
channel.shutdownNow();
|
||||
controller.destroy();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncServerSecurityPolicy_failed_returnsFailureStatus() throws Exception {
|
||||
ListenableFuture<Status> status = makeCall();
|
||||
service.setSecurityPolicyStatusWhenReady(Status.ALREADY_EXISTS);
|
||||
idleLoopers();
|
||||
|
||||
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncServerSecurityPolicy_failedFuture_failsWithCodeInternal() throws Exception {
|
||||
ListenableFuture<Status> status = makeCall();
|
||||
service.setSecurityPolicyFailed(new IllegalStateException("oops"));
|
||||
idleLoopers();
|
||||
|
||||
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.INTERNAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncServerSecurityPolicy_allowed_returnsOkStatus() throws Exception {
|
||||
ListenableFuture<Status> status = makeCall();
|
||||
service.setSecurityPolicyStatusWhenReady(Status.OK);
|
||||
idleLoopers();
|
||||
|
||||
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.OK);
|
||||
}
|
||||
|
||||
private ListenableFuture<Status> makeCall() {
|
||||
ClientCall<Empty, Empty> call =
|
||||
channel.newCall(
|
||||
getMethodDescriptor(), CallOptions.DEFAULT.withExecutor(service.getExecutor()));
|
||||
ListenableFuture<Empty> responseFuture =
|
||||
ClientCalls.futureUnaryCall(call, Empty.getDefaultInstance());
|
||||
|
||||
idleLoopers();
|
||||
|
||||
return Futures.catching(
|
||||
Futures.transform(responseFuture, unused -> Status.OK, directExecutor()),
|
||||
StatusRuntimeException.class,
|
||||
StatusRuntimeException::getStatus,
|
||||
directExecutor());
|
||||
}
|
||||
|
||||
private static void idleLoopers() {
|
||||
shadowOf(Looper.getMainLooper()).idle();
|
||||
}
|
||||
|
||||
private static MethodDescriptor<Empty, Empty> getMethodDescriptor() {
|
||||
MethodDescriptor.Marshaller<Empty> marshaller =
|
||||
ProtoLiteUtils.marshaller(Empty.getDefaultInstance());
|
||||
|
||||
return MethodDescriptor.newBuilder(marshaller, marshaller)
|
||||
.setFullMethodName(FULL_METHOD_NAME)
|
||||
.setType(MethodDescriptor.MethodType.UNARY)
|
||||
.setSampledToLocalTracing(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static class SomeService extends LifecycleService {
|
||||
|
||||
private final IBinderReceiver binderReceiver = new IBinderReceiver();
|
||||
private final ArrayBlockingQueue<SettableFuture<Status>> statusesToSet =
|
||||
new ArrayBlockingQueue<>(128);
|
||||
private Server server;
|
||||
private final ScheduledExecutorService scheduledExecutorService =
|
||||
new MainThreadScheduledExecutorService();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
AndroidComponentAddress listenAddress =
|
||||
AndroidComponentAddress.forRemoteComponent(context.getPackageName(), "HostService");
|
||||
|
||||
MethodDescriptor<Empty, Empty> methodDesc = getMethodDescriptor();
|
||||
ServerCallHandler<Empty, Empty> callHandler =
|
||||
|
@ -173,8 +79,9 @@ public final class RobolectricBinderSecurityTest {
|
|||
ServerServiceDefinition def =
|
||||
ServerServiceDefinition.builder(SERVICE_NAME).addMethod(methodDef).build();
|
||||
|
||||
IBinderReceiver binderReceiver = new IBinderReceiver();
|
||||
server =
|
||||
BinderServerBuilder.forAddress(AndroidComponentAddress.forContext(this), binderReceiver)
|
||||
BinderServerBuilder.forAddress(listenAddress, binderReceiver)
|
||||
.addService(def)
|
||||
.securityPolicy(
|
||||
ServerSecurityPolicy.newBuilder()
|
||||
|
@ -183,18 +90,12 @@ public final class RobolectricBinderSecurityTest {
|
|||
new AsyncSecurityPolicy() {
|
||||
@Override
|
||||
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
|
||||
return Futures.submitAsync(
|
||||
() -> {
|
||||
SettableFuture<Status> status = SettableFuture.create();
|
||||
statusesToSet.add(status);
|
||||
return status;
|
||||
},
|
||||
getExecutor());
|
||||
}
|
||||
})
|
||||
.build())
|
||||
.executor(getExecutor())
|
||||
.scheduledExecutorService(getExecutor())
|
||||
.build();
|
||||
try {
|
||||
server.start();
|
||||
|
@ -202,51 +103,66 @@ public final class RobolectricBinderSecurityTest {
|
|||
throw new IllegalStateException(e);
|
||||
}
|
||||
|
||||
Application context = ApplicationProvider.getApplicationContext();
|
||||
ComponentName componentName = new ComponentName(context, SomeService.class);
|
||||
shadowOf(context)
|
||||
.setComponentNameAndServiceForBindService(
|
||||
componentName, checkNotNull(binderReceiver.get()));
|
||||
.setComponentNameAndServiceForBindServiceForIntent(
|
||||
listenAddress.asBindIntent(),
|
||||
listenAddress.getComponent(),
|
||||
checkNotNull(binderReceiver.get()));
|
||||
channel =
|
||||
BinderChannelBuilder.forAddress(listenAddress, context)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an {@link ScheduledExecutorService} under which all of the gRPC computations run. The
|
||||
* execution of any pending tasks on this executor can be triggered via {@link #idleLoopers()}.
|
||||
*/
|
||||
ScheduledExecutorService getExecutor() {
|
||||
return scheduledExecutorService;
|
||||
}
|
||||
|
||||
void setSecurityPolicyStatusWhenReady(Status status) {
|
||||
getNextEnqueuedStatus().set(status);
|
||||
}
|
||||
|
||||
void setSecurityPolicyFailed(Exception e) {
|
||||
getNextEnqueuedStatus().setException(e);
|
||||
}
|
||||
|
||||
private SettableFuture<Status> getNextEnqueuedStatus() {
|
||||
@Nullable SettableFuture<Status> future = statusesToSet.poll();
|
||||
while (future == null) {
|
||||
// Keep idling until the future is available.
|
||||
idleLoopers();
|
||||
future = statusesToSet.poll();
|
||||
}
|
||||
return checkNotNull(future);
|
||||
}
|
||||
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
super.onBind(intent);
|
||||
return checkNotNull(binderReceiver.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
@After
|
||||
public void tearDown() {
|
||||
channel.shutdownNow();
|
||||
server.shutdownNow();
|
||||
}
|
||||
|
||||
/** A future representing a task submitted to a {@link Handler}. */
|
||||
@Test
|
||||
public void testAsyncServerSecurityPolicy_failed_returnsFailureStatus() throws Exception {
|
||||
ListenableFuture<Status> status = makeCall();
|
||||
statusesToSet.take().set(Status.ALREADY_EXISTS);
|
||||
|
||||
assertThat(status.get().getCode()).isEqualTo(Status.Code.ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncServerSecurityPolicy_failedFuture_failsWithCodeInternal() throws Exception {
|
||||
ListenableFuture<Status> status = makeCall();
|
||||
statusesToSet.take().setException(new IllegalStateException("oops"));
|
||||
|
||||
assertThat(status.get().getCode()).isEqualTo(Status.Code.INTERNAL);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAsyncServerSecurityPolicy_allowed_returnsOkStatus() throws Exception {
|
||||
ListenableFuture<Status> status = makeCall();
|
||||
statusesToSet.take().set(Status.OK);
|
||||
|
||||
assertThat(status.get().getCode()).isEqualTo(Status.Code.OK);
|
||||
}
|
||||
|
||||
private ListenableFuture<Status> makeCall() {
|
||||
ClientCall<Empty, Empty> call = channel.newCall(getMethodDescriptor(), CallOptions.DEFAULT);
|
||||
ListenableFuture<Empty> responseFuture =
|
||||
ClientCalls.futureUnaryCall(call, Empty.getDefaultInstance());
|
||||
|
||||
return Futures.catching(
|
||||
Futures.transform(responseFuture, unused -> Status.OK, directExecutor()),
|
||||
StatusRuntimeException.class,
|
||||
StatusRuntimeException::getStatus,
|
||||
directExecutor());
|
||||
}
|
||||
|
||||
private static MethodDescriptor<Empty, Empty> getMethodDescriptor() {
|
||||
MethodDescriptor.Marshaller<Empty> marshaller =
|
||||
ProtoLiteUtils.marshaller(Empty.getDefaultInstance());
|
||||
|
||||
return MethodDescriptor.newBuilder(marshaller, marshaller)
|
||||
.setFullMethodName(FULL_METHOD_NAME)
|
||||
.setType(MethodDescriptor.MethodType.UNARY)
|
||||
.setSampledToLocalTracing(true)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue