1 /* 2 * Copyright (C) 2018 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.textclassifier.downloader; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static org.mockito.ArgumentMatchers.any; 21 import static org.mockito.Mockito.when; 22 import static org.testng.Assert.expectThrows; 23 24 import androidx.test.core.app.ApplicationProvider; 25 import com.google.android.downloader.DownloadConstraints; 26 import com.google.android.downloader.DownloadDestination; 27 import com.google.android.downloader.DownloadRequest; 28 import com.google.android.downloader.DownloadResult; 29 import com.google.android.downloader.Downloader; 30 import com.google.android.downloader.ErrorDetails; 31 import com.google.android.downloader.RequestException; 32 import com.google.android.downloader.SimpleFileDownloadDestination; 33 import com.google.common.util.concurrent.FluentFuture; 34 import com.google.common.util.concurrent.Futures; 35 import com.google.common.util.concurrent.ListenableFuture; 36 import com.google.common.util.concurrent.MoreExecutors; 37 import com.google.common.util.concurrent.SettableFuture; 38 import java.io.File; 39 import java.net.URI; 40 import org.junit.Before; 41 import org.junit.Rule; 42 import org.junit.Test; 43 import org.junit.runner.RunWith; 44 import org.junit.runners.JUnit4; 45 import org.mockito.Mock; 46 import org.mockito.junit.MockitoJUnit; 47 import org.mockito.junit.MockitoRule; 48 49 @RunWith(JUnit4.class) 50 public final class ModelDownloaderServiceImplTest { 51 52 @Rule public final MockitoRule mocks = MockitoJUnit.rule(); 53 54 private static final long BYTES_WRITTEN = 1L; 55 private static final String DOWNLOAD_URI = 56 "https://www.gstatic.com/android/text_classifier/r/v999/en.fb"; 57 private static final int DOWNLOADER_LIB_ERROR_CODE = 500; 58 private static final String ERROR_MESSAGE = "err_msg"; 59 private static final Exception DOWNLOADER_LIB_EXCEPTION = 60 new RequestException( 61 ErrorDetails.builder() 62 .setErrorMessage(ERROR_MESSAGE) 63 .setHttpStatusCode(DOWNLOADER_LIB_ERROR_CODE) 64 .build()); 65 66 @Mock private Downloader downloader; 67 private File targetModelFile; 68 private File targetMetadataFile; 69 private ModelDownloaderServiceImpl modelDownloaderServiceImpl; 70 private TestSuccessCallbackImpl successCallback; 71 private TestFailureCallbackImpl failureCallback; 72 73 @Before setUp()74 public void setUp() { 75 76 this.targetModelFile = 77 new File(ApplicationProvider.getApplicationContext().getCacheDir(), "model.fb"); 78 this.targetMetadataFile = ModelDownloaderServiceImpl.getMetadataFile(targetModelFile); 79 this.modelDownloaderServiceImpl = 80 new ModelDownloaderServiceImpl(MoreExecutors.newDirectExecutorService(), downloader); 81 this.successCallback = new TestSuccessCallbackImpl(); 82 this.failureCallback = new TestFailureCallbackImpl(); 83 84 targetModelFile.deleteOnExit(); 85 targetMetadataFile.deleteOnExit(); 86 when(downloader.newRequestBuilder(any(), any(DownloadDestination.class))) 87 .thenReturn( 88 DownloadRequest.newBuilder() 89 .setUri(URI.create(DOWNLOAD_URI)) 90 .setDownloadConstraints(DownloadConstraints.NONE) 91 .setDestination( 92 new SimpleFileDownloadDestination(targetModelFile, targetMetadataFile))); 93 } 94 95 @Test download_succeeded()96 public void download_succeeded() throws Exception { 97 targetModelFile.createNewFile(); 98 targetMetadataFile.createNewFile(); 99 when(downloader.execute(any())) 100 .thenReturn( 101 FluentFuture.from(Futures.immediateFuture(DownloadResult.create(BYTES_WRITTEN)))); 102 modelDownloaderServiceImpl.download( 103 DOWNLOAD_URI, targetModelFile.getAbsolutePath(), successCallback); 104 105 assertThat(successCallback.getBytesWrittenFuture().get()).isEqualTo(BYTES_WRITTEN); 106 assertThat(targetModelFile.exists()).isTrue(); 107 assertThat(targetMetadataFile.exists()).isFalse(); 108 } 109 110 @Test download_failed()111 public void download_failed() throws Exception { 112 targetModelFile.createNewFile(); 113 targetMetadataFile.createNewFile(); 114 when(downloader.execute(any())) 115 .thenReturn(FluentFuture.from(Futures.immediateFailedFuture(DOWNLOADER_LIB_EXCEPTION))); 116 modelDownloaderServiceImpl.download( 117 DOWNLOAD_URI, targetModelFile.getAbsolutePath(), successCallback); 118 119 Throwable t = 120 expectThrows(Throwable.class, () -> successCallback.getBytesWrittenFuture().get()); 121 assertThat(t).hasCauseThat().isInstanceOf(ModelDownloadException.class); 122 ModelDownloadException e = (ModelDownloadException) t.getCause(); 123 assertThat(e.getErrorCode()).isEqualTo(ModelDownloadException.FAILED_TO_DOWNLOAD_OTHER); 124 assertThat(e.getDownloaderLibErrorCode()).isEqualTo(DOWNLOADER_LIB_ERROR_CODE); 125 assertThat(e).hasMessageThat().contains(ERROR_MESSAGE); 126 assertThat(targetModelFile.exists()).isFalse(); 127 assertThat(targetMetadataFile.exists()).isFalse(); 128 } 129 130 @Test download_succeeded_callbackFailed()131 public void download_succeeded_callbackFailed() throws Exception { 132 targetModelFile.createNewFile(); 133 targetMetadataFile.createNewFile(); 134 when(downloader.execute(any())) 135 .thenReturn( 136 FluentFuture.from(Futures.immediateFuture(DownloadResult.create(BYTES_WRITTEN)))); 137 modelDownloaderServiceImpl.download( 138 DOWNLOAD_URI, targetModelFile.getAbsolutePath(), failureCallback); 139 140 assertThat(failureCallback.onSuccessCalled).isTrue(); 141 assertThat(targetModelFile.exists()).isTrue(); 142 assertThat(targetMetadataFile.exists()).isFalse(); 143 } 144 145 @Test download_failed_callbackFailed()146 public void download_failed_callbackFailed() throws Exception { 147 targetModelFile.createNewFile(); 148 targetMetadataFile.createNewFile(); 149 when(downloader.execute(any())) 150 .thenReturn(FluentFuture.from(Futures.immediateFailedFuture(DOWNLOADER_LIB_EXCEPTION))); 151 modelDownloaderServiceImpl.download( 152 DOWNLOAD_URI, targetModelFile.getAbsolutePath(), failureCallback); 153 154 assertThat(failureCallback.onFailureCalled).isTrue(); 155 assertThat(targetModelFile.exists()).isFalse(); 156 assertThat(targetMetadataFile.exists()).isFalse(); 157 } 158 159 // NOTICE: Had some problem mocking this AIDL interface, so created fake impls 160 private static final class TestSuccessCallbackImpl extends IModelDownloaderCallback.Stub { 161 private final SettableFuture<Long> bytesWrittenFuture = SettableFuture.<Long>create(); 162 getBytesWrittenFuture()163 public ListenableFuture<Long> getBytesWrittenFuture() { 164 return bytesWrittenFuture; 165 } 166 167 @Override onSuccess(long bytesWritten)168 public void onSuccess(long bytesWritten) { 169 bytesWrittenFuture.set(bytesWritten); 170 } 171 172 @Override onFailure(int downloaderLibErrorCode, String errorMsg)173 public void onFailure(int downloaderLibErrorCode, String errorMsg) { 174 bytesWrittenFuture.setException( 175 new ModelDownloadException( 176 ModelDownloadException.FAILED_TO_DOWNLOAD_OTHER, downloaderLibErrorCode, errorMsg)); 177 } 178 } 179 180 private static final class TestFailureCallbackImpl extends IModelDownloaderCallback.Stub { 181 public boolean onSuccessCalled = false; 182 public boolean onFailureCalled = false; 183 184 @Override onSuccess(long bytesWritten)185 public void onSuccess(long bytesWritten) { 186 onSuccessCalled = true; 187 throw new RuntimeException(); 188 } 189 190 @Override onFailure(int downloaderLibErrorCode, String errorMsg)191 public void onFailure(int downloaderLibErrorCode, String errorMsg) { 192 onFailureCalled = true; 193 throw new RuntimeException(); 194 } 195 } 196 } 197