StorageTest.java revision dffbb9c4567e9d29d19964a83129e38dceab7055
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
17package com.android.providers.downloads;
18
19import static android.app.DownloadManager.COLUMN_REASON;
20import static android.app.DownloadManager.ERROR_INSUFFICIENT_SPACE;
21import static android.app.DownloadManager.STATUS_FAILED;
22import static android.app.DownloadManager.STATUS_SUCCESSFUL;
23import static android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION;
24import static android.provider.Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION;
25
26import android.app.DownloadManager;
27import android.content.pm.PackageManager;
28import android.os.Environment;
29import android.os.StatFs;
30import android.provider.Downloads.Impl;
31import android.test.MoreAsserts;
32import android.util.Log;
33
34import com.android.providers.downloads.StorageUtils.ObserverLatch;
35import com.google.mockwebserver.MockResponse;
36import com.google.mockwebserver.SocketPolicy;
37
38import libcore.io.ErrnoException;
39import libcore.io.ForwardingOs;
40import libcore.io.IoUtils;
41import libcore.io.Libcore;
42import libcore.io.Os;
43import libcore.io.StructStatVfs;
44
45import java.io.File;
46import java.io.FileDescriptor;
47import java.io.FileOutputStream;
48import java.io.IOException;
49import java.util.concurrent.TimeUnit;
50
51public 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}
249