diff options
Diffstat (limited to 'javatests/com/google/android/libraries/mobiledatadownload/DownloadFileGroupIntegrationTest.java')
-rw-r--r-- | javatests/com/google/android/libraries/mobiledatadownload/DownloadFileGroupIntegrationTest.java | 559 |
1 files changed, 355 insertions, 204 deletions
diff --git a/javatests/com/google/android/libraries/mobiledatadownload/DownloadFileGroupIntegrationTest.java b/javatests/com/google/android/libraries/mobiledatadownload/DownloadFileGroupIntegrationTest.java index c5b3239..818b2e4 100644 --- a/javatests/com/google/android/libraries/mobiledatadownload/DownloadFileGroupIntegrationTest.java +++ b/javatests/com/google/android/libraries/mobiledatadownload/DownloadFileGroupIntegrationTest.java @@ -20,42 +20,51 @@ import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopul import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_ID; import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_SIZE; import static com.google.android.libraries.mobiledatadownload.TestFileGroupPopulator.FILE_URL; +import static com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies.DownloaderConfigurationType; +import static com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies.ExecutorType; import static com.google.common.truth.Truth.assertThat; +import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertThrows; -import static org.junit.Assert.fail; +import android.accounts.Account; import android.content.Context; import android.net.Uri; import android.util.Log; import androidx.test.core.app.ApplicationProvider; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest; +import com.google.android.libraries.mobiledatadownload.account.AccountUtil; import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader; -import com.google.android.libraries.mobiledatadownload.downloader.offroad.dagger.downloader2.BaseFileDownloaderModule; import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend; -import com.google.android.libraries.mobiledatadownload.file.integration.downloader.SharedPreferencesDownloadMetadata; +import com.google.android.libraries.mobiledatadownload.file.backends.JavaFileBackend; import com.google.android.libraries.mobiledatadownload.file.transforms.CompressTransform; import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor; import com.google.android.libraries.mobiledatadownload.monitor.NetworkUsageMonitor; import com.google.android.libraries.mobiledatadownload.testing.BlockingFileDownloader; +import com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies; +import com.google.android.libraries.mobiledatadownload.testing.TestFileDownloader; import com.google.android.libraries.mobiledatadownload.testing.TestFlags; import com.google.common.base.Optional; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; -import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.ListeningScheduledExecutorService; import com.google.common.util.concurrent.MoreExecutors; import com.google.mobiledatadownload.ClientConfigProto.ClientFile; import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup; +import com.google.mobiledatadownload.DownloadConfigProto.DataFile; import com.google.mobiledatadownload.DownloadConfigProto.DataFileGroup; import com.google.mobiledatadownload.DownloadConfigProto.DownloadConditions.DeviceNetworkPolicy; +import com.google.mobiledatadownload.TransformProto; +import com.google.mobiledatadownload.TransformProto.Transform; +import com.google.mobiledatadownload.TransformProto.Transforms; +import com.google.testing.junit.testparameterinjector.TestParameter; +import com.google.testing.junit.testparameterinjector.TestParameterInjector; +import java.util.ArrayList; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -64,19 +73,22 @@ import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; -@RunWith(AndroidJUnit4.class) +/** + * Integration Tests that relate to {@link MobileDataDownload#downloadFileGroup}. + * + * <p>NOTE: Any tests related to cancellation should be added to {@link + * DownloadFileGroupCancellationIntegrationTest} instead. + */ +@RunWith(TestParameterInjector.class) public class DownloadFileGroupIntegrationTest { private static final String TAG = "DownloadFileGroupIntegrationTest"; - private static final int MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS = 300; + private static final int MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS = 60; + private static final int MAX_MULTI_MDD_API_WAIT_TIME_SECS = 120; + private static final long MAX_MDD_API_WAIT_TIME_SECS = 5L; - // Note: Control Executor must not be a single thread executor. - private static final ListeningExecutorService CONTROL_EXECUTOR = - MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); - private static final ScheduledExecutorService DOWNLOAD_EXECUTOR = - Executors.newScheduledThreadPool(2); - private static final ListeningExecutorService listeningExecutorService = - MoreExecutors.listeningDecorator(DOWNLOAD_EXECUTOR); + private static final ListeningScheduledExecutorService DOWNLOAD_EXECUTOR = + MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(4)); private static final String FILE_GROUP_NAME_INSECURE_URL = "test-group-insecure-url"; private static final String FILE_GROUP_NAME_MULTIPLE_FILES = "test-group-multiple-files"; @@ -88,6 +100,24 @@ public class DownloadFileGroupIntegrationTest { private static final String FILE_NOT_EXIST_URL = "https://www.gstatic.com/icing/idd/notexist/file.txt"; + private static final String TEST_DATA_RELATIVE_PATH = + "third_party/java_src/android_libs/mobiledatadownload/javatests/com/google/android/libraries/mobiledatadownload/testdata/"; + + private static final String TEST_DATA_URL = "https://test.url/full_file.txt"; + private static final String TEST_DATA_CHECKSUM = "0c4f1e55c4ec28d0305c5cfde8610b7e6e9f7d9a"; + private static final int TEST_DATA_BYTE_SIZE = 110; + + private static final String TEST_DATA_COMPRESS_URL = "https://test.url/full_file.zlib"; + private static final String TEST_DATA_COMPRESS_CHECKSUM = + "cbffcf480fd52a3c6bf9d21206d36f0a714bb97a"; + private static final int TEST_DATA_COMPRESS_BYTE_SIZE = 92; + + private static final String VARIANT_1 = "test-variant-1"; + private static final String VARIANT_2 = "test-variant-2"; + + private static final Account ACCOUNT_1 = AccountUtil.create("account-name-1", "account-type"); + private static final Account ACCOUNT_2 = AccountUtil.create("account-name-2", "account-type"); + private static final Context context = ApplicationProvider.getApplicationContext(); @Mock private TaskScheduler mockTaskScheduler; @@ -95,81 +125,111 @@ public class DownloadFileGroupIntegrationTest { @Mock private DownloadProgressMonitor mockDownloadProgressMonitor; private SynchronousFileStorage fileStorage; + private ListeningExecutorService controlExecutor; private final TestFlags flags = new TestFlags(); - @Rule public final MockitoRule mocks = MockitoJUnit.rule(); + @Rule(order = 1) + public final MockitoRule mocks = MockitoJUnit.rule(); - /* Differentiates between Downloader libraries for shared test method assertions. */ - private enum DownloaderVersion { - V2 - } + @TestParameter ExecutorType controlExecutorType; @Before public void setUp() throws Exception { fileStorage = new SynchronousFileStorage( - /* backends= */ ImmutableList.of(AndroidFileBackend.builder(context).build()), + /* backends= */ ImmutableList.of( + AndroidFileBackend.builder(context).build(), new JavaFileBackend()), /* transforms= */ ImmutableList.of(new CompressTransform()), /* monitors= */ ImmutableList.of(mockNetworkUsageMonitor, mockDownloadProgressMonitor)); - } - @Test - public void downloadAndRead_downloader2() throws Exception { - Supplier<FileDownloader> fileDownloaderSupplier = - () -> - BaseFileDownloaderModule.createOffroad2FileDownloader( - context, - DOWNLOAD_EXECUTOR, - CONTROL_EXECUTOR, - fileStorage, - new SharedPreferencesDownloadMetadata( - context.getSharedPreferences("downloadmetadata", 0), listeningExecutorService), - Optional.of(mockDownloadProgressMonitor), - /* urlEngineOptional= */ Optional.absent(), - /* exceptionHandlerOptional= */ Optional.absent(), - /* authTokenProviderOptional= */ Optional.absent(), - /* trafficTag= */ Optional.absent(), - flags); - - testDownloadAndRead(fileDownloaderSupplier, DownloaderVersion.V2); + controlExecutor = controlExecutorType.executor(); } @Test - public void downloadFailed_downloader2() throws Exception { - Supplier<FileDownloader> fileDownloaderSupplier = - () -> - BaseFileDownloaderModule.createOffroad2FileDownloader( - context, - DOWNLOAD_EXECUTOR, - CONTROL_EXECUTOR, - fileStorage, - new SharedPreferencesDownloadMetadata( - context.getSharedPreferences("downloadmetadata", 0), listeningExecutorService), - Optional.of(mockDownloadProgressMonitor), - /* urlEngineOptional= */ Optional.absent(), - /* exceptionHandlerOptional= */ Optional.absent(), - /* authTokenProviderOptional= */ Optional.absent(), - /* trafficTag= */ Optional.absent(), - flags); - - testDownloadFailed(fileDownloaderSupplier, DownloaderVersion.V2); + public void downloadAndRead( + @TestParameter DownloaderConfigurationType downloaderConfigurationType) throws Exception { + Optional<String> instanceId = Optional.of(MddTestDependencies.randomInstanceId()); + TestFileGroupPopulator testFileGroupPopulator = new TestFileGroupPopulator(context); + MobileDataDownload mobileDataDownload = + builderForTest() + .setInstanceIdOptional(instanceId) + .setFileDownloaderSupplier( + downloaderConfigurationType.fileDownloaderSupplier( + context, + controlExecutor, + DOWNLOAD_EXECUTOR, + fileStorage, + flags, + Optional.of(mockDownloadProgressMonitor), + instanceId)) + .addFileGroupPopulator(testFileGroupPopulator) + .build(); + + testFileGroupPopulator + .refreshFileGroups(mobileDataDownload) + .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); + + mobileDataDownload + .downloadFileGroup( + DownloadFileGroupRequest.newBuilder() + .setGroupName(FILE_GROUP_NAME) + .setListenerOptional( + Optional.of( + new DownloadListener() { + @Override + public void onProgress(long currentSize) { + Log.i(TAG, "onProgress " + currentSize); + } + + @Override + public void onComplete(ClientFileGroup clientFileGroup) { + Log.i(TAG, "onComplete " + clientFileGroup.getGroupName()); + } + })) + .build()) + .get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, SECONDS); + + ClientFileGroup clientFileGroup = + mobileDataDownload + .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) + .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); + + assertThat(clientFileGroup).isNotNull(); + assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME); + assertThat(clientFileGroup.getFileCount()).isEqualTo(1); + + ClientFile clientFile = clientFileGroup.getFileList().get(0); + assertThat(clientFile.getFileId()).isEqualTo(FILE_ID); + Uri androidUri = Uri.parse(clientFile.getFileUri()); + assertThat(fileStorage.fileSize(androidUri)).isEqualTo(FILE_SIZE); + + mobileDataDownload.clear().get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); + + switch (downloaderConfigurationType) { + case V2_PLATFORM: + // No-op + } } - private void testDownloadFailed( - Supplier<FileDownloader> fileDownloaderSupplier, DownloaderVersion version) throws Exception { + @Test + public void downloadFailed() throws Exception { + // NOTE: The test failures here are not network stack dependent, so there's + // no need to parameterize this test for different network stacks. + Optional<String> instanceId = Optional.of(MddTestDependencies.randomInstanceId()); MobileDataDownload mobileDataDownload = - MobileDataDownloadBuilder.newBuilder() - .setContext(context) - .setControlExecutor(CONTROL_EXECUTOR) - .setFileDownloaderSupplier(fileDownloaderSupplier) - .setTaskScheduler(Optional.of(mockTaskScheduler)) - .setDeltaDecoderOptional(Optional.absent()) - .setFileStorage(fileStorage) - .setNetworkUsageMonitor(mockNetworkUsageMonitor) - .setDownloadMonitorOptional(Optional.of(mockDownloadProgressMonitor)) - .setFlagsOptional(Optional.of(flags)) + builderForTest() + .setInstanceIdOptional(instanceId) + .setFileDownloaderSupplier( + DownloaderConfigurationType.V2_PLATFORM.fileDownloaderSupplier( + context, + controlExecutor, + DOWNLOAD_EXECUTOR, + fileStorage, + flags, + Optional.of(mockDownloadProgressMonitor), + instanceId)) .build(); // The data file group has a file with insecure url. @@ -200,7 +260,7 @@ public class DownloadFileGroupIntegrationTest { mobileDataDownload .addFileGroup( AddFileGroupRequest.newBuilder().setDataFileGroup(groupWithInsecureUrl).build()) - .get()) + .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) .isTrue(); assertThat( @@ -209,7 +269,7 @@ public class DownloadFileGroupIntegrationTest { AddFileGroupRequest.newBuilder() .setDataFileGroup(groupWithMultipleFiles) .build()) - .get()) + .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS)) .isTrue(); ExecutionException exception = @@ -221,7 +281,7 @@ public class DownloadFileGroupIntegrationTest { DownloadFileGroupRequest.newBuilder() .setGroupName(FILE_GROUP_NAME_INSECURE_URL) .build()) - .get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, TimeUnit.SECONDS)); + .get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, SECONDS)); assertThat(exception).hasCauseThat().isInstanceOf(AggregateException.class); AggregateException cause = (AggregateException) exception.getCause(); assertThat(cause).isNotNull(); @@ -239,173 +299,264 @@ public class DownloadFileGroupIntegrationTest { DownloadFileGroupRequest.newBuilder() .setGroupName(FILE_GROUP_NAME_MULTIPLE_FILES) .build()) - .get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, TimeUnit.SECONDS)); + .get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, SECONDS)); assertThat(exception2).hasCauseThat().isInstanceOf(AggregateException.class); AggregateException cause2 = (AggregateException) exception2.getCause(); assertThat(cause2).isNotNull(); ImmutableList<Throwable> failures2 = cause2.getFailures(); assertThat(failures2).hasSize(2); assertThat(failures2.get(0)).isInstanceOf(DownloadException.class); - switch (version) { - case V2: - assertThat(failures2.get(0)) - .hasCauseThat() - .hasMessageThat() - .containsMatch("httpStatusCode=404"); - break; - } + assertThat(failures2.get(0)) + .hasCauseThat() + .hasMessageThat() + .containsMatch("httpStatusCode=404"); assertThat(failures2.get(1)).isInstanceOf(DownloadException.class); assertThat(failures2.get(1)).hasMessageThat().contains("INSECURE_URL_ERROR"); - switch (version) { - case V2: - // No-op - } + AggregateException exception3 = + assertThrows( + AggregateException.class, + () -> { + try { + ListenableFuture<ClientFileGroup> downloadFuture1 = + mobileDataDownload.downloadFileGroup( + DownloadFileGroupRequest.newBuilder() + .setGroupName(FILE_GROUP_NAME_MULTIPLE_FILES) + .build()); + ListenableFuture<ClientFileGroup> downloadFuture2 = + mobileDataDownload.downloadFileGroup( + DownloadFileGroupRequest.newBuilder() + .setGroupName(FILE_GROUP_NAME_INSECURE_URL) + .build()); + + Futures.successfulAsList(downloadFuture1, downloadFuture2) + .get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, SECONDS); + + AggregateException.throwIfFailed( + ImmutableList.of(downloadFuture1, downloadFuture2), + "Expected download failures"); + } catch (ExecutionException e) { + throw e; + } + }); + assertThat(exception3.getFailures()).hasSize(2); } - private void testDownloadAndRead( - Supplier<FileDownloader> fileDownloaderSupplier, DownloaderVersion version) throws Exception { - TestFileGroupPopulator testFileGroupPopulator = new TestFileGroupPopulator(context); + @Test + public void removePartialDownloadThenDownloadAgain( + @TestParameter DownloaderConfigurationType downloaderConfigurationType) throws Exception { + Optional<String> instanceId = Optional.of(MddTestDependencies.randomInstanceId()); + + Supplier<FileDownloader> fileDownloaderSupplier = + downloaderConfigurationType.fileDownloaderSupplier( + context, + controlExecutor, + DOWNLOAD_EXECUTOR, + fileStorage, + flags, + Optional.of(mockDownloadProgressMonitor), + instanceId); + BlockingFileDownloader blockingFileDownloader = + new BlockingFileDownloader(DOWNLOAD_EXECUTOR, fileDownloaderSupplier.get()); + MobileDataDownload mobileDataDownload = - MobileDataDownloadBuilder.newBuilder() - .setContext(context) - .setControlExecutor(CONTROL_EXECUTOR) - .setFileDownloaderSupplier(fileDownloaderSupplier) - .addFileGroupPopulator(testFileGroupPopulator) - .setTaskScheduler(Optional.of(mockTaskScheduler)) - .setDeltaDecoderOptional(Optional.absent()) - .setFileStorage(fileStorage) - .setNetworkUsageMonitor(mockNetworkUsageMonitor) - .setDownloadMonitorOptional(Optional.of(mockDownloadProgressMonitor)) - .setFlagsOptional(Optional.of(flags)) + builderForTest() + .setInstanceIdOptional(instanceId) + .setFileDownloaderSupplier(() -> blockingFileDownloader) .build(); - testFileGroupPopulator.refreshFileGroups(mobileDataDownload).get(); - mobileDataDownload - .downloadFileGroup( - DownloadFileGroupRequest.newBuilder() - .setGroupName(FILE_GROUP_NAME) - .setListenerOptional( - Optional.of( - new DownloadListener() { - @Override - public void onProgress(long currentSize) { - Log.i(TAG, "onProgress " + currentSize); - } + mobileDataDownload.clear().get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); - @Override - public void onComplete(ClientFileGroup clientFileGroup) { - Log.i(TAG, "onComplete " + clientFileGroup.getGroupName()); - } - })) - .build()) - .get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, TimeUnit.SECONDS); + // Add the filegroup, start downloading, then cancel while in progress. + TestFileGroupPopulator testFileGroupPopulator = new TestFileGroupPopulator(context); + testFileGroupPopulator + .refreshFileGroups(mobileDataDownload) + .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); - String debugString = mobileDataDownload.getDebugInfoAsString(); - Log.i(TAG, "MDD Lib dump:"); - for (String line : debugString.split("\n", -1)) { - Log.i(TAG, line); - } + ListenableFuture<ClientFileGroup> downloadFuture = + mobileDataDownload.downloadFileGroup( + DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()); + + blockingFileDownloader.finishDownloading(); // Unblocks blockingFileDownloader + blockingFileDownloader.waitForDelegateStarted(); // Waits until offroadDownloader starts + + // NOTE: add a little wait to allow Downloader's listeners to run. + Thread.sleep(/* millis= */ 200); + + downloadFuture.cancel(true /* may interrupt */); + + // NOTE: add a little wait to allow Downloader's listeners to run. + Thread.sleep(/* millis= */ 200); + + // Remove the filegroup. + ListenableFuture<Boolean> removeFuture = + mobileDataDownload.removeFileGroup( + RemoveFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()); + removeFuture.get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); + + // Add then try to download again. + blockingFileDownloader.resetState(); + blockingFileDownloader.finishDownloading(); // Unblocks blockingFileDownloader + + testFileGroupPopulator + .refreshFileGroups(mobileDataDownload) + .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); + downloadFuture = + mobileDataDownload.downloadFileGroup( + DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()); + + downloadFuture.get(MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS, SECONDS); + + // The file should have downloaded as expected. ClientFileGroup clientFileGroup = mobileDataDownload .getFileGroup(GetFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()) - .get(); + .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); assertThat(clientFileGroup).isNotNull(); - assertThat(clientFileGroup.getGroupName()).isEqualTo(FILE_GROUP_NAME); assertThat(clientFileGroup.getFileCount()).isEqualTo(1); - - ClientFile clientFile = clientFileGroup.getFileList().get(0); - assertThat(clientFile.getFileId()).isEqualTo(FILE_ID); - Uri androidUri = Uri.parse(clientFile.getFileUri()); + Uri androidUri = Uri.parse(clientFileGroup.getFileList().get(0).getFileUri()); assertThat(fileStorage.fileSize(androidUri)).isEqualTo(FILE_SIZE); + } + + @Test + public void downloadDifferentGroupsWithSameFileTest() throws Exception { + Optional<String> instanceId = Optional.of(MddTestDependencies.randomInstanceId()); + MobileDataDownload mobileDataDownload = + builderForTest() + .setInstanceIdOptional(instanceId) + .setFileDownloaderSupplier( + () -> + new TestFileDownloader(TEST_DATA_RELATIVE_PATH, fileStorage, DOWNLOAD_EXECUTOR)) + .build(); + + DataFile.Builder dataFileBuilder = + DataFile.newBuilder() + .setUrlToDownload(TEST_DATA_URL) + .setChecksum(TEST_DATA_CHECKSUM) + .setByteSize(TEST_DATA_BYTE_SIZE); + DataFileGroup.Builder groupBuilder = DataFileGroup.newBuilder(); + + // Add all groups concurrently + ArrayList<ListenableFuture<Boolean>> addFutures = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + String groupName = String.format("group%d", i); + String fileId = String.format("group%d_file", i); + + DataFile file = dataFileBuilder.setFileId(fileId).build(); + DataFileGroup group = + DataFileGroup.newBuilder().setGroupName(groupName).addFile(file).build(); + + addFutures.add( + mobileDataDownload.addFileGroup( + AddFileGroupRequest.newBuilder().setDataFileGroup(group).build())); + } + Futures.allAsList(addFutures).get(MAX_MULTI_MDD_API_WAIT_TIME_SECS, SECONDS); - mobileDataDownload.clear().get(); + // Start all downloads concurrently + ArrayList<ListenableFuture<ClientFileGroup>> downloadFutures = new ArrayList<>(); + for (int i = 0; i < 50; i++) { + String groupName = String.format("group%d", i); - switch (version) { - case V2: - // No-op + downloadFutures.add( + mobileDataDownload.downloadFileGroup( + DownloadFileGroupRequest.newBuilder().setGroupName(groupName).build())); } + List<ClientFileGroup> groups = + Futures.allAsList(downloadFutures).get(MAX_MULTI_MDD_API_WAIT_TIME_SECS, SECONDS); + + assertThat(groups).doesNotContain(null); } @Test - public void cancelDownload() throws Exception { - // In this test we will start a download and make sure that calling cancel on the returned - // future will cancel the download. - // We create a BlockingFileDownloader that allows the download to be blocked indefinitely. - // We also provide a delegate FileDownloader that attaches a FutureCallback to the internal - // download future and fail if the future is not cancelled. - BlockingFileDownloader blockingFileDownloader = - new BlockingFileDownloader( - listeningExecutorService, - new FileDownloader() { - @Override - public ListenableFuture<Void> startDownloading(DownloadRequest downloadRequest) { - ListenableFuture<Void> downloadTaskFuture = Futures.immediateVoidFuture(); - Futures.addCallback( - downloadTaskFuture, - new FutureCallback<Void>() { - @Override - public void onSuccess(Void result) { - // Should not get here since we will cancel the future. - fail(); - } - - @Override - public void onFailure(Throwable t) { - assertThat(downloadTaskFuture.isCancelled()).isTrue(); - - Log.i(TAG, "downloadTask is cancelled!"); - } - }, - listeningExecutorService); - return downloadTaskFuture; - } - }); - Supplier<FileDownloader> neverFinishDownloader = () -> blockingFileDownloader; + public void concurrentDownloads_withSameFile_withDifferentDownloadTransforms_completes( + @TestParameter boolean enableDedupByFileKey) throws Exception { + flags.enableFileDownloadDedupByFileKey = Optional.of(enableDedupByFileKey); - // Use never finish downloader to test whether the cancellation on the downloadFuture would - // cancel all the parent futures. - TestFileGroupPopulator testFileGroupPopulator = new TestFileGroupPopulator(context); + Optional<String> instanceId = Optional.of(MddTestDependencies.randomInstanceId()); MobileDataDownload mobileDataDownload = - MobileDataDownloadBuilder.newBuilder() - .setContext(context) - .setControlExecutor(CONTROL_EXECUTOR) - .setFileDownloaderSupplier(neverFinishDownloader) - .addFileGroupPopulator(testFileGroupPopulator) - .setTaskScheduler(Optional.of(mockTaskScheduler)) - .setDeltaDecoderOptional(Optional.absent()) - .setFileStorage(fileStorage) - .setNetworkUsageMonitor(mockNetworkUsageMonitor) - .setDownloadMonitorOptional(Optional.of(mockDownloadProgressMonitor)) - .setFlagsOptional(Optional.of(flags)) + builderForTest() + .setInstanceIdOptional(instanceId) + .setFileDownloaderSupplier( + () -> + new TestFileDownloader(TEST_DATA_RELATIVE_PATH, fileStorage, DOWNLOAD_EXECUTOR)) .build(); - testFileGroupPopulator.refreshFileGroups(mobileDataDownload).get(); + // Create two groups which share the same file, but have different download transforms + DataFileGroup groupWithoutTransform = + DataFileGroup.newBuilder() + .setGroupName("groupWithoutTransform") + .addFile( + DataFile.newBuilder() + .setFileId("file_no_transform") + .setUrlToDownload(TEST_DATA_URL) + .setChecksum(TEST_DATA_CHECKSUM) + .setByteSize(TEST_DATA_BYTE_SIZE)) + .build(); - // Now start to download the file group. - ListenableFuture<ClientFileGroup> downloadFileGroupFuture = - mobileDataDownload.downloadFileGroup( - DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()); + DataFileGroup groupWithTransform = + DataFileGroup.newBuilder() + .setGroupName("groupWithTransform") + .addFile( + DataFile.newBuilder() + .setFileId("file_no_transform") + .setUrlToDownload(TEST_DATA_COMPRESS_URL) + .setChecksum(TEST_DATA_CHECKSUM) + .setByteSize(TEST_DATA_BYTE_SIZE) + .setDownloadedFileChecksum(TEST_DATA_COMPRESS_CHECKSUM) + .setDownloadedFileByteSize(TEST_DATA_COMPRESS_BYTE_SIZE) + .setDownloadTransforms( + Transforms.newBuilder() + .addTransform( + Transform.newBuilder() + .setCompress( + TransformProto.CompressTransform.getDefaultInstance()) + .build()) + .build()) + .build()) + .build(); - // Note: we could have a race condition here between when we call the - // downloadFileGroupFuture.cancel and when the FileDownloader.startDownloading is executed. - // The following call will ensure that we will only call cancel on the downloadFileGroupFuture - // when the actual download has happened (the downloadTaskFuture). - // This will block until the downloadTaskFuture starts. - blockingFileDownloader.waitForDownloadStarted(); + // Add both groups, then attempt to download both concurrently + mobileDataDownload + .addFileGroup( + AddFileGroupRequest.newBuilder().setDataFileGroup(groupWithoutTransform).build()) + .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); + mobileDataDownload + .addFileGroup(AddFileGroupRequest.newBuilder().setDataFileGroup(groupWithTransform).build()) + .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); - // Cancel the downloadFileGroupFuture, it should cascade cancellation to downloadTaskFuture. - downloadFileGroupFuture.cancel(true /*may interrupt*/); + ListenableFuture<ClientFileGroup> downloadWithoutTransform = + mobileDataDownload.downloadFileGroup( + DownloadFileGroupRequest.newBuilder().setGroupName("groupWithoutTransform").build()); + ListenableFuture<ClientFileGroup> downloadWithTransform = + mobileDataDownload.downloadFileGroup( + DownloadFileGroupRequest.newBuilder().setGroupName("groupWithTransform").build()); - // Allow the download to continue and trigger our delegate FileDownloader. If the future isn't - // cancelled, the onSuccess callback should fail the test. - blockingFileDownloader.finishDownloading(); - blockingFileDownloader.waitForDownloadCompleted(); + List<ClientFileGroup> downloadedGroups = + Futures.allAsList(ImmutableList.of(downloadWithoutTransform, downloadWithTransform)) + .get(MAX_MULTI_MDD_API_WAIT_TIME_SECS, SECONDS); - assertThat(downloadFileGroupFuture.isCancelled()).isTrue(); + // Both groups are downloaded and both files point to the same on-device uri. + assertThat(downloadedGroups).doesNotContain(null); + assertThat(downloadedGroups.get(0).getFile(0).getFileUri()) + .isEqualTo(downloadedGroups.get(1).getFile(0).getFileUri()); + } - mobileDataDownload.clear().get(); + /** + * Returns MDD Builder with common dependencies set -- additional dependencies are added in each + * test as needed. + */ + private MobileDataDownloadBuilder builderForTest() { + + return MobileDataDownloadBuilder.newBuilder() + .setContext(context) + .setControlExecutor(controlExecutor) + .setFileStorage(fileStorage) + .setTaskScheduler(Optional.of(mockTaskScheduler)) + .setDeltaDecoderOptional(Optional.absent()) + .setNetworkUsageMonitor(mockNetworkUsageMonitor) + .setDownloadMonitorOptional(Optional.of(mockDownloadProgressMonitor)) + .setFlagsOptional(Optional.of(flags)); } } |