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