• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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