diff --git a/binder/src/test/java/io/grpc/binder/RobolectricBinderSecurityTest.java b/binder/src/test/java/io/grpc/binder/RobolectricBinderSecurityTest.java index ab81fc6b6d..16f06ad81c 100644 --- a/binder/src/test/java/io/grpc/binder/RobolectricBinderSecurityTest.java +++ b/binder/src/test/java/io/grpc/binder/RobolectricBinderSecurityTest.java @@ -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,90 +37,117 @@ 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 controller; - private SomeService service; + private final ArrayBlockingQueue> 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.forRemoteComponent(context.getPackageName(), "HostService"); - AndroidComponentAddress listenAddress = AndroidComponentAddress.forContext(service); - ScheduledExecutorService executor = service.getExecutor(); + MethodDescriptor methodDesc = getMethodDescriptor(); + ServerCallHandler callHandler = + ServerCalls.asyncUnaryCall( + (req, respObserver) -> { + respObserver.onNext(req); + respObserver.onCompleted(); + }); + ServerMethodDefinition methodDef = + ServerMethodDefinition.create(methodDesc, callHandler); + ServerServiceDefinition def = + ServerServiceDefinition.builder(SERVICE_NAME).addMethod(methodDef).build(); + + IBinderReceiver binderReceiver = new IBinderReceiver(); + server = + BinderServerBuilder.forAddress(listenAddress, binderReceiver) + .addService(def) + .securityPolicy( + ServerSecurityPolicy.newBuilder() + .servicePolicy( + SERVICE_NAME, + new AsyncSecurityPolicy() { + @Override + public ListenableFuture checkAuthorizationAsync(int uid) { + SettableFuture status = SettableFuture.create(); + statusesToSet.add(status); + return status; + } + }) + .build()) + .build(); + try { + server.start(); + } catch (IOException e) { + throw new IllegalStateException(e); + } + + shadowOf(context) + .setComponentNameAndServiceForBindServiceForIntent( + listenAddress.asBindIntent(), + listenAddress.getComponent(), + checkNotNull(binderReceiver.get())); channel = BinderChannelBuilder.forAddress(listenAddress, context) - .executor(executor) - .scheduledExecutorService(executor) - .offloadExecutor(executor) .build(); - idleLoopers(); } @After public void tearDown() { channel.shutdownNow(); - controller.destroy(); + server.shutdownNow(); } @Test public void testAsyncServerSecurityPolicy_failed_returnsFailureStatus() throws Exception { ListenableFuture status = makeCall(); - service.setSecurityPolicyStatusWhenReady(Status.ALREADY_EXISTS); - idleLoopers(); + statusesToSet.take().set(Status.ALREADY_EXISTS); - assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.ALREADY_EXISTS); + assertThat(status.get().getCode()).isEqualTo(Status.Code.ALREADY_EXISTS); } @Test public void testAsyncServerSecurityPolicy_failedFuture_failsWithCodeInternal() throws Exception { ListenableFuture status = makeCall(); - service.setSecurityPolicyFailed(new IllegalStateException("oops")); - idleLoopers(); + statusesToSet.take().setException(new IllegalStateException("oops")); - assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.INTERNAL); + assertThat(status.get().getCode()).isEqualTo(Status.Code.INTERNAL); } @Test public void testAsyncServerSecurityPolicy_allowed_returnsOkStatus() throws Exception { ListenableFuture status = makeCall(); - service.setSecurityPolicyStatusWhenReady(Status.OK); - idleLoopers(); + statusesToSet.take().set(Status.OK); - assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.OK); + assertThat(status.get().getCode()).isEqualTo(Status.Code.OK); } private ListenableFuture makeCall() { - ClientCall call = - channel.newCall( - getMethodDescriptor(), CallOptions.DEFAULT.withExecutor(service.getExecutor())); + ClientCall call = channel.newCall(getMethodDescriptor(), CallOptions.DEFAULT); ListenableFuture responseFuture = ClientCalls.futureUnaryCall(call, Empty.getDefaultInstance()); - idleLoopers(); - return Futures.catching( Futures.transform(responseFuture, unused -> Status.OK, directExecutor()), StatusRuntimeException.class, @@ -133,10 +155,6 @@ public final class RobolectricBinderSecurityTest { directExecutor()); } - private static void idleLoopers() { - shadowOf(Looper.getMainLooper()).idle(); - } - private static MethodDescriptor getMethodDescriptor() { MethodDescriptor.Marshaller marshaller = ProtoLiteUtils.marshaller(Empty.getDefaultInstance()); @@ -147,106 +165,4 @@ public final class RobolectricBinderSecurityTest { .setSampledToLocalTracing(true) .build(); } - - private static class SomeService extends LifecycleService { - - private final IBinderReceiver binderReceiver = new IBinderReceiver(); - private final ArrayBlockingQueue> statusesToSet = - new ArrayBlockingQueue<>(128); - private Server server; - private final ScheduledExecutorService scheduledExecutorService = - new MainThreadScheduledExecutorService(); - - @Override - public void onCreate() { - super.onCreate(); - - MethodDescriptor methodDesc = getMethodDescriptor(); - ServerCallHandler callHandler = - ServerCalls.asyncUnaryCall( - (req, respObserver) -> { - respObserver.onNext(req); - respObserver.onCompleted(); - }); - ServerMethodDefinition methodDef = - ServerMethodDefinition.create(methodDesc, callHandler); - ServerServiceDefinition def = - ServerServiceDefinition.builder(SERVICE_NAME).addMethod(methodDef).build(); - - server = - BinderServerBuilder.forAddress(AndroidComponentAddress.forContext(this), binderReceiver) - .addService(def) - .securityPolicy( - ServerSecurityPolicy.newBuilder() - .servicePolicy( - SERVICE_NAME, - new AsyncSecurityPolicy() { - @Override - public ListenableFuture checkAuthorizationAsync(int uid) { - return Futures.submitAsync( - () -> { - SettableFuture status = SettableFuture.create(); - statusesToSet.add(status); - return status; - }, - getExecutor()); - } - }) - .build()) - .executor(getExecutor()) - .scheduledExecutorService(getExecutor()) - .build(); - try { - server.start(); - } catch (IOException e) { - throw new IllegalStateException(e); - } - - Application context = ApplicationProvider.getApplicationContext(); - ComponentName componentName = new ComponentName(context, SomeService.class); - shadowOf(context) - .setComponentNameAndServiceForBindService( - componentName, checkNotNull(binderReceiver.get())); - } - - /** - * 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 getNextEnqueuedStatus() { - @Nullable SettableFuture 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(); - server.shutdownNow(); - } - - /** A future representing a task submitted to a {@link Handler}. */ - } }