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.TestFileGroupPopulator.FILE_GROUP_NAME; 19 import static com.google.android.libraries.mobiledatadownload.testing.MddTestDependencies.ExecutorType; 20 import static com.google.common.truth.Truth.assertThat; 21 import static java.util.concurrent.TimeUnit.SECONDS; 22 import static org.junit.Assert.fail; 23 24 import android.accounts.Account; 25 import android.content.Context; 26 import android.util.Log; 27 import androidx.test.core.app.ApplicationProvider; 28 import com.google.android.libraries.mobiledatadownload.account.AccountUtil; 29 import com.google.android.libraries.mobiledatadownload.downloader.DownloadRequest; 30 import com.google.android.libraries.mobiledatadownload.downloader.FileDownloader; 31 import com.google.android.libraries.mobiledatadownload.file.SynchronousFileStorage; 32 import com.google.android.libraries.mobiledatadownload.file.backends.AndroidFileBackend; 33 import com.google.android.libraries.mobiledatadownload.file.transforms.CompressTransform; 34 import com.google.android.libraries.mobiledatadownload.monitor.DownloadProgressMonitor; 35 import com.google.android.libraries.mobiledatadownload.monitor.NetworkUsageMonitor; 36 import com.google.android.libraries.mobiledatadownload.testing.BlockingFileDownloader; 37 import com.google.android.libraries.mobiledatadownload.testing.TestFlags; 38 import com.google.android.libraries.mobiledatadownload.tracing.PropagatedFutures; 39 import com.google.common.base.Optional; 40 import com.google.common.collect.ImmutableList; 41 import com.google.common.util.concurrent.FutureCallback; 42 import com.google.common.util.concurrent.Futures; 43 import com.google.common.util.concurrent.ListenableFuture; 44 import com.google.common.util.concurrent.ListeningExecutorService; 45 import com.google.common.util.concurrent.ListeningScheduledExecutorService; 46 import com.google.common.util.concurrent.MoreExecutors; 47 import com.google.mobiledatadownload.ClientConfigProto.ClientFileGroup; 48 import com.google.testing.junit.testparameterinjector.TestParameter; 49 import com.google.testing.junit.testparameterinjector.TestParameterInjector; 50 import java.util.concurrent.Executors; 51 import org.junit.Before; 52 import org.junit.Rule; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 import org.mockito.Mock; 56 import org.mockito.junit.MockitoJUnit; 57 import org.mockito.junit.MockitoRule; 58 59 /** 60 * Integration Tests that relate to download cancellation should be placed here. 61 * 62 * <p>This includes calling {@link MobileDataDownload#cancelForegroundDownload} for cancelling the 63 * future returned from {@link MobileDataDownload#downloadFileGroup} or {@link 64 * MobileDataDownload#downloadFileGroupWithForegroundService}. 65 */ 66 @RunWith(TestParameterInjector.class) 67 public class DownloadFileGroupCancellationIntegrationTest { 68 69 private static final String TAG = "DownloadFileGroupCancellationIntegrationTest"; 70 private static final int MAX_DOWNLOAD_FILE_GROUP_WAIT_TIME_SECS = 60; 71 private static final long MAX_MDD_API_WAIT_TIME_SECS = 5L; 72 73 private static final ListeningScheduledExecutorService DOWNLOAD_EXECUTOR = 74 MoreExecutors.listeningDecorator(Executors.newScheduledThreadPool(4)); 75 76 private static final String FILE_GROUP_NAME_INSECURE_URL = "test-group-insecure-url"; 77 private static final String FILE_GROUP_NAME_MULTIPLE_FILES = "test-group-multiple-files"; 78 79 private static final String FILE_ID_1 = "test-file-1"; 80 private static final String FILE_ID_2 = "test-file-2"; 81 private static final String FILE_CHECKSUM_1 = "a1cba9d87b1440f41ce9e7da38c43e1f6bd7d5df"; 82 private static final String FILE_CHECKSUM_2 = "cb2459d9f1b508993aba36a5ffd942a7e0d49ed6"; 83 private static final String FILE_NOT_EXIST_URL = 84 "https://www.gstatic.com/icing/idd/notexist/file.txt"; 85 86 private static final String VARIANT_1 = "test-variant-1"; 87 private static final String VARIANT_2 = "test-variant-2"; 88 89 private static final Account ACCOUNT_1 = AccountUtil.create("account-name-1", "account-type"); 90 private static final Account ACCOUNT_2 = AccountUtil.create("account-name-2", "account-type"); 91 92 private static final Context context = ApplicationProvider.getApplicationContext(); 93 94 @Mock private TaskScheduler mockTaskScheduler; 95 @Mock private NetworkUsageMonitor mockNetworkUsageMonitor; 96 @Mock private DownloadProgressMonitor mockDownloadProgressMonitor; 97 98 private SynchronousFileStorage fileStorage; 99 private ListeningExecutorService controlExecutor; 100 101 private final TestFlags flags = new TestFlags(); 102 103 @Rule(order = 1) 104 public final MockitoRule mocks = MockitoJUnit.rule(); 105 106 @TestParameter ExecutorType controlExecutorType; 107 108 @Before setUp()109 public void setUp() throws Exception { 110 111 fileStorage = 112 new SynchronousFileStorage( 113 /* backends= */ ImmutableList.of(AndroidFileBackend.builder(context).build()), 114 /* transforms= */ ImmutableList.of(new CompressTransform()), 115 /* monitors= */ ImmutableList.of(mockNetworkUsageMonitor, mockDownloadProgressMonitor)); 116 117 controlExecutor = controlExecutorType.executor(); 118 } 119 120 @Test cancelDownload()121 public void cancelDownload() throws Exception { 122 // In this test we will start a download and make sure that calling cancel on the returned 123 // future will cancel the download. 124 // We create a BlockingFileDownloader that allows the download to be blocked indefinitely. 125 // We also provide a delegate FileDownloader that attaches a FutureCallback to the internal 126 // download future and fail if the future is not cancelled. 127 BlockingFileDownloader blockingFileDownloader = 128 new BlockingFileDownloader( 129 DOWNLOAD_EXECUTOR, 130 new FileDownloader() { 131 @Override 132 public ListenableFuture<Void> startDownloading(DownloadRequest downloadRequest) { 133 ListenableFuture<Void> downloadTaskFuture = Futures.immediateVoidFuture(); 134 PropagatedFutures.addCallback( 135 downloadTaskFuture, 136 new FutureCallback<Void>() { 137 @Override 138 public void onSuccess(Void result) { 139 // Should not get here since we will cancel the future. 140 fail(); 141 } 142 143 @Override 144 public void onFailure(Throwable t) { 145 assertThat(downloadTaskFuture.isCancelled()).isTrue(); 146 147 Log.i(TAG, "downloadTask is cancelled!"); 148 } 149 }, 150 DOWNLOAD_EXECUTOR); 151 return downloadTaskFuture; 152 } 153 }); 154 155 // Use never finish downloader to test whether the cancellation on the downloadFuture would 156 // cancel all the parent futures. 157 TestFileGroupPopulator testFileGroupPopulator = new TestFileGroupPopulator(context); 158 MobileDataDownload mobileDataDownload = 159 builderForTest() 160 .setFileDownloaderSupplier(() -> blockingFileDownloader) 161 .addFileGroupPopulator(testFileGroupPopulator) 162 .build(); 163 164 testFileGroupPopulator 165 .refreshFileGroups(mobileDataDownload) 166 .get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 167 168 // Now start to download the file group. 169 ListenableFuture<ClientFileGroup> downloadFileGroupFuture = 170 mobileDataDownload.downloadFileGroup( 171 DownloadFileGroupRequest.newBuilder().setGroupName(FILE_GROUP_NAME).build()); 172 173 // Note: we could have a race condition here between when we call the 174 // downloadFileGroupFuture.cancel and when the FileDownloader.startDownloading is executed. 175 // The following call will ensure that we will only call cancel on the downloadFileGroupFuture 176 // when the actual download has happened (the downloadTaskFuture). 177 // This will block until the downloadTaskFuture starts. 178 blockingFileDownloader.waitForDownloadStarted(); 179 180 // Cancel the downloadFileGroupFuture, it should cascade cancellation to downloadTaskFuture. 181 downloadFileGroupFuture.cancel(true /*may interrupt*/); 182 183 // Allow the download to continue and trigger our delegate FileDownloader. If the future isn't 184 // cancelled, the onSuccess callback should fail the test. 185 blockingFileDownloader.finishDownloading(); 186 blockingFileDownloader.waitForDownloadCompleted(); 187 188 assertThat(downloadFileGroupFuture.isCancelled()).isTrue(); 189 190 mobileDataDownload.clear().get(MAX_MDD_API_WAIT_TIME_SECS, SECONDS); 191 } 192 193 /** 194 * Returns MDD Builder with common dependencies set -- additional dependencies are added in each 195 * test as needed. 196 */ builderForTest()197 private MobileDataDownloadBuilder builderForTest() { 198 199 return MobileDataDownloadBuilder.newBuilder() 200 .setContext(context) 201 .setControlExecutor(controlExecutor) 202 .setFileStorage(fileStorage) 203 .setTaskScheduler(Optional.of(mockTaskScheduler)) 204 .setDeltaDecoderOptional(Optional.absent()) 205 .setNetworkUsageMonitor(mockNetworkUsageMonitor) 206 .setDownloadMonitorOptional(Optional.of(mockDownloadProgressMonitor)) 207 .setFlagsOptional(Optional.of(flags)); 208 } 209 } 210