• 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.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