Skip to content

Commit 64fe061

Browse files
authored
Simplify RobolectricBinderSecurityTest (#12058)
- Use INSTRUMENTATION_TEST @LooperMode to avoid custom Executors and idleLoopers() toil. - No android.app.Service is actually needed with Robolectric.
1 parent 80cc988 commit 64fe061

File tree

1 file changed

+57
-141
lines changed

1 file changed

+57
-141
lines changed

binder/src/test/java/io/grpc/binder/RobolectricBinderSecurityTest.java

Lines changed: 57 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@
2222
import static org.robolectric.Shadows.shadowOf;
2323

2424
import android.app.Application;
25-
import android.content.ComponentName;
26-
import android.content.Intent;
27-
import android.os.IBinder;
28-
import android.os.Looper;
29-
import androidx.lifecycle.LifecycleService;
3025
import androidx.test.core.app.ApplicationProvider;
3126
import com.google.common.util.concurrent.Futures;
3227
import com.google.common.util.concurrent.ListenableFuture;
@@ -42,101 +37,124 @@
4237
import io.grpc.ServerServiceDefinition;
4338
import io.grpc.Status;
4439
import io.grpc.StatusRuntimeException;
45-
import io.grpc.binder.internal.MainThreadScheduledExecutorService;
4640
import io.grpc.protobuf.lite.ProtoLiteUtils;
4741
import io.grpc.stub.ClientCalls;
4842
import io.grpc.stub.ServerCalls;
4943
import java.io.IOException;
5044
import java.util.concurrent.ArrayBlockingQueue;
51-
import java.util.concurrent.ScheduledExecutorService;
52-
import javax.annotation.Nullable;
5345
import org.junit.After;
5446
import org.junit.Before;
5547
import org.junit.Test;
5648
import org.junit.runner.RunWith;
57-
import org.robolectric.Robolectric;
5849
import org.robolectric.RobolectricTestRunner;
59-
import org.robolectric.android.controller.ServiceController;
50+
import org.robolectric.annotation.LooperMode;
51+
import org.robolectric.annotation.LooperMode.Mode;
6052

6153
@RunWith(RobolectricTestRunner.class)
54+
@LooperMode(Mode.INSTRUMENTATION_TEST)
6255
public final class RobolectricBinderSecurityTest {
6356

6457
private static final String SERVICE_NAME = "fake_service";
6558
private static final String FULL_METHOD_NAME = "fake_service/fake_method";
6659
private final Application context = ApplicationProvider.getApplicationContext();
67-
private ServiceController<SomeService> controller;
68-
private SomeService service;
60+
private final ArrayBlockingQueue<SettableFuture<Status>> statusesToSet =
61+
new ArrayBlockingQueue<>(128);
6962
private ManagedChannel channel;
63+
private Server server;
7064

7165
@Before
7266
public void setUp() {
73-
controller = Robolectric.buildService(SomeService.class);
74-
service = controller.create().get();
67+
AndroidComponentAddress listenAddress =
68+
AndroidComponentAddress.forRemoteComponent(context.getPackageName(), "HostService");
69+
70+
MethodDescriptor<Empty, Empty> methodDesc = getMethodDescriptor();
71+
ServerCallHandler<Empty, Empty> callHandler =
72+
ServerCalls.asyncUnaryCall(
73+
(req, respObserver) -> {
74+
respObserver.onNext(req);
75+
respObserver.onCompleted();
76+
});
77+
ServerMethodDefinition<Empty, Empty> methodDef =
78+
ServerMethodDefinition.create(methodDesc, callHandler);
79+
ServerServiceDefinition def =
80+
ServerServiceDefinition.builder(SERVICE_NAME).addMethod(methodDef).build();
81+
82+
IBinderReceiver binderReceiver = new IBinderReceiver();
83+
server =
84+
BinderServerBuilder.forAddress(listenAddress, binderReceiver)
85+
.addService(def)
86+
.securityPolicy(
87+
ServerSecurityPolicy.newBuilder()
88+
.servicePolicy(
89+
SERVICE_NAME,
90+
new AsyncSecurityPolicy() {
91+
@Override
92+
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
93+
SettableFuture<Status> status = SettableFuture.create();
94+
statusesToSet.add(status);
95+
return status;
96+
}
97+
})
98+
.build())
99+
.build();
100+
try {
101+
server.start();
102+
} catch (IOException e) {
103+
throw new IllegalStateException(e);
104+
}
75105

76-
AndroidComponentAddress listenAddress = AndroidComponentAddress.forContext(service);
77-
ScheduledExecutorService executor = service.getExecutor();
106+
shadowOf(context)
107+
.setComponentNameAndServiceForBindServiceForIntent(
108+
listenAddress.asBindIntent(),
109+
listenAddress.getComponent(),
110+
checkNotNull(binderReceiver.get()));
78111
channel =
79112
BinderChannelBuilder.forAddress(listenAddress, context)
80-
.executor(executor)
81-
.scheduledExecutorService(executor)
82-
.offloadExecutor(executor)
83113
.build();
84-
idleLoopers();
85114
}
86115

87116
@After
88117
public void tearDown() {
89118
channel.shutdownNow();
90-
controller.destroy();
119+
server.shutdownNow();
91120
}
92121

93122
@Test
94123
public void testAsyncServerSecurityPolicy_failed_returnsFailureStatus() throws Exception {
95124
ListenableFuture<Status> status = makeCall();
96-
service.setSecurityPolicyStatusWhenReady(Status.ALREADY_EXISTS);
97-
idleLoopers();
125+
statusesToSet.take().set(Status.ALREADY_EXISTS);
98126

99-
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.ALREADY_EXISTS);
127+
assertThat(status.get().getCode()).isEqualTo(Status.Code.ALREADY_EXISTS);
100128
}
101129

102130
@Test
103131
public void testAsyncServerSecurityPolicy_failedFuture_failsWithCodeInternal() throws Exception {
104132
ListenableFuture<Status> status = makeCall();
105-
service.setSecurityPolicyFailed(new IllegalStateException("oops"));
106-
idleLoopers();
133+
statusesToSet.take().setException(new IllegalStateException("oops"));
107134

108-
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.INTERNAL);
135+
assertThat(status.get().getCode()).isEqualTo(Status.Code.INTERNAL);
109136
}
110137

111138
@Test
112139
public void testAsyncServerSecurityPolicy_allowed_returnsOkStatus() throws Exception {
113140
ListenableFuture<Status> status = makeCall();
114-
service.setSecurityPolicyStatusWhenReady(Status.OK);
115-
idleLoopers();
141+
statusesToSet.take().set(Status.OK);
116142

117-
assertThat(Futures.getDone(status).getCode()).isEqualTo(Status.Code.OK);
143+
assertThat(status.get().getCode()).isEqualTo(Status.Code.OK);
118144
}
119145

120146
private ListenableFuture<Status> makeCall() {
121-
ClientCall<Empty, Empty> call =
122-
channel.newCall(
123-
getMethodDescriptor(), CallOptions.DEFAULT.withExecutor(service.getExecutor()));
147+
ClientCall<Empty, Empty> call = channel.newCall(getMethodDescriptor(), CallOptions.DEFAULT);
124148
ListenableFuture<Empty> responseFuture =
125149
ClientCalls.futureUnaryCall(call, Empty.getDefaultInstance());
126150

127-
idleLoopers();
128-
129151
return Futures.catching(
130152
Futures.transform(responseFuture, unused -> Status.OK, directExecutor()),
131153
StatusRuntimeException.class,
132154
StatusRuntimeException::getStatus,
133155
directExecutor());
134156
}
135157

136-
private static void idleLoopers() {
137-
shadowOf(Looper.getMainLooper()).idle();
138-
}
139-
140158
private static MethodDescriptor<Empty, Empty> getMethodDescriptor() {
141159
MethodDescriptor.Marshaller<Empty> marshaller =
142160
ProtoLiteUtils.marshaller(Empty.getDefaultInstance());
@@ -147,106 +165,4 @@ private static MethodDescriptor<Empty, Empty> getMethodDescriptor() {
147165
.setSampledToLocalTracing(true)
148166
.build();
149167
}
150-
151-
private static class SomeService extends LifecycleService {
152-
153-
private final IBinderReceiver binderReceiver = new IBinderReceiver();
154-
private final ArrayBlockingQueue<SettableFuture<Status>> statusesToSet =
155-
new ArrayBlockingQueue<>(128);
156-
private Server server;
157-
private final ScheduledExecutorService scheduledExecutorService =
158-
new MainThreadScheduledExecutorService();
159-
160-
@Override
161-
public void onCreate() {
162-
super.onCreate();
163-
164-
MethodDescriptor<Empty, Empty> methodDesc = getMethodDescriptor();
165-
ServerCallHandler<Empty, Empty> callHandler =
166-
ServerCalls.asyncUnaryCall(
167-
(req, respObserver) -> {
168-
respObserver.onNext(req);
169-
respObserver.onCompleted();
170-
});
171-
ServerMethodDefinition<Empty, Empty> methodDef =
172-
ServerMethodDefinition.create(methodDesc, callHandler);
173-
ServerServiceDefinition def =
174-
ServerServiceDefinition.builder(SERVICE_NAME).addMethod(methodDef).build();
175-
176-
server =
177-
BinderServerBuilder.forAddress(AndroidComponentAddress.forContext(this), binderReceiver)
178-
.addService(def)
179-
.securityPolicy(
180-
ServerSecurityPolicy.newBuilder()
181-
.servicePolicy(
182-
SERVICE_NAME,
183-
new AsyncSecurityPolicy() {
184-
@Override
185-
public ListenableFuture<Status> checkAuthorizationAsync(int uid) {
186-
return Futures.submitAsync(
187-
() -> {
188-
SettableFuture<Status> status = SettableFuture.create();
189-
statusesToSet.add(status);
190-
return status;
191-
},
192-
getExecutor());
193-
}
194-
})
195-
.build())
196-
.executor(getExecutor())
197-
.scheduledExecutorService(getExecutor())
198-
.build();
199-
try {
200-
server.start();
201-
} catch (IOException e) {
202-
throw new IllegalStateException(e);
203-
}
204-
205-
Application context = ApplicationProvider.getApplicationContext();
206-
ComponentName componentName = new ComponentName(context, SomeService.class);
207-
shadowOf(context)
208-
.setComponentNameAndServiceForBindService(
209-
componentName, checkNotNull(binderReceiver.get()));
210-
}
211-
212-
/**
213-
* Returns an {@link ScheduledExecutorService} under which all of the gRPC computations run. The
214-
* execution of any pending tasks on this executor can be triggered via {@link #idleLoopers()}.
215-
*/
216-
ScheduledExecutorService getExecutor() {
217-
return scheduledExecutorService;
218-
}
219-
220-
void setSecurityPolicyStatusWhenReady(Status status) {
221-
getNextEnqueuedStatus().set(status);
222-
}
223-
224-
void setSecurityPolicyFailed(Exception e) {
225-
getNextEnqueuedStatus().setException(e);
226-
}
227-
228-
private SettableFuture<Status> getNextEnqueuedStatus() {
229-
@Nullable SettableFuture<Status> future = statusesToSet.poll();
230-
while (future == null) {
231-
// Keep idling until the future is available.
232-
idleLoopers();
233-
future = statusesToSet.poll();
234-
}
235-
return checkNotNull(future);
236-
}
237-
238-
@Override
239-
public IBinder onBind(Intent intent) {
240-
super.onBind(intent);
241-
return checkNotNull(binderReceiver.get());
242-
}
243-
244-
@Override
245-
public void onDestroy() {
246-
super.onDestroy();
247-
server.shutdownNow();
248-
}
249-
250-
/** A future representing a task submitted to a {@link Handler}. */
251-
}
252168
}

0 commit comments

Comments
 (0)