diff options
Diffstat (limited to 'tests/src/com/android/providers/downloads')
7 files changed, 350 insertions, 46 deletions
diff --git a/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java b/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java index 3b937389..28c5dc7d 100644 --- a/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java +++ b/tests/src/com/android/providers/downloads/AbstractDownloadProviderFunctionalTest.java @@ -74,15 +74,18 @@ public abstract class AbstractDownloadProviderFunctionalTest extends static class MockContentResolverWithNotify extends MockContentResolver { public boolean mNotifyWasCalled = false; + public MockContentResolverWithNotify(Context context) { + super(context); + } + public synchronized void resetNotified() { mNotifyWasCalled = false; } @Override - public synchronized void notifyChange(Uri uri, ContentObserver observer, - boolean syncToNetwork) { + public synchronized void notifyChange( + Uri uri, ContentObserver observer, boolean syncToNetwork) { mNotifyWasCalled = true; - notifyAll(); } } @@ -94,20 +97,17 @@ public abstract class AbstractDownloadProviderFunctionalTest extends static class TestContext extends RenamingDelegatingContext { private static final String FILENAME_PREFIX = "test."; - private ContentResolver mResolver; + private final ContentResolver mResolver; private final NotificationManager mNotifManager; boolean mHasServiceBeenStarted = false; public TestContext(Context realContext) { super(realContext, FILENAME_PREFIX); + mResolver = new MockContentResolverWithNotify(this); mNotifManager = mock(NotificationManager.class); } - public void setResolver(ContentResolver resolver) { - mResolver = resolver; - } - /** * Direct DownloadService to our test instance of DownloadProvider. */ @@ -156,12 +156,20 @@ public abstract class AbstractDownloadProviderFunctionalTest extends System.setProperty("dexmaker.dexcache", getContext().getCacheDir().toString()); final Context realContext = getContext(); + mTestContext = new TestContext(realContext); - setupProviderAndResolver(); - mTestContext.setResolver(mResolver); + mResolver = (MockContentResolverWithNotify) mTestContext.getContentResolver(); + + final DownloadProvider provider = new DownloadProvider(); + provider.mSystemFacade = mSystemFacade; + provider.attachInfo(mTestContext, null); + + mResolver.addProvider(PROVIDER_AUTHORITY, provider); + setContext(mTestContext); setupService(); getService().mSystemFacade = mSystemFacade; + mSystemFacade.setUp(); assertTrue(isDatabaseEmpty()); // ensure we're not messing with real data mServer = new MockWebServer(); @@ -186,14 +194,6 @@ public abstract class AbstractDownloadProviderFunctionalTest extends } } - void setupProviderAndResolver() { - DownloadProvider provider = new DownloadProvider(); - provider.mSystemFacade = mSystemFacade; - provider.attachInfo(mTestContext, null); - mResolver = new MockContentResolverWithNotify(); - mResolver.addProvider(PROVIDER_AUTHORITY, provider); - } - /** * Remove any downloaded files and delete any lingering downloads. */ diff --git a/tests/src/com/android/providers/downloads/AbstractPublicApiTest.java b/tests/src/com/android/providers/downloads/AbstractPublicApiTest.java index 348dbd1b..2846c7af 100644 --- a/tests/src/com/android/providers/downloads/AbstractPublicApiTest.java +++ b/tests/src/com/android/providers/downloads/AbstractPublicApiTest.java @@ -28,6 +28,9 @@ import android.os.ParcelFileDescriptor; import android.os.SystemClock; import android.util.Log; +import libcore.io.IoUtils; +import libcore.io.Streams; + import java.io.InputStream; import java.net.MalformedURLException; import java.net.UnknownHostException; @@ -91,19 +94,23 @@ public abstract class AbstractPublicApiTest extends AbstractDownloadProviderFunc } } - String getContents() throws Exception { + byte[] getRawContents() throws Exception { ParcelFileDescriptor downloadedFile = mManager.openDownloadedFile(mId); assertTrue("Invalid file descriptor: " + downloadedFile, downloadedFile.getFileDescriptor().valid()); - final InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream( + final InputStream is = new ParcelFileDescriptor.AutoCloseInputStream( downloadedFile); try { - return readStream(stream); + return Streams.readFully(is); } finally { - stream.close(); + IoUtils.closeQuietly(is); } } + String getContents() throws Exception { + return new String(getRawContents()); + } + void runUntilStatus(int status) throws TimeoutException { final long startMillis = mSystemFacade.currentTimeMillis(); startService(null); diff --git a/tests/src/com/android/providers/downloads/FakeSystemFacade.java b/tests/src/com/android/providers/downloads/FakeSystemFacade.java index d54c1224..5a15d399 100644 --- a/tests/src/com/android/providers/downloads/FakeSystemFacade.java +++ b/tests/src/com/android/providers/downloads/FakeSystemFacade.java @@ -64,12 +64,12 @@ public class FakeSystemFacade implements SystemFacade { @Override public Long getMaxBytesOverMobile() { - return mMaxBytesOverMobile ; + return mMaxBytesOverMobile; } @Override public Long getRecommendedMaxBytesOverMobile() { - return mRecommendedMaxBytesOverMobile ; + return mRecommendedMaxBytesOverMobile; } @Override diff --git a/tests/src/com/android/providers/downloads/HelpersTest.java b/tests/src/com/android/providers/downloads/HelpersTest.java index 50f4c44c..121b7cda 100644 --- a/tests/src/com/android/providers/downloads/HelpersTest.java +++ b/tests/src/com/android/providers/downloads/HelpersTest.java @@ -16,29 +16,73 @@ package com.android.providers.downloads; +import android.net.Uri; import android.provider.Downloads; import android.test.AndroidTestCase; -import android.test.suitebuilder.annotation.LargeTest; +import android.test.suitebuilder.annotation.SmallTest; + +import libcore.io.IoUtils; + +import java.io.File; /** * This test exercises methods in the {@Helpers} utility class. */ -@LargeTest +@SmallTest public class HelpersTest extends AndroidTestCase { - public HelpersTest() { + @Override + protected void tearDown() throws Exception { + IoUtils.deleteContents(getContext().getFilesDir()); + IoUtils.deleteContents(getContext().getCacheDir()); + + super.tearDown(); + } + + public void testGenerateSaveFile() throws Exception { + final File expected = new File(getContext().getFilesDir(), "file.mp4"); + final String actual = Helpers.generateSaveFile(getContext(), + "http://example.com/file.txt", null, null, null, + "video/mp4", Downloads.Impl.DESTINATION_CACHE_PARTITION); + assertEquals(expected.getAbsolutePath(), actual); + } + + public void testGenerateSaveFileDupes() throws Exception { + final File expected1 = new File(getContext().getFilesDir(), "file.txt"); + final String actual1 = Helpers.generateSaveFile(getContext(), "http://example.com/file.txt", + null, null, null, null, Downloads.Impl.DESTINATION_CACHE_PARTITION); + + final File expected2 = new File(getContext().getFilesDir(), "file-1.txt"); + final String actual2 = Helpers.generateSaveFile(getContext(), "http://example.com/file.txt", + null, null, null, null, Downloads.Impl.DESTINATION_CACHE_PARTITION); + + assertEquals(expected1.getAbsolutePath(), actual1); + assertEquals(expected2.getAbsolutePath(), actual2); } - public void testGetFullPath() throws Exception { - String hint = "file:///com.android.providers.downloads/test"; + public void testGenerateSaveFileNoExtension() throws Exception { + final File expected = new File(getContext().getFilesDir(), "file.mp4"); + final String actual = Helpers.generateSaveFile(getContext(), + "http://example.com/file", null, null, null, + "video/mp4", Downloads.Impl.DESTINATION_CACHE_PARTITION); + assertEquals(expected.getAbsolutePath(), actual); + } + + public void testGenerateSaveFileHint() throws Exception { + final File expected = new File(getContext().getFilesDir(), "meow"); + final String hint = Uri.fromFile(expected).toString(); - // Test that we never change requested filename. - String fileName = Helpers.getFullPath( - hint, - "video/mp4", // MIME type corresponding to file extension .mp4 - Downloads.Impl.DESTINATION_FILE_URI, - null); - assertEquals(hint, fileName); + // Test that we never change requested filename. + final String actual = Helpers.generateSaveFile(getContext(), "url", hint, + "dispo", "locat", "video/mp4", Downloads.Impl.DESTINATION_FILE_URI); + assertEquals(expected.getAbsolutePath(), actual); } + public void testGenerateSaveFileDisposition() throws Exception { + final File expected = new File(getContext().getFilesDir(), "real.mp4"); + final String actual = Helpers.generateSaveFile(getContext(), + "http://example.com/file.txt", null, "attachment; filename=\"subdir/real.pdf\"", + null, "video/mp4", Downloads.Impl.DESTINATION_CACHE_PARTITION); + assertEquals(expected.getAbsolutePath(), actual); + } } diff --git a/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java b/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java index bde95815..d7b389c5 100644 --- a/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java +++ b/tests/src/com/android/providers/downloads/PublicApiFunctionalTest.java @@ -53,6 +53,8 @@ import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.RecordedRequest; import com.google.mockwebserver.SocketPolicy; +import libcore.io.IoUtils; + import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -83,9 +85,7 @@ public class PublicApiFunctionalTest extends AbstractPublicApiTest { mTestDirectory = new File(Environment.getExternalStorageDirectory() + File.separator + "download_manager_functional_test"); if (mTestDirectory.exists()) { - for (File file : mTestDirectory.listFiles()) { - file.delete(); - } + IoUtils.deleteContents(mTestDirectory); } else { mTestDirectory.mkdir(); } @@ -94,9 +94,7 @@ public class PublicApiFunctionalTest extends AbstractPublicApiTest { @Override protected void tearDown() throws Exception { if (mTestDirectory != null && mTestDirectory.exists()) { - for (File file : mTestDirectory.listFiles()) { - file.delete(); - } + IoUtils.deleteContents(mTestDirectory); mTestDirectory.delete(); } super.tearDown(); @@ -223,7 +221,7 @@ public class PublicApiFunctionalTest extends AbstractPublicApiTest { boolean isFirstResponse = (start == 0); int status = isFirstResponse ? HTTP_OK : HTTP_PARTIAL; MockResponse response = buildResponse(status, FILE_CONTENT.substring(start, end)) - .setHeader("Content-length", totalLength) + .setHeader("Content-length", isFirstResponse ? totalLength : (end - start)) .setHeader("Etag", ETAG); if (!isFirstResponse) { response.setHeader( @@ -475,7 +473,7 @@ public class PublicApiFunctionalTest extends AbstractPublicApiTest { // 2. Try resuming A, but fail ETag check mSystemFacade.incrementTimeMillis(RETRY_DELAY_MILLIS); download.runUntilStatus(STATUS_FAILED); - assertEquals(HTTP_PRECON_FAILED, download.getReason()); + assertEquals(DownloadManager.ERROR_CANNOT_RESUME, download.getReason()); req = takeRequest(); assertEquals("bytes=2-", getHeaderValue(req, "Range")); assertEquals(A, getHeaderValue(req, "If-Match")); diff --git a/tests/src/com/android/providers/downloads/StorageTest.java b/tests/src/com/android/providers/downloads/StorageTest.java new file mode 100644 index 00000000..eaac3bdc --- /dev/null +++ b/tests/src/com/android/providers/downloads/StorageTest.java @@ -0,0 +1,248 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.providers.downloads; + +import static android.app.DownloadManager.COLUMN_REASON; +import static android.app.DownloadManager.ERROR_INSUFFICIENT_SPACE; +import static android.app.DownloadManager.STATUS_FAILED; +import static android.app.DownloadManager.STATUS_SUCCESSFUL; +import static android.provider.Downloads.Impl.DESTINATION_CACHE_PARTITION; +import static android.provider.Downloads.Impl.DESTINATION_SYSTEMCACHE_PARTITION; + +import android.app.DownloadManager; +import android.content.pm.PackageManager; +import android.os.Environment; +import android.os.StatFs; +import android.provider.Downloads.Impl; +import android.test.MoreAsserts; +import android.util.Log; + +import com.android.providers.downloads.StorageUtils.ObserverLatch; +import com.google.mockwebserver.MockResponse; +import com.google.mockwebserver.SocketPolicy; + +import libcore.io.ErrnoException; +import libcore.io.ForwardingOs; +import libcore.io.IoUtils; +import libcore.io.Libcore; +import libcore.io.Os; +import libcore.io.StructStatVfs; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +public class StorageTest extends AbstractPublicApiTest { + private static final String TAG = "StorageTest"; + + private static final int DOWNLOAD_SIZE = 512 * 1024; + private static final byte[] DOWNLOAD_BODY; + + static { + DOWNLOAD_BODY = new byte[DOWNLOAD_SIZE]; + for (int i = 0; i < DOWNLOAD_SIZE; i++) { + DOWNLOAD_BODY[i] = (byte) (i % 32); + } + } + + private Os mOriginal; + private long mStealBytes; + + public StorageTest() { + super(new FakeSystemFacade()); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + + StorageUtils.sForceFullEviction = true; + mStealBytes = 0; + + mOriginal = Libcore.os; + Libcore.os = new ForwardingOs(mOriginal) { + @Override + public StructStatVfs statvfs(String path) throws ErrnoException { + return stealBytes(os.statvfs(path)); + } + + @Override + public StructStatVfs fstatvfs(FileDescriptor fd) throws ErrnoException { + return stealBytes(os.fstatvfs(fd)); + } + + private StructStatVfs stealBytes(StructStatVfs s) { + final long stealBlocks = (mStealBytes + (s.f_bsize - 1)) / s.f_bsize; + final long f_bavail = s.f_bavail - stealBlocks; + return new StructStatVfs(s.f_bsize, s.f_frsize, s.f_blocks, s.f_bfree, f_bavail, + s.f_files, s.f_ffree, s.f_favail, s.f_fsid, s.f_flag, s.f_namemax); + } + }; + } + + @Override + protected void tearDown() throws Exception { + super.tearDown(); + + StorageUtils.sForceFullEviction = false; + mStealBytes = 0; + + if (mOriginal != null) { + Libcore.os = mOriginal; + } + } + + private enum CacheStatus { CLEAN, DIRTY } + private enum BodyType { COMPLETE, CHUNKED } + + public void testDataDirtyComplete() throws Exception { + prepareAndRunDownload(DESTINATION_CACHE_PARTITION, + CacheStatus.DIRTY, BodyType.COMPLETE, + STATUS_SUCCESSFUL, -1); + } + + public void testDataDirtyChunked() throws Exception { + prepareAndRunDownload(DESTINATION_CACHE_PARTITION, + CacheStatus.DIRTY, BodyType.CHUNKED, + STATUS_SUCCESSFUL, -1); + } + + public void testDataCleanComplete() throws Exception { + prepareAndRunDownload(DESTINATION_CACHE_PARTITION, + CacheStatus.CLEAN, BodyType.COMPLETE, + STATUS_FAILED, ERROR_INSUFFICIENT_SPACE); + } + + public void testDataCleanChunked() throws Exception { + prepareAndRunDownload(DESTINATION_CACHE_PARTITION, + CacheStatus.CLEAN, BodyType.CHUNKED, + STATUS_FAILED, ERROR_INSUFFICIENT_SPACE); + } + + public void testCacheDirtyComplete() throws Exception { + prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION, + CacheStatus.DIRTY, BodyType.COMPLETE, + STATUS_SUCCESSFUL, -1); + } + + public void testCacheDirtyChunked() throws Exception { + prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION, + CacheStatus.DIRTY, BodyType.CHUNKED, + STATUS_SUCCESSFUL, -1); + } + + public void testCacheCleanComplete() throws Exception { + prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION, + CacheStatus.CLEAN, BodyType.COMPLETE, + STATUS_FAILED, ERROR_INSUFFICIENT_SPACE); + } + + public void testCacheCleanChunked() throws Exception { + prepareAndRunDownload(DESTINATION_SYSTEMCACHE_PARTITION, + CacheStatus.CLEAN, BodyType.CHUNKED, + STATUS_FAILED, ERROR_INSUFFICIENT_SPACE); + } + + private void prepareAndRunDownload( + int dest, CacheStatus cache, BodyType body, int expectedStatus, int expectedReason) + throws Exception { + + // Ensure that we've purged everything possible for destination + final File dirtyDir; + if (dest == DESTINATION_CACHE_PARTITION) { + final PackageManager pm = getContext().getPackageManager(); + final ObserverLatch observer = new ObserverLatch(); + pm.freeStorageAndNotify(Long.MAX_VALUE, observer); + + try { + if (!observer.latch.await(30, TimeUnit.SECONDS)) { + throw new IOException("Timeout while freeing disk space"); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + + dirtyDir = getContext().getCacheDir(); + + } else if (dest == DESTINATION_SYSTEMCACHE_PARTITION) { + IoUtils.deleteContents(Environment.getDownloadCacheDirectory()); + dirtyDir = Environment.getDownloadCacheDirectory(); + + } else { + throw new IllegalArgumentException("Unknown destination"); + } + + // Allocate a cache file, if requested, making it large enough and old + // enough to clear. + final File dirtyFile; + if (cache == CacheStatus.DIRTY) { + dirtyFile = new File(dirtyDir, "cache_file.bin"); + assertTrue(dirtyFile.createNewFile()); + final FileOutputStream os = new FileOutputStream(dirtyFile); + final int dirtySize = (DOWNLOAD_SIZE * 3) / 2; + Libcore.os.posix_fallocate(os.getFD(), 0, dirtySize); + IoUtils.closeQuietly(os); + + dirtyFile.setLastModified( + System.currentTimeMillis() - (StorageUtils.MIN_DELETE_AGE * 2)); + } else { + dirtyFile = null; + } + + // At this point, hide all other disk space to make the download fail; + // if we have a dirty cache file it can be cleared to let us proceed. + final long targetFree = StorageUtils.RESERVED_BYTES + (DOWNLOAD_SIZE / 2); + + final StatFs stat = new StatFs(dirtyDir.getAbsolutePath()); + Log.d(TAG, "Available bytes (before steal): " + stat.getAvailableBytes()); + mStealBytes = stat.getAvailableBytes() - targetFree; + + stat.restat(dirtyDir.getAbsolutePath()); + Log.d(TAG, "Available bytes (after steal): " + stat.getAvailableBytes()); + + final MockResponse resp = new MockResponse().setResponseCode(200) + .setHeader("Content-type", "text/plain") + .setSocketPolicy(SocketPolicy.DISCONNECT_AT_END); + if (body == BodyType.CHUNKED) { + resp.setChunkedBody(DOWNLOAD_BODY, 1021); + } else { + resp.setBody(DOWNLOAD_BODY); + } + enqueueResponse(resp); + + final DownloadManager.Request req = getRequest(); + if (dest == Impl.DESTINATION_SYSTEMCACHE_PARTITION) { + req.setDestinationToSystemCache(); + } + final Download download = enqueueRequest(req); + download.runUntilStatus(expectedStatus); + + if (expectedStatus == STATUS_SUCCESSFUL) { + MoreAsserts.assertEquals(DOWNLOAD_BODY, download.getRawContents()); + } + + if (expectedReason != -1) { + assertEquals(expectedReason, download.getLongField(COLUMN_REASON)); + } + + if (dirtyFile != null) { + assertFalse(dirtyFile.exists()); + } + } +} diff --git a/tests/src/com/android/providers/downloads/ThreadingTest.java b/tests/src/com/android/providers/downloads/ThreadingTest.java index 920f703b..1e501444 100644 --- a/tests/src/com/android/providers/downloads/ThreadingTest.java +++ b/tests/src/com/android/providers/downloads/ThreadingTest.java @@ -27,6 +27,7 @@ import com.google.android.collect.Sets; import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.SocketPolicy; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -60,9 +61,10 @@ public class ThreadingTest extends AbstractPublicApiTest { public void testFilenameRace() throws Exception { final List<Pair<Download, String>> downloads = Lists.newArrayList(); + final HashSet<String> expectedBodies = Sets.newHashSet(); // Request dozen files at once with same name - for (int i = 0; i < 12; i++) { + for (int i = 0; i < 32; i++) { final String body = "DOWNLOAD " + i + " CONTENTS"; enqueueResponse(new MockResponse().setResponseCode(HTTP_OK).setBody(body) .setHeader("Content-type", "text/plain") @@ -70,6 +72,7 @@ public class ThreadingTest extends AbstractPublicApiTest { final Download d = enqueueRequest(getRequest()); downloads.add(Pair.create(d, body)); + expectedBodies.add(body); } // Kick off downloads in parallel @@ -82,6 +85,7 @@ public class ThreadingTest extends AbstractPublicApiTest { // Ensure that contents are clean and filenames unique final Set<String> seenFiles = Sets.newHashSet(); + final HashSet<String> actualBodies = Sets.newHashSet(); for (Pair<Download, String> d : downloads) { final String file = d.first.getStringField(DownloadManager.COLUMN_LOCAL_FILENAME); @@ -91,7 +95,10 @@ public class ThreadingTest extends AbstractPublicApiTest { final String expected = d.second; final String actual = d.first.getContents(); - assertEquals(expected, actual); + + actualBodies.add(actual); } + + assertEquals(expectedBodies, actualBodies); } } |