eaac3bdc185c54f68bab8a2b6b0f56fd631c222e
[android/platform/packages/providers/DownloadProvider.git] / tests / src / com / android / providers / downloads / StorageTest.java
1 /*
2  * Copyright (C) 2014 The Android Open Source Project
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
17 package com.android.providers.downloads;
18
19 import static android.app.DownloadManager.COLUMN_REASON;
20 import static android.app.DownloadManager.ERROR_INSUFFICIENT_SPACE;
21 import static android.app.DownloadManager.STATUS_FAILED;
22 import static android.app.DownloadManager.STATUS_SUCCESSFUL;
23 import static android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION;
24 import static android.provider.Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION;
25
26 import android.app.DownloadManager;
27 import android.content.pm.PackageManager;
28 import android.os.Environment;
29 import android.os.StatFs;
30 import android.provider.Downloads.Impl;
31 import android.test.MoreAsserts;
32 import android.util.Log;
33
34 import com.android.providers.downloads.StorageUtils.ObserverLatch;
35 import com.google.mockwebserver.MockResponse;
36 import com.google.mockwebserver.SocketPolicy;
37
38 import libcore.io.ErrnoException;
39 import libcore.io.ForwardingOs;
40 import libcore.io.IoUtils;
41 import libcore.io.Libcore;
42 import libcore.io.Os;
43 import libcore.io.StructStatVfs;
44
45 import java.io.File;
46 import java.io.FileDescriptor;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.util.concurrent.TimeUnit;
50
51 public class StorageTest extends AbstractPublicApiTest {
52     private static final String TAG = "StorageTest";
53
54     private static final int DOWNLOAD_SIZE = 512 * 1024;
55     private static final byte[] DOWNLOAD_BODY;
56
57     static {
58         DOWNLOAD_BODY = new byte[DOWNLOAD_SIZE];
59         for (int i = 0; i < DOWNLOAD_SIZE; i++) {
60             DOWNLOAD_BODY[i] = (byte) (i % 32);
61         }
62     }
63
64     private Os mOriginal;
65     private long mStealBytes;
66
67     public StorageTest() {
68         super(new FakeSystemFacade());
69     }
70
71     @Override
72     protected void setUp() throws Exception {
73         super.setUp();
74
75         StorageUtils.sForceFullEviction = true;
76         mStealBytes = 0;
77
78         mOriginal = Libcore.os;
79         Libcore.os = new ForwardingOs(mOriginal) {
80             @Override
81             public StructStatVfs statvfs(String path) throws ErrnoException {
82                 return stealBytes(os.statvfs(path));
83             }
84
85             @Override
86             public StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException {
87                 return stealBytes(os.fstatvfs(fd));
88             }
89
90             private StructStatVfs stealBytes(StructStatVfs s) {
91                 final long stealBlocks = (mStealBytes + (s.f_bsize - 1)) / s.f_bsize;
92                 final long f_bavail = s.f_bavail - stealBlocks;
93                 return new StructStatVfs(s.f_bsize, s.f_frsize, s.f_blocks, s.f_bfree, f_bavail,
94                         s.f_files, s.f_ffree, s.f_favail, s.f_fsid, s.f_flag, s.f_namemax);
95             }
96         };
97     }
98
99     @Override
100     protected void tearDown() throws Exception {
101         super.tearDown();
102
103         StorageUtils.sForceFullEviction = false;
104         mStealBytes = 0;
105
106         if (mOriginal != null) {
107             Libcore.os = mOriginal;
108         }
109     }
110
111     private enum CacheStatus { CLEAN, DIRTY }
112     private enum BodyType { COMPLETE, CHUNKED }
113
114     public void testDataDirtyComplete() throws Exception {
115         prepareAndRunDownload(DESTINATION_CACHE_PARTITION,
116                 CacheStatus.DIRTY, BodyType.COMPLETE,
117                 STATUS_SUCCESSFUL, -1);
118     }
119
120     public void testDataDirtyChunked() throws Exception {
121         prepareAndRunDownload(DESTINATION_CACHE_PARTITION,
122                 CacheStatus.DIRTY, BodyType.CHUNKED,
123                 STATUS_SUCCESSFUL, -1);
124     }
125
126     public void testDataCleanComplete() throws Exception {
127         prepareAndRunDownload(DESTINATION_CACHE_PARTITION,
128                 CacheStatus.CLEAN, BodyType.COMPLETE,
129                 STATUS_FAILED, ERROR_INSUFFICIENT_SPACE);
130     }
131
132     public void testDataCleanChunked() throws Exception {
133         prepareAndRunDownload(DESTINATION_CACHE_PARTITION,
134                 CacheStatus.CLEAN, BodyType.CHUNKED,
135                 STATUS_FAILED, ERROR_INSUFFICIENT_SPACE);
136     }
137
138     public void testCacheDirtyComplete() throws Exception {
139         prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION,
140                 CacheStatus.DIRTY, BodyType.COMPLETE,
141                 STATUS_SUCCESSFUL, -1);
142     }
143
144     public void testCacheDirtyChunked() throws Exception {
145         prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION,
146                 CacheStatus.DIRTY, BodyType.CHUNKED,
147                 STATUS_SUCCESSFUL, -1);
148     }
149
150     public void testCacheCleanComplete() throws Exception {
151         prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION,
152                 CacheStatus.CLEAN, BodyType.COMPLETE,
153                 STATUS_FAILED, ERROR_INSUFFICIENT_SPACE);
154     }
155
156     public void testCacheCleanChunked() throws Exception {
157         prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION,
158                 CacheStatus.CLEAN, BodyType.CHUNKED,
159                 STATUS_FAILED, ERROR_INSUFFICIENT_SPACE);
160     }
161
162     private void prepareAndRunDownload(
163             int dest, CacheStatus cache, BodyType body, int expectedStatus, int expectedReason)
164             throws Exception {
165
166         // Ensure that we've purged everything possible for destination
167         final File dirtyDir;
168         if (dest == DESTINATION_CACHE_PARTITION) {
169             final PackageManager pm = getContext().getPackageManager();
170             final ObserverLatch observer = new ObserverLatch();
171             pm.freeStorageAndNotify(Long.MAX_VALUE, observer);
172
173             try {
174                 if (!observer.latch.await(30, TimeUnit.SECONDS)) {
175                     throw new IOException("Timeout while freeing disk space");
176                 }
177             } catch (InterruptedException e) {
178                 Thread.currentThread().interrupt();
179             }
180
181             dirtyDir = getContext().getCacheDir();
182
183         } else if (dest == DESTINATION_SYSTEMCACHE_PARTITION) {
184             IoUtils.deleteContents(Environment.getDownloadCacheDirectory());
185             dirtyDir = Environment.getDownloadCacheDirectory();
186
187         } else {
188             throw new IllegalArgumentException("Unknown destination");
189         }
190
191         // Allocate a cache file, if requested, making it large enough and old
192         // enough to clear.
193         final File dirtyFile;
194         if (cache == CacheStatus.DIRTY) {
195             dirtyFile = new File(dirtyDir, "cache_file.bin");
196             assertTrue(dirtyFile.createNewFile());
197             final FileOutputStream os = new FileOutputStream(dirtyFile);
198             final int dirtySize = (DOWNLOAD_SIZE * 3) / 2;
199             Libcore.os.posix_fallocate(os.getFD(), 0, dirtySize);
200             IoUtils.closeQuietly(os);
201
202             dirtyFile.setLastModified(
203                     System.currentTimeMillis() - (StorageUtils.MIN_DELETE_AGE * 2));
204         } else {
205             dirtyFile = null;
206         }
207
208         // At this point, hide all other disk space to make the download fail;
209         // if we have a dirty cache file it can be cleared to let us proceed.
210         final long targetFree = StorageUtils.RESERVED_BYTES + (DOWNLOAD_SIZE / 2);
211
212         final StatFs stat = new StatFs(dirtyDir.getAbsolutePath());
213         Log.d(TAG, "Available bytes (before steal): " + stat.getAvailableBytes());
214         mStealBytes = stat.getAvailableBytes() - targetFree;
215
216         stat.restat(dirtyDir.getAbsolutePath());
217         Log.d(TAG, "Available bytes (after steal): " + stat.getAvailableBytes());
218
219         final MockResponse resp = new MockResponse().setResponseCode(200)
220                 .setHeader("Content-type", "text/plain")
221                 .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END);
222         if (body == BodyType.CHUNKED) {
223             resp.setChunkedBody(DOWNLOAD_BODY, 1021);
224         } else {
225             resp.setBody(DOWNLOAD_BODY);
226         }
227         enqueueResponse(resp);
228
229         final DownloadManager.Request req = getRequest();
230         if (dest == Impl.DESTINATION_SYSTEMCACHE_PARTITION) {
231             req.setDestinationToSystemCache();
232         }
233         final Download download = enqueueRequest(req);
234         download.runUntilStatus(expectedStatus);
235
236         if (expectedStatus == STATUS_SUCCESSFUL) {
237             MoreAsserts.assertEquals(DOWNLOAD_BODY, download.getRawContents());
238         }
239
240         if (expectedReason != -1) {
241             assertEquals(expectedReason, download.getLongField(COLUMN_REASON));
242         }
243
244         if (dirtyFile != null) {
245             assertFalse(dirtyFile.exists());
246         }
247     }
248 }