1 /* 2 * Copyright 2022 Google LLC 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 package com.google.android.libraries.mobiledatadownload; 17 18 import static com.google.android.libraries.mobiledatadownload.DownloadException.DownloadResultCode.ANDROID_DOWNLOADER_HTTP_ERROR; 19 import static com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies.ExecutorType; 20 import static com.google.common.truth.Truth.assertThat; 21 import static com.google.common.util.concurrent.Futures.immediateVoidFuture; 22 import static org.junit.Assert.assertThrows; 23 import static org.mockito.ArgumentMatchers.any; 24 import static org.mockito.Mockito.verify; 25 import static org.mockito.Mockito.when; 26 27 import android.content.Context; 28 import android.net.Uri; 29 import androidx.test.core.app.ApplicationProvider; 30 import com.google.android.libraries.mobiledatadownload.downloader.DownloadConstraints; 31 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader; 32 import com.google.android.libraries.mobiledatadownload.downloader.offroad.dagger.downloader2.BaseFileDownloaderModule; 33 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; 34 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend; 35 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidUri; 36 import com.google.android.libraries.mobiledatadownload.file.integration.downloader.SharedPreferencesDownloadMetadata; 37 import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor; 38 import com.google.android.libraries.mobiledatadownload.monitor.NetworkUsageMonitor; 39 import com.google.android.libraries.mobiledatadownload.testing.BlockingFileDownloader; 40 import com.google.android.libraries.mobiledatadownload.testing.FakeTimeSource; 41 import com.google.android.libraries.mobiledatadownload.testing.TestFlags; 42 import com.google.common.base.Optional; 43 import com.google.common.base.Supplier; 44 import com.google.common.collect.ImmutableList; 45 import com.google.common.util.concurrent.ListenableFuture; 46 import com.google.common.util.concurrent.ListeningExecutorService; 47 import com.google.common.util.concurrent.ListeningScheduledExecutorService; 48 import com.google.common.util.concurrent.MoreExecutors; 49 import com.google.testing.junit.testparameterinjector.TestParameter; 50 import com.google.testing.junit.testparameterinjector.TestParameterInjector; 51 import java.util.concurrent.CancellationException; 52 import java.util.concurrent.ExecutionException; 53 import java.util.concurrent.Executors; 54 import org.junit.After; 55 import org.junit.Before; 56 import org.junit.Rule; 57 import org.junit.Test; 58 import org.junit.runner.RunWith; 59 import org.mockito.Mock; 60 import org.mockito.junit.MockitoJUnit; 61 import org.mockito.junit.MockitoRule; 62 63 @RunWith(TestParameterInjector.class) 64 public class DownloadFileIntegrationTest { 65 66 @Rule(order = 1) 67 public final MockitoRule mocks = MockitoJUnit.rule(); 68 69 private static final String TAG = "DownloadFileIntegrationTest"; 70 71 private static final long TIMEOUT_MS = 3000; 72 73 private static final int FILE_SIZE = 554; 74 private static final String FILE_URL = "https://www.gstatic.com/suggest-dev/odws1_empty.jar"; 75 private static final String DOES_NOT_EXIST_FILE_URL = 76 "https://www.gstatic.com/non-existing/suggest-dev/not-exist.txt"; 77 78 private static final ListeningScheduledExecutorService DOWNLOAD_EXECUTOR = 79 MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(2)); 80 81 private static final Context context = ApplicationProvider.getApplicationContext(); 82 83 private final Uri destinationFileUri = 84 AndroidUri.builder(context).setModule("mdd").setRelativePath("file_1").build(); 85 private final FakeTimeSource clock = new FakeTimeSource(); 86 private final TestFlags flags = new TestFlags(); 87 88 private MobileDataDownload mobileDataDownload; 89 private DownloadProgressMonitor downloadProgressMonitor; 90 private SynchronousFileStorage fileStorage; 91 92 private Supplier<FileDownloader> fileDownloaderSupplier; 93 private ListeningExecutorService controlExecutor; 94 95 @Mock private SingleFileDownloadListener mockDownloadListener; 96 @Mock private NetworkUsageMonitor mockNetworkUsageMonitor; 97 98 @TestParameter ExecutorType controlExecutorType; 99 100 @Before setUp()101 public void setUp() throws Exception { 102 // Set a default behavior for the download listener. 103 when(mockDownloadListener.onComplete()).thenReturn(immediateVoidFuture()); 104 105 controlExecutor = controlExecutorType.executor(); 106 107 downloadProgressMonitor = new DownloadProgressMonitor(clock, controlExecutor); 108 109 fileStorage = 110 new SynchronousFileStorage( 111 /* backends= */ ImmutableList.of(AndroidFileBackend.builder(context).build()), 112 /* transforms= */ ImmutableList.of(), 113 /* monitors= */ ImmutableList.of(downloadProgressMonitor)); 114 115 fileDownloaderSupplier = 116 () -> 117 BaseFileDownloaderModule.createOffroad2FileDownloader( 118 context, 119 DOWNLOAD_EXECUTOR, 120 controlExecutor, 121 fileStorage, 122 new SharedPreferencesDownloadMetadata( 123 context.getSharedPreferences("downloadmetadata", 0), controlExecutor), 124 Optional.of(downloadProgressMonitor), 125 /* urlEngineOptional= */ Optional.absent(), 126 /* exceptionHandlerOptional= */ Optional.absent(), 127 /* authTokenProviderOptional= */ Optional.absent(), 128 // /* cookieJarSupplierOptional= */ Optional.absent(), 129 /* trafficTag= */ Optional.absent(), 130 flags); 131 } 132 133 @After tearDown()134 public void tearDown() throws Exception { 135 if (fileStorage.exists(destinationFileUri)) { 136 fileStorage.deleteFile(destinationFileUri); 137 } 138 } 139 140 @Test downloadFile_success()141 public void downloadFile_success() throws Exception { 142 mobileDataDownload = builderForTest().build(); 143 144 assertThat(fileStorage.exists(destinationFileUri)).isFalse(); 145 146 SingleFileDownloadRequest downloadRequest = 147 SingleFileDownloadRequest.newBuilder() 148 .setDestinationFileUri(destinationFileUri) 149 .setUrlToDownload(FILE_URL) 150 .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED) 151 .setListenerOptional(Optional.of(mockDownloadListener)) 152 .build(); 153 154 ListenableFuture<Void> downloadFuture = mobileDataDownload.downloadFile(downloadRequest); 155 downloadFuture.get(); 156 157 // Verify the file is downloaded. 158 assertThat(fileStorage.exists(destinationFileUri)).isTrue(); 159 assertThat(fileStorage.fileSize(destinationFileUri)).isEqualTo(FILE_SIZE); 160 fileStorage.deleteFile(destinationFileUri); 161 162 // Verify the downloadListener is called. 163 // Sleep for 1 sec to wait for the Future's callback to finish. 164 Thread.sleep(/* millis= */ 1000); 165 verify(mockDownloadListener).onComplete(); 166 } 167 168 @Test downloadFile_failure()169 public void downloadFile_failure() throws Exception { 170 mobileDataDownload = builderForTest().build(); 171 172 assertThat(fileStorage.exists(destinationFileUri)).isFalse(); 173 174 // Trying to download doesn't exist URL. 175 SingleFileDownloadRequest downloadRequest = 176 SingleFileDownloadRequest.newBuilder() 177 .setDestinationFileUri(destinationFileUri) 178 .setUrlToDownload(DOES_NOT_EXIST_FILE_URL) 179 .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED) 180 .setListenerOptional(Optional.of(mockDownloadListener)) 181 .build(); 182 183 ListenableFuture<Void> downloadFuture = mobileDataDownload.downloadFile(downloadRequest); 184 ExecutionException ex = assertThrows(ExecutionException.class, downloadFuture::get); 185 assertThat(ex).hasCauseThat().isInstanceOf(DownloadException.class); 186 assertThat(((DownloadException) ex.getCause()).getDownloadResultCode()) 187 .isEqualTo(ANDROID_DOWNLOADER_HTTP_ERROR); 188 189 // Verify the file is downloaded. 190 assertThat(fileStorage.exists(destinationFileUri)).isFalse(); 191 192 // Verify the downloadListener is called. 193 // Sleep for 1 sec to wait for the Future's callback to finish. 194 Thread.sleep(/* millis= */ 1000); 195 verify(mockDownloadListener).onFailure(any(DownloadException.class)); 196 } 197 198 @Test downloadFile_cancel()199 public void downloadFile_cancel() throws Exception { 200 // Use a BlockingFileDownloader to ensure download remains in progress until it is cancelled. 201 BlockingFileDownloader blockingFileDownloader = new BlockingFileDownloader(DOWNLOAD_EXECUTOR); 202 203 mobileDataDownload = 204 builderForTest().setFileDownloaderSupplier(() -> blockingFileDownloader).build(); 205 206 SingleFileDownloadRequest downloadRequest = 207 SingleFileDownloadRequest.newBuilder() 208 .setDestinationFileUri(destinationFileUri) 209 .setUrlToDownload(FILE_URL) 210 .setDownloadConstraints(DownloadConstraints.NETWORK_CONNECTED) 211 .build(); 212 213 ListenableFuture<Void> downloadFuture = mobileDataDownload.downloadFile(downloadRequest); 214 215 // Note: we could have a race condition here between when the FileDownloader.startDownloading() 216 // is called and when we cancel our download with Future.cancel(). To prevent this, we first 217 // wait until we have started downloading to ensure that it is in progress before we cancel. 218 blockingFileDownloader.waitForDownloadStarted(); 219 220 // Cancel the download 221 downloadFuture.cancel(/* mayInterruptIfRunning= */ true); 222 223 assertThrows(CancellationException.class, downloadFuture::get); 224 225 // Cleanup 226 blockingFileDownloader.resetState(); 227 } 228 229 /** 230 * Returns MDD Builder with common dependencies set -- additional dependencies are added in each 231 * test as needed. 232 */ builderForTest()233 private MobileDataDownloadBuilder builderForTest() { 234 return MobileDataDownloadBuilder.newBuilder() 235 .setContext(context) 236 .setControlExecutor(controlExecutor) 237 .setFileDownloaderSupplier(fileDownloaderSupplier) 238 .setFileStorage(fileStorage) 239 .setDownloadMonitorOptional(Optional.of(downloadProgressMonitor)) 240 .setNetworkUsageMonitor(mockNetworkUsageMonitor) 241 .setFlagsOptional(Optional.of(flags)); 242 } 243 } 244