Java tutorial
/* * Copyright (C) 2016 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.sdklib.repository.legacy.remote.internal; import com.android.annotations.NonNull; import com.android.annotations.Nullable; import com.android.repository.io.FileOp; import com.android.repository.io.FileOpUtils; import com.android.repository.testframework.MockFileOp; import com.android.sdklib.AndroidLocationTestCase; import com.android.utils.Pair; import com.google.common.base.Charsets; import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; import org.apache.http.StatusLine; import org.apache.http.message.BasicHttpResponse; import org.apache.http.message.BasicStatusLine; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; public class DownloadCacheTest extends AndroidLocationTestCase { private OutputStreamMockFileOp mFileOp; private MockMonitor mMonitor; static class OutputStreamMockFileOp extends MockFileOp { Set<File> mWrittenFiles = new LinkedHashSet<>(); @NonNull @Override public OutputStream newFileOutputStream(@NonNull File file, boolean append) throws IOException { mWrittenFiles.add(file); return super.newFileOutputStream(file, append); } @NonNull @Override public OutputStream newFileOutputStream(@NonNull File file) throws IOException { mWrittenFiles.add(file); return super.newFileOutputStream(file); } @Override public void reset() { super.reset(); mWrittenFiles.clear(); } public String getWrittenFiles() { StringBuilder sb = new StringBuilder(); for (File f : mWrittenFiles) { sb.append('<').append(getAgnosticAbsPath(f)).append(": "); byte[] data = super.getContent(f); if (data == null) { sb.append("(stream not closed properly)>"); } else { sb.append('\'').append(new String(data)).append("'>"); } } return sb.toString(); } }; /** * A private version of DownloadCache that never calls {@link UrlOpener}. */ private static class NoDownloadCache extends DownloadCache { private final Map<String, Pair<InputStream, Integer>> mReplies = new HashMap<>(); public NoDownloadCache(@NonNull Strategy strategy) { super(strategy); } public NoDownloadCache(@NonNull FileOp fileOp, @NonNull Strategy strategy) { super(fileOp, strategy); } @Override protected Pair<InputStream, URLConnection> openUrl(@NonNull String url, boolean needsMarkResetSupport, @NonNull ITaskMonitor monitor, @Nullable Header[] headers) throws IOException { Pair<InputStream, Integer> reply = mReplies.get(url); if (reply != null) { return Pair.of(reply.getFirst(), new HttpURLConnection(new URL(url)) { @Override public void disconnect() { } @Override public boolean usingProxy() { return false; } @Override public void connect() throws IOException { } @Override public int getResponseCode() throws IOException { return reply.getSecond(); } }); } // http-client's behavior is to return a FNF instead of 404. throw new FileNotFoundException(url); } public void registerResponse(@NonNull String url, int httpCode, @Nullable String content) { InputStream is = null; if (content != null) { is = new ByteArrayInputStream(content.getBytes(Charsets.UTF_8)); } Pair<InputStream, Integer> reply = Pair.of(is, httpCode); mReplies.put(url, reply); } } @Override public void setUp() throws Exception { super.setUp(); mFileOp = new OutputStreamMockFileOp(); mMonitor = new MockMonitor(); } @Override public void tearDown() throws Exception { super.tearDown(); } public void testMissingResource() throws Exception { // Downloads must fail when using the only-cache strategy and there's nothing in the cache. // In that case, it returns null to indicate the resource is simply not found. // Since the mock implementation always returns a 404 and no stream, there is no // difference between the various cache strategies. mFileOp.reset(); NoDownloadCache d1 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.ONLY_CACHE); InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor); assertNull(is1); assertEquals("", mMonitor.getAllCapturedLogs()); assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot())); assertEquals("", mFileOp.getWrittenFiles()); // HTTP-Client's behavior is to return a FNF instead of 404 so we'll try that first mFileOp.reset(); NoDownloadCache d2 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.DIRECT); try { d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor); fail("Expected: NoDownloadCache.openCachedUrl should have thrown a FileNotFoundException"); } catch (FileNotFoundException e) { assertEquals("http://www.example.com/download1.xml", e.getMessage()); } assertEquals("", mMonitor.getAllCapturedLogs()); assertEquals("", mFileOp.getWrittenFiles()); // Try again but this time we'll define a 404 reply to test the rest of the code path. mFileOp.reset(); d2.registerResponse("http://www.example.com/download1.xml", 404, null); InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor); assertNull(is2); assertEquals("", mMonitor.getAllCapturedLogs()); assertEquals("", mFileOp.getWrittenFiles()); mFileOp.reset(); NoDownloadCache d3 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.SERVE_CACHE); d3.registerResponse("http://www.example.com/download1.xml", 404, null); InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor); assertNull(is3); assertEquals("", mMonitor.getAllCapturedLogs()); assertEquals("", mFileOp.getWrittenFiles()); mFileOp.reset(); NoDownloadCache d4 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.FRESH_CACHE); d4.registerResponse("http://www.example.com/download1.xml", 404, null); InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor); assertNull(is4); assertEquals("", mMonitor.getAllCapturedLogs()); assertEquals("", mFileOp.getWrittenFiles()); } public void testExistingResource() throws Exception { // The resource exists but only-cache doesn't hit the network so it will // fail when the resource is not cached. mFileOp.reset(); NoDownloadCache d1 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.ONLY_CACHE); d1.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah"); InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor); assertNull(is1); assertEquals("", mMonitor.getAllCapturedLogs()); assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot())); assertEquals("", mFileOp.getWrittenFiles()); // HTTP-Client's behavior is to return a FNF instead of 404 so we'll try that first mFileOp.reset(); NoDownloadCache d2 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.DIRECT); d2.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah"); InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor); assertNotNull(is2); assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is2, Charsets.UTF_8)).readLine()); assertEquals("", mMonitor.getAllCapturedLogs()); assertEquals("", mFileOp.getWrittenFiles()); mFileOp.reset(); NoDownloadCache d3 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.SERVE_CACHE); d3.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah"); InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor); assertNotNull(is3); assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is3, Charsets.UTF_8)).readLine()); assertEquals("", mMonitor.getAllCapturedLogs()); assertEquals("<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'Blah blah blah'>" + "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" + "#<creation timestamp>\n" + "URL=http\\://www.example.com/download1.xml\n" + "Status-Code=200\n" + "'>", sanitize(d3, mFileOp.getWrittenFiles())); mFileOp.reset(); NoDownloadCache d4 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.FRESH_CACHE); d4.registerResponse("http://www.example.com/download1.xml", 200, "Blah blah blah"); InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor); assertNotNull(is4); assertEquals("Blah blah blah", new BufferedReader(new InputStreamReader(is4, Charsets.UTF_8)).readLine()); assertEquals("", mMonitor.getAllCapturedLogs()); assertEquals("<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'Blah blah blah'>" + "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" + "#<creation timestamp>\n" + "URL=http\\://www.example.com/download1.xml\n" + "Status-Code=200\n" + "'>", sanitize(d4, mFileOp.getWrittenFiles())); } public void testCachedResource() throws Exception { mFileOp.reset(); NoDownloadCache d1 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.ONLY_CACHE); d1.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content"); mFileOp.recordExistingFile( mFileOp.getAgnosticAbsPath( FileOpUtils.append(d1.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")), 123456L, "This is the cached content"); mFileOp.recordExistingFile( mFileOp.getAgnosticAbsPath( FileOpUtils.append(d1.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")), 123456L, "URL=http\\://www.example.com/download1.xml\n" + "Status-Code=200\n"); InputStream is1 = d1.openCachedUrl("http://www.example.com/download1.xml", mMonitor); // Only-cache strategy returns the value from the cache, not the actual resource. assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is1, Charsets.UTF_8)).readLine()); assertEquals("", mMonitor.getAllCapturedLogs()); assertTrue(mFileOp.hasRecordedExistingFolder(d1.getCacheRoot())); // The cache hasn't been modified, only read assertEquals("", mFileOp.getWrittenFiles()); } public void testCachedResource2() throws Exception { // Direct ignores the cache. mFileOp.reset(); NoDownloadCache d2 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.DIRECT); d2.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content"); mFileOp.recordExistingFile( mFileOp.getAgnosticAbsPath( FileOpUtils.append(d2.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")), 123456L, "This is the cached content"); mFileOp.recordExistingFile( mFileOp.getAgnosticAbsPath( FileOpUtils.append(d2.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")), 123456L, "URL=http\\://www.example.com/download1.xml\n" + "Status-Code=200\n"); InputStream is2 = d2.openCachedUrl("http://www.example.com/download1.xml", mMonitor); // Direct strategy ignores the cache. assertEquals("This is the new content", new BufferedReader(new InputStreamReader(is2, Charsets.UTF_8)).readLine()); assertEquals("", mMonitor.getAllCapturedLogs()); assertTrue(mFileOp.hasRecordedExistingFolder(d2.getCacheRoot())); // Direct strategy doesn't update the cache. assertEquals("", mFileOp.getWrittenFiles()); } public void testCachedResource3() throws Exception { // Serve-cache reads from the cache if available, ignoring its freshness (here the timestamp // is way older than the 10-minute freshness encoded in the DownloadCache.) mFileOp.reset(); NoDownloadCache d3 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.SERVE_CACHE); d3.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content"); mFileOp.recordExistingFile( mFileOp.getAgnosticAbsPath( FileOpUtils.append(d3.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")), 123456L, "This is the cached content"); mFileOp.recordExistingFile( mFileOp.getAgnosticAbsPath( FileOpUtils.append(d3.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")), 123456L, "URL=http\\://www.example.com/download1.xml\n" + "Status-Code=200\n"); InputStream is3 = d3.openCachedUrl("http://www.example.com/download1.xml", mMonitor); // We get content from the cache. assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is3, Charsets.UTF_8)).readLine()); assertEquals("", mMonitor.getAllCapturedLogs()); assertTrue(mFileOp.hasRecordedExistingFolder(d3.getCacheRoot())); // Cache isn't updated since nothing fresh was read. assertEquals("", mFileOp.getWrittenFiles()); } public void testCachedResource4() throws Exception { // fresh-cache reads the cache, finds it stale (here the timestamp // is way older than the 10-minute freshness encoded in the DownloadCache) // and will fetch the new resource instead and update the cache. mFileOp.reset(); NoDownloadCache d4 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.FRESH_CACHE); d4.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content"); mFileOp.recordExistingFile( mFileOp.getAgnosticAbsPath( FileOpUtils.append(d4.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")), 123456L, "This is the cached content"); mFileOp.recordExistingFile( mFileOp.getAgnosticAbsPath( FileOpUtils.append(d4.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")), 123456L, "URL=http\\://www.example.com/download1.xml\n" + "Status-Code=200\n"); InputStream is4 = d4.openCachedUrl("http://www.example.com/download1.xml", mMonitor); // Cache is discarded, actual resource is returned. assertEquals("This is the new content", new BufferedReader(new InputStreamReader(is4, Charsets.UTF_8)).readLine()); assertEquals("", mMonitor.getAllCapturedLogs()); assertTrue(mFileOp.hasRecordedExistingFolder(d4.getCacheRoot())); // Cache isn updated since something fresh was read. assertEquals("<$CACHE/sdkbin-1_9b8dc757-download1_xml: 'This is the new content'>" + "<$CACHE/sdkinf-1_9b8dc757-download1_xml: '### Meta data for SDK Manager cache. Do not modify.\n" + "#<creation timestamp>\n" + "URL=http\\://www.example.com/download1.xml\n" + "Status-Code=200\n" + "'>", sanitize(d4, mFileOp.getWrittenFiles())); } public void testCachedResource5() throws Exception { // fresh-cache reads the cache, finds it still valid stale (less than 10-minute old), // and uses the cached resource. mFileOp.reset(); NoDownloadCache d5 = new NoDownloadCache(mFileOp, DownloadCache.Strategy.FRESH_CACHE); d5.registerResponse("http://www.example.com/download1.xml", 200, "This is the new content"); mFileOp.recordExistingFile( mFileOp.getAgnosticAbsPath( FileOpUtils.append(d5.getCacheRoot(), "sdkbin-1_9b8dc757-download1_xml")), System.currentTimeMillis() - 1000, "This is the cached content"); mFileOp.recordExistingFile( mFileOp.getAgnosticAbsPath( FileOpUtils.append(d5.getCacheRoot(), "sdkinf-1_9b8dc757-download1_xml")), System.currentTimeMillis() - 1000, "URL=http\\://www.example.com/download1.xml\n" + "Status-Code=200\n"); InputStream is5 = d5.openCachedUrl("http://www.example.com/download1.xml", mMonitor); // Cache is used. assertEquals("This is the cached content", new BufferedReader(new InputStreamReader(is5, Charsets.UTF_8)).readLine()); assertEquals("", mMonitor.getAllCapturedLogs()); assertTrue(mFileOp.hasRecordedExistingFolder(d5.getCacheRoot())); // Cache isn't updated since nothing fresh was read. assertEquals("", mFileOp.getWrittenFiles()); } // -------- @Nullable private String sanitize(@NonNull DownloadCache dc, @Nullable String msg) { if (msg != null) { msg = msg.replace("\r\n", "\n"); String absRoot = mFileOp.getAgnosticAbsPath(dc.getCacheRoot()); msg = msg.replace(absRoot, "$CACHE"); // Cached files also contain a creation timestamp which we need to find and remove. msg = msg.replaceAll("\n#[A-Z][A-Za-z0-9: ]+20[0-9]{2}\n", "\n#<creation timestamp>\n"); } return msg; } }