• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 The Android Open Source Project
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 com.android.media.audiotestharness.client.grpc;
18 
19 import static org.junit.Assert.assertArrayEquals;
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertNotNull;
22 import static org.junit.Assert.assertTrue;
23 import static org.mockito.ArgumentMatchers.any;
24 import static org.mockito.Mockito.verify;
25 
26 import com.android.media.audiotestharness.proto.AudioTestHarnessGrpc;
27 
28 import com.google.common.util.concurrent.ListeningExecutorService;
29 import com.google.common.util.concurrent.MoreExecutors;
30 
31 import io.grpc.ManagedChannel;
32 import io.grpc.ManagedChannelBuilder;
33 import io.grpc.inprocess.InProcessChannelBuilder;
34 import io.grpc.inprocess.InProcessServerBuilder;
35 import io.grpc.testing.GrpcCleanupRule;
36 
37 import org.hamcrest.CoreMatchers;
38 import org.junit.Before;
39 import org.junit.Rule;
40 import org.junit.Test;
41 import org.junit.rules.ExpectedException;
42 import org.junit.runner.RunWith;
43 import org.junit.runners.JUnit4;
44 import org.mockito.ArgumentCaptor;
45 import org.mockito.Mock;
46 import org.mockito.junit.MockitoJUnit;
47 import org.mockito.junit.MockitoRule;
48 
49 import java.nio.ByteBuffer;
50 import java.nio.ByteOrder;
51 import java.time.Duration;
52 import java.util.concurrent.ScheduledExecutorService;
53 import java.util.concurrent.TimeUnit;
54 
55 @RunWith(JUnit4.class)
56 public class GrpcAudioCaptureStreamTests {
57 
58     @Rule public GrpcCleanupRule mGrpcCleanupRule = new GrpcCleanupRule();
59 
60     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
61 
62     @Rule public ExpectedException mExceptionRule = ExpectedException.none();
63 
64     @Mock ScheduledExecutorService mScheduledExecutorService;
65 
66     private ListeningExecutorService mListeningExecutorService;
67 
68     private GrpcAudioCaptureStreamFactory mGrpcAudioCaptureStreamFactory;
69 
70     private AudioTestHarnessGrpc.AudioTestHarnessStub mAudioTestHarnessStub;
71 
72     @Before
setUp()73     public void setUp() throws Exception {
74         mListeningExecutorService = MoreExecutors.newDirectExecutorService();
75 
76         String serverName = InProcessServerBuilder.generateName();
77 
78         mGrpcCleanupRule.register(
79                 InProcessServerBuilder.forName(serverName)
80                         .executor(mListeningExecutorService)
81                         .addService(new AudioTestHarnessTestImpl())
82                         .build()
83                         .start());
84 
85         ManagedChannel channel =
86                 mGrpcCleanupRule.register(
87                         InProcessChannelBuilder.forName(serverName)
88                                 .executor(mListeningExecutorService)
89                                 .build());
90         mAudioTestHarnessStub = AudioTestHarnessGrpc.newStub(channel);
91 
92         mGrpcAudioCaptureStreamFactory =
93                 GrpcAudioCaptureStreamFactory.create(mScheduledExecutorService);
94     }
95 
96     @Test
create_returnsNotNullInstance()97     public void create_returnsNotNullInstance() throws Exception {
98         assertNotNull(
99                 GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService));
100     }
101 
102     @Test
create_schedulesCancellationTaskWithProperDeadline()103     public void create_schedulesCancellationTaskWithProperDeadline() throws Exception {
104         ArgumentCaptor<Long> timeValueCaptor = ArgumentCaptor.forClass(Long.class);
105         ArgumentCaptor<TimeUnit> timeUnitCaptor = ArgumentCaptor.forClass(TimeUnit.class);
106 
107         GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService);
108 
109         // Grab the timeout from the call to the Scheduled Executor Service as a Duration.
110         verify(mScheduledExecutorService)
111                 .schedule((Runnable) any(), timeValueCaptor.capture(), timeUnitCaptor.capture());
112         Duration actualScheduledDuration =
113                 Duration.ofNanos(
114                         TimeUnit.NANOSECONDS.convert(
115                                 timeValueCaptor.getValue(), timeUnitCaptor.getValue()));
116 
117         // Ensure that we are within our expected timeout within a 1s epsilon.
118         Duration difference = actualScheduledDuration.minus(Duration.ofHours(1)).abs();
119         assertTrue(difference.getSeconds() < 1L);
120     }
121 
122     @Test(expected = NullPointerException.class)
123     public void newStream_throwsNullPointerException_nullStub() throws Exception {
124         assertNotNull(mGrpcAudioCaptureStreamFactory.newStream(/* audioTestHarnessStub= */ null));
125     }
126 
127     @Test(expected = NullPointerException.class)
128     public void create_throwsNullPointerException_nullStub() throws Exception {
129         GrpcAudioCaptureStream.create(/* audioTestHarnessStub= */ null, mScheduledExecutorService);
130     }
131 
132     @Test(expected = NullPointerException.class)
133     public void create_throwsNullPointerException_nullScheduledExecutorService() throws Exception {
134         GrpcAudioCaptureStream.create(mAudioTestHarnessStub, /* scheduledExecutorService= */ null);
135     }
136 
137     @Test
138     public void available_throwsProperIOException_grpcError() throws Exception {
139         expectGrpcCommunicationErrorException();
140         GrpcAudioCaptureStream grpcAudioCaptureStream = buildCaptureStreamWithPendingGrpcError();
141 
142         grpcAudioCaptureStream.available();
143     }
144 
145     @Test
146     public void read_returnsProperDataFromGrpc() throws Exception {
147         GrpcAudioCaptureStream grpcAudioCaptureStream =
148                 GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService);
149 
150         byte[] readBytes = new byte[AudioTestHarnessTestImpl.MESSAGE.length];
151 
152         int numBytesRead = grpcAudioCaptureStream.read(readBytes);
153 
154         assertEquals(AudioTestHarnessTestImpl.MESSAGE.length, numBytesRead);
155         assertArrayEquals(AudioTestHarnessTestImpl.MESSAGE, readBytes);
156     }
157 
158     @Test
159     public void read_singleByte_throwsProperIOException_whenStreamClosed() throws Exception {
160         expectInternalErrorException();
161         GrpcAudioCaptureStream grpcAudioCaptureStream =
162                 GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService);
163         grpcAudioCaptureStream.close();
164 
165         grpcAudioCaptureStream.read();
166     }
167 
168     @Test
169     public void read_multipleBytes_throwsProperIOException_whenStreamClosed() throws Exception {
170         expectInternalErrorException();
171         GrpcAudioCaptureStream grpcAudioCaptureStream =
172                 GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService);
173 
174         grpcAudioCaptureStream.close();
175 
176         grpcAudioCaptureStream.read(new byte[AudioTestHarnessTestImpl.MESSAGE.length]);
177     }
178 
179     @Test
180     public void read_multipleBytesWithOffset_throwsProperIOException_whenStreamClosed()
181             throws Exception {
182         expectInternalErrorException();
183         GrpcAudioCaptureStream grpcAudioCaptureStream =
184                 GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService);
185         grpcAudioCaptureStream.close();
186 
187         grpcAudioCaptureStream.read(
188                 new byte[AudioTestHarnessTestImpl.MESSAGE.length], /* off= */ 1, /* len= */ 2);
189     }
190 
191     @Test
192     public void read_throwsIOException_grpcError() throws Exception {
193         expectGrpcCommunicationErrorException();
194         GrpcAudioCaptureStream grpcAudioCaptureStream = buildCaptureStreamWithPendingGrpcError();
195 
196         grpcAudioCaptureStream.read(new byte[AudioTestHarnessTestImpl.MESSAGE.length]);
197     }
198 
199     @Test
200     public void read_shortSamples_readsAsExpected() throws Exception {
201         GrpcAudioCaptureStream grpcAudioCaptureStream =
202                 GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService);
203         short[] expected = new short[AudioTestHarnessTestImpl.MESSAGE.length / 2];
204         ByteBuffer.wrap(AudioTestHarnessTestImpl.MESSAGE)
205                 .order(ByteOrder.LITTLE_ENDIAN)
206                 .asShortBuffer()
207                 .get(expected);
208 
209         short[] actual = new short[AudioTestHarnessTestImpl.MESSAGE.length / 2];
210         int numRead =
211                 grpcAudioCaptureStream.read(actual, /* offset= */ 0, /* max= */ actual.length);
212 
213         assertArrayEquals(expected, actual);
214         assertEquals(AudioTestHarnessTestImpl.MESSAGE.length / 2, numRead);
215     }
216 
217     @Test(expected = IllegalArgumentException.class)
218     public void read_shortSamples_throwsIllegalArgumentException_negativeOffset() throws Exception {
219         GrpcAudioCaptureStream grpcAudioCaptureStream =
220                 GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService);
221         grpcAudioCaptureStream.read(/* samples= */ new short[4], /* offset= */ -25, /* max= */ 1);
222     }
223 
224     @Test(expected = IllegalArgumentException.class)
225     public void read_shortSamples_throwsIllegalArgumentException_offsetTooLarge() throws Exception {
226         GrpcAudioCaptureStream grpcAudioCaptureStream =
227                 GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService);
228         grpcAudioCaptureStream.read(/* samples= */ new short[4], /* offset= */ 25, /* max= */ 1);
229     }
230 
231     @Test(expected = IllegalArgumentException.class)
232     public void read_shortSamples_throwsIllegalArgumentException_negativeLen() throws Exception {
233         GrpcAudioCaptureStream grpcAudioCaptureStream =
234                 GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService);
235         grpcAudioCaptureStream.read(/* samples= */ new short[4], /* offset= */ 0, /* max= */ -24);
236     }
237 
238     @Test(expected = IllegalArgumentException.class)
239     public void read_shortSamples_throwsIllegalArgumentException_lenTooLarge() throws Exception {
240         GrpcAudioCaptureStream grpcAudioCaptureStream =
241                 GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService);
242         grpcAudioCaptureStream.read(/* samples= */ new short[4], /* offset= */ 0, /* max= */ 24);
243     }
244 
245     @Test
246     public void reset_throwsProperIOException_whenStreamClosed() throws Exception {
247         expectInternalErrorException();
248         GrpcAudioCaptureStream grpcAudioCaptureStream =
249                 GrpcAudioCaptureStream.create(mAudioTestHarnessStub, mScheduledExecutorService);
250         grpcAudioCaptureStream.close();
251 
252         grpcAudioCaptureStream.reset();
253     }
254 
255     @Test
256     public void reset_throwsProperIOException_grpcError() throws Exception {
257         expectGrpcCommunicationErrorException();
258         GrpcAudioCaptureStream grpcAudioCaptureStream = buildCaptureStreamWithPendingGrpcError();
259 
260         grpcAudioCaptureStream.reset();
261     }
262 
263     @Test
264     public void skip_throwsProperIOException_grpcError() throws Exception {
265         expectGrpcCommunicationErrorException();
266         GrpcAudioCaptureStream grpcAudioCaptureStream = buildCaptureStreamWithPendingGrpcError();
267 
268         grpcAudioCaptureStream.skip(/* n= */ 1);
269     }
270 
271     /**
272      * Builds a new {@link GrpcAudioCaptureStream} with a pending gRPC error internally.
273      *
274      * <p>This method is used to verify that gRPC errors take precedence and are propagated properly
275      * to callers.
276      */
277     private GrpcAudioCaptureStream buildCaptureStreamWithPendingGrpcError() throws Exception {
278         AudioTestHarnessGrpc.AudioTestHarnessStub disconnectedStub =
279                 AudioTestHarnessGrpc.newStub(
280                         mGrpcCleanupRule.register(
281                                 ManagedChannelBuilder.forAddress("localhost", 12345)
282                                         .executor(mListeningExecutorService)
283                                         .build()));
284 
285         GrpcAudioCaptureStream grpcAudioCaptureStream =
286                 GrpcAudioCaptureStream.create(disconnectedStub, mScheduledExecutorService);
287 
288         // Read once from the stream, giving the gRPC exception time to propagate. Even with
289         // the direct executor this is necessary and *should* be deterministic.
290         grpcAudioCaptureStream.read(new byte[1]);
291         return grpcAudioCaptureStream;
292     }
293 
294     /** Configures the exception rule to expect the gRPC Communication Error exception. */
295     private void expectGrpcCommunicationErrorException() throws Exception {
296         mExceptionRule.expectMessage("Audio Test Harness gRPC Communication Error");
297         mExceptionRule.expectCause(CoreMatchers.notNullValue(Throwable.class));
298     }
299 
300     /** Configures the exception rule to expect the gRPC Internal Error exception. */
301     private void expectInternalErrorException() throws Exception {
302         mExceptionRule.expectMessage("Audio Test Harness gRPC Internal Error");
303         mExceptionRule.expectCause(CoreMatchers.notNullValue(Throwable.class));
304     }
305 }
306