diff options
Diffstat (limited to 'luni/src')
10 files changed, 354 insertions, 114 deletions
diff --git a/luni/src/main/java/java/util/zip/ZipFile.java b/luni/src/main/java/java/util/zip/ZipFile.java index 4b3e431..43e8567 100644 --- a/luni/src/main/java/java/util/zip/ZipFile.java +++ b/luni/src/main/java/java/util/zip/ZipFile.java @@ -33,6 +33,7 @@ import java.util.Iterator; import java.util.LinkedHashMap; import libcore.io.BufferIterator; import libcore.io.HeapBufferIterator; +import libcore.io.IoUtils; import libcore.io.Streams; /** @@ -199,7 +200,19 @@ public class ZipFile implements Closeable, ZipConstants { raf = new RandomAccessFile(filename, "r"); - readCentralDir(); + // Make sure to close the RandomAccessFile if reading the central directory fails. + boolean mustCloseFile = true; + try { + readCentralDir(); + + // Read succeeded so do not close the underlying RandomAccessFile. + mustCloseFile = false; + } finally { + if (mustCloseFile) { + IoUtils.closeQuietly(raf); + } + } + guard.open("close"); } diff --git a/luni/src/main/java/javax/net/ssl/SSLEngineResult.java b/luni/src/main/java/javax/net/ssl/SSLEngineResult.java index 8a98831..3360832 100644 --- a/luni/src/main/java/javax/net/ssl/SSLEngineResult.java +++ b/luni/src/main/java/javax/net/ssl/SSLEngineResult.java @@ -110,16 +110,16 @@ public class SSLEngineResult { public SSLEngineResult(SSLEngineResult.Status status, SSLEngineResult.HandshakeStatus handshakeStatus, int bytesConsumed, int bytesProduced) { if (status == null) { - throw new IllegalArgumentException("status is null"); + throw new IllegalArgumentException("status == null"); } if (handshakeStatus == null) { - throw new IllegalArgumentException("handshakeStatus is null"); + throw new IllegalArgumentException("handshakeStatus == null"); } if (bytesConsumed < 0) { - throw new IllegalArgumentException("bytesConsumed is negative"); + throw new IllegalArgumentException("bytesConsumed < 0: " + bytesConsumed); } if (bytesProduced < 0) { - throw new IllegalArgumentException("bytesProduced is negative"); + throw new IllegalArgumentException("bytesProduced < 0: " + bytesProduced); } this.status = status; this.handshakeStatus = handshakeStatus; diff --git a/luni/src/main/java/libcore/reflect/AnnotationAccess.java b/luni/src/main/java/libcore/reflect/AnnotationAccess.java index d63ad30..2a72c18 100644 --- a/luni/src/main/java/libcore/reflect/AnnotationAccess.java +++ b/luni/src/main/java/libcore/reflect/AnnotationAccess.java @@ -16,7 +16,6 @@ package libcore.reflect; -import com.android.dex.ClassDef; import com.android.dex.Dex; import com.android.dex.EncodedValueReader; import com.android.dex.FieldId; @@ -167,7 +166,7 @@ public final class AnnotationAccess { */ public static <A extends Annotation> A getDeclaredAnnotation( AnnotatedElement element, Class<A> annotationClass) { - com.android.dex.Annotation a = getMethodAnnotation(element, annotationClass); + com.android.dex.Annotation a = getAnnotation(element, annotationClass); return a != null ? toAnnotationInstance(getDexClass(element), annotationClass, a) : null; @@ -178,10 +177,10 @@ public final class AnnotationAccess { */ public static boolean isDeclaredAnnotationPresent( AnnotatedElement element, Class<? extends Annotation> annotationClass) { - return getMethodAnnotation(element, annotationClass) != null; + return getAnnotation(element, annotationClass) != null; } - private static com.android.dex.Annotation getMethodAnnotation( + private static com.android.dex.Annotation getAnnotation( AnnotatedElement element, Class<? extends Annotation> annotationClass) { int annotationSetOffset = getAnnotationSetOffset(element); if (annotationSetOffset == 0) { @@ -190,17 +189,17 @@ public final class AnnotationAccess { Class<?> dexClass = getDexClass(element); Dex dex = dexClass.getDex(); - int annotationTypeIndex = getTypeIndex(dex, annotationClass); - if (annotationTypeIndex == -1) { - return null; // The dex file doesn't use this annotation. - } - Dex.Section setIn = dex.open(annotationSetOffset); // annotation_set_item + String annotationInternalName = InternalNames.getInternalName(annotationClass); for (int i = 0, size = setIn.readInt(); i < size; i++) { int annotationOffset = setIn.readInt(); Dex.Section annotationIn = dex.open(annotationOffset); // annotation_item + // The internal string name of the annotation is compared here and deliberately not + // the value of annotationClass.getTypeIndex(). The annotationClass may have been + // defined by a different dex file, which would make the indexes incomparable. com.android.dex.Annotation candidate = annotationIn.readAnnotation(); - if (candidate.getTypeIndex() == annotationTypeIndex) { + String candidateInternalName = dex.typeNames().get(candidate.getTypeIndex()); + if (candidateInternalName.equals(annotationInternalName)) { return candidate; } } @@ -268,23 +267,6 @@ public final class AnnotationAccess { : ((Member) element).getDeclaringClass(); } - public static int getFieldIndex(Class<?> declaringClass, Class<?> type, String name) { - Dex dex = declaringClass.getDex(); - int declaringClassIndex = getTypeIndex(dex, declaringClass); - int typeIndex = getTypeIndex(dex, type); - int nameIndex = dex.findStringIndex(name); - FieldId fieldId = new FieldId(dex, declaringClassIndex, typeIndex, nameIndex); - return dex.findFieldIndex(fieldId); - } - - public static int getMethodIndex(Class<?> declaringClass, String name, int protoIndex) { - Dex dex = declaringClass.getDex(); - int declaringClassIndex = getTypeIndex(dex, declaringClass); - int nameIndex = dex.findStringIndex(name); - MethodId methodId = new MethodId(dex, declaringClassIndex, protoIndex, nameIndex); - return dex.findMethodIndex(methodId); - } - /** * Returns the parameter annotations on {@code member}. */ @@ -357,6 +339,8 @@ public final class AnnotationAccess { */ Class<?> annotationClass = method.getDeclaringClass(); + // All lookups of type and string indexes are within the Dex that declares the annotation so + // the indexes can be compared directly. Dex dex = annotationClass.getDex(); EncodedValueReader reader = getOnlyAnnotationValue( dex, annotationClass, "Ldalvik/annotation/AnnotationDefault;"); @@ -365,7 +349,7 @@ public final class AnnotationAccess { } int fieldCount = reader.readAnnotation(); - if (reader.getAnnotationType() != getTypeIndex(dex, annotationClass)) { + if (reader.getAnnotationType() != annotationClass.getDexTypeIndex()) { throw new AssertionError("annotation value type != annotation class"); } @@ -540,22 +524,6 @@ public final class AnnotationAccess { * was derived. */ - /** Find dex's type index for the class c */ - private static int getTypeIndex(Dex dex, Class<?> c) { - if (dex == c.getDex()) { - return c.getDexTypeIndex(); - } - if (dex == null) { - return -1; - } - int typeIndex = dex.findTypeIndex(InternalNames.getInternalName(c)); - if (typeIndex < 0) { - typeIndex = -1; - } - return typeIndex; - } - - private static EncodedValueReader getAnnotationReader( Dex dex, AnnotatedElement element, String annotationName, int expectedFieldCount) { int annotationSetOffset = getAnnotationSetOffset(element); diff --git a/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java b/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java index 8b8c729..6c7452d 100644 --- a/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java +++ b/luni/src/test/java/libcore/icu/AlphabeticIndexTest.java @@ -20,8 +20,7 @@ import java.util.Locale; public class AlphabeticIndexTest extends junit.framework.TestCase { private static AlphabeticIndex.ImmutableIndex createIndex(Locale locale) { - return new AlphabeticIndex(locale).addLabels(Locale.US) - .getImmutableIndex(); + return new AlphabeticIndex(locale).addLabels(Locale.US).getImmutableIndex(); } private static void assertHasLabel(AlphabeticIndex.ImmutableIndex ii, String string, String expectedLabel) { @@ -111,8 +110,8 @@ public class AlphabeticIndexTest extends junit.framework.TestCase { // German: [A-Z] (no ß or umlauted characters in standard alphabet) AlphabeticIndex.ImmutableIndex de = createIndex(Locale.GERMAN); assertHasLabel(de, "ßind", "S"); + // We no longer split out "S", "Sch", and "St". assertHasLabel(de, "Sacher", "S"); - // "Sch" and "St" are also options for lists by last name. assertHasLabel(de, "Schiller", "S"); assertHasLabel(de, "Steiff", "S"); } @@ -141,14 +140,19 @@ public class AlphabeticIndexTest extends junit.framework.TestCase { public void test_zh_CN() throws Exception { // Simplified Chinese (default collator Pinyin): [A-Z] - // Shen/Chen (simplified): should be, usually, 'S' for name collator and 'C' for apps/other AlphabeticIndex.ImmutableIndex zh_CN = createIndex(new Locale("zh", "CN")); // Jia/Gu: should be, usually, 'J' for name collator and 'G' for apps/other assertHasLabel(zh_CN, "\u8d3e", "J"); - // Shen/Chen - assertHasLabel(zh_CN, "\u6c88", "C"); // icu4c 50 does not specialize for names. + // Shen/Chen (simplified): should usually be 'S' for names and 'C' for apps/other. + // icu4c does not specialize for names and defaults to 'C'. + // Some OEMs prefer to default to 'S'. + // We allow either to pass CTS since neither choice is right all the time. + // assertHasLabel(zh_CN, "\u6c88", "C"); + String shenChenLabel = zh_CN.getBucketLabel(zh_CN.getBucketIndex("\u6c88")); + assertTrue(shenChenLabel.equals("C") || shenChenLabel.equals("S")); + // Shen/Chen (traditional) assertHasLabel(zh_CN, "\u700b", "S"); } diff --git a/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java b/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java index d03ae65..9766cef 100644 --- a/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java +++ b/luni/src/test/java/libcore/java/lang/ProcessBuilderTest.java @@ -22,10 +22,10 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.HashMap; import java.util.Map; -import libcore.dalvik.system.CloseGuardTester; +import libcore.java.util.AbstractResourceLeakageDetectorTestCase; import static tests.support.Support_Exec.execAndCheckOutput; -public class ProcessBuilderTest extends junit.framework.TestCase { +public class ProcessBuilderTest extends AbstractResourceLeakageDetectorTestCase { private static String shell() { String deviceSh = "/system/bin/sh"; @@ -84,14 +84,8 @@ public class ProcessBuilderTest extends junit.framework.TestCase { } public void testDestroyDoesNotLeak() throws IOException { - CloseGuardTester closeGuardTester = new CloseGuardTester(); - try { - Process process = new ProcessBuilder(shell(), "-c", "echo out; echo err 1>&2").start(); - process.destroy(); - closeGuardTester.assertEverythingWasClosed(); - } finally { - closeGuardTester.close(); - } + Process process = new ProcessBuilder(shell(), "-c", "echo out; echo err 1>&2").start(); + process.destroy(); } public void testEnvironmentMapForbidsNulls() throws Exception { diff --git a/luni/src/test/java/libcore/java/net/URLConnectionTest.java b/luni/src/test/java/libcore/java/net/URLConnectionTest.java index b40976e..e945e60 100644 --- a/luni/src/test/java/libcore/java/net/URLConnectionTest.java +++ b/luni/src/test/java/libcore/java/net/URLConnectionTest.java @@ -21,9 +21,7 @@ import com.google.mockwebserver.MockResponse; import com.google.mockwebserver.MockWebServer; import com.google.mockwebserver.RecordedRequest; import com.google.mockwebserver.SocketPolicy; -import dalvik.system.CloseGuard; import java.io.ByteArrayOutputStream; -import java.io.Closeable; import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -31,7 +29,6 @@ import java.io.OutputStream; import java.net.Authenticator; import java.net.CacheRequest; import java.net.CacheResponse; -import java.net.ConnectException; import java.net.HttpRetryException; import java.net.HttpURLConnection; import java.net.InetAddress; @@ -68,9 +65,8 @@ import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.X509TrustManager; -import junit.framework.TestCase; -import libcore.java.lang.ref.FinalizationTester; import libcore.java.security.TestKeyStore; +import libcore.java.util.AbstractResourceLeakageDetectorTestCase; import libcore.javax.net.ssl.TestSSLContext; import tests.net.StuckServer; @@ -80,7 +76,8 @@ import static com.google.mockwebserver.SocketPolicy.FAIL_HANDSHAKE; import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_INPUT_AT_END; import static com.google.mockwebserver.SocketPolicy.SHUTDOWN_OUTPUT_AT_END; -public final class URLConnectionTest extends TestCase { +public final class URLConnectionTest extends AbstractResourceLeakageDetectorTestCase { + private MockWebServer server; private HttpResponseCache cache; private String hostName; @@ -101,8 +98,10 @@ public final class URLConnectionTest extends TestCase { System.clearProperty("https.proxyHost"); System.clearProperty("https.proxyPort"); server.shutdown(); + server = null; if (cache != null) { cache.delete(); + cache = null; } super.tearDown(); } @@ -797,47 +796,17 @@ public final class URLConnectionTest extends TestCase { } public void testDisconnectAfterOnlyResponseCodeCausesNoCloseGuardWarning() throws IOException { - CloseGuardGuard guard = new CloseGuardGuard(); - try { - server.enqueue(new MockResponse() - .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) - .addHeader("Content-Encoding: gzip")); - server.play(); + server.enqueue(new MockResponse() + .setBody(gzip("ABCABCABC".getBytes("UTF-8"))) + .addHeader("Content-Encoding: gzip")); + server.play(); - HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + HttpURLConnection connection = (HttpURLConnection) server.getUrl("/").openConnection(); + try { assertEquals(200, connection.getResponseCode()); - connection.disconnect(); - connection = null; - assertFalse(guard.wasCloseGuardCalled()); } finally { - guard.close(); - } - } - - public static class CloseGuardGuard implements Closeable, CloseGuard.Reporter { - private final CloseGuard.Reporter oldReporter = CloseGuard.getReporter(); - - private AtomicBoolean closeGuardCalled = new AtomicBoolean(); - - public CloseGuardGuard() { - CloseGuard.setReporter(this); - } - - @Override public void report(String message, Throwable allocationSite) { - oldReporter.report(message, allocationSite); - closeGuardCalled.set(true); - } - - public boolean wasCloseGuardCalled() { - FinalizationTester.induceFinalization(); - close(); - return closeGuardCalled.get(); - } - - @Override public void close() { - CloseGuard.setReporter(oldReporter); + connection.disconnect(); } - } public void testDefaultRequestProperty() throws Exception { @@ -2272,11 +2241,15 @@ public final class URLConnectionTest extends TestCase { HttpsURLConnection connection = (HttpsURLConnection) server.getUrl("/").openConnection(); connection.setSSLSocketFactory(testSSLContext.clientContext.getSocketFactory()); connection.connect(); - assertNotNull(connection.getHostnameVerifier()); - assertNull(connection.getLocalCertificates()); - assertNotNull(connection.getServerCertificates()); - assertNotNull(connection.getCipherSuite()); - assertNotNull(connection.getPeerPrincipal()); + try { + assertNotNull(connection.getHostnameVerifier()); + assertNull(connection.getLocalCertificates()); + assertNotNull(connection.getServerCertificates()); + assertNotNull(connection.getCipherSuite()); + assertNotNull(connection.getPeerPrincipal()); + } finally { + connection.disconnect(); + } } /** diff --git a/luni/src/test/java/libcore/java/nio/channels/FileIOInterruptTest.java b/luni/src/test/java/libcore/java/nio/channels/FileIOInterruptTest.java index c06df04..02c6f3b 100644 --- a/luni/src/test/java/libcore/java/nio/channels/FileIOInterruptTest.java +++ b/luni/src/test/java/libcore/java/nio/channels/FileIOInterruptTest.java @@ -17,7 +17,6 @@ package libcore.java.nio.channels; import junit.framework.TestCase; -import android.system.ErrnoException; import android.system.OsConstants; import java.io.File; import java.io.FileInputStream; @@ -90,6 +89,9 @@ public class FileIOInterruptTest extends TestCase { super.tearDown(); fifoFile.delete(); VOGAR_DEVICE_TEMP_DIR.delete(); + + // Clear the interrupted state, if set. + Thread.interrupted(); } public void testStreamRead_exceptionWhenAlreadyClosed() throws Exception { @@ -208,6 +210,43 @@ public class FileIOInterruptTest extends TestCase { fifoWriter.tidyUp(); } + public void testChannelRead_exceptionWhenAlreadyInterrupted() throws Exception { + testChannelRead_exceptionWhenAlreadyInterrupted(ChannelReader.Method.READ); + } + + public void testChannelReadV_exceptionWhenAlreadyInterrupted() throws Exception { + testChannelRead_exceptionWhenAlreadyInterrupted(ChannelReader.Method.READV); + } + + private void testChannelRead_exceptionWhenAlreadyInterrupted(ChannelReader.Method method) + throws Exception { + FifoWriter fifoWriter = new FifoWriter(fifoFile); + fifoWriter.start(); + FileInputStream fis = new FileInputStream(fifoFile); + FileChannel fileInputChannel = fis.getChannel(); + + Thread.currentThread().interrupt(); + + ByteBuffer buffer = ByteBuffer.allocateDirect(10); + try { + if (method == ChannelReader.Method.READ) { + fileInputChannel.read(buffer); + } else { + ByteBuffer buffer2 = ByteBuffer.allocateDirect(10); + fileInputChannel.read(new ByteBuffer[] { buffer, buffer2}); + } + fail(); + } catch (IOException expected) { + assertSame(ClosedByInterruptException.class, expected.getClass()); + } + + // Check but also clear the interrupted status, so we can wait for the FifoWriter thread in + // tidyUp(). + assertTrue(Thread.interrupted()); + + fifoWriter.tidyUp(); + } + public void testChannelRead_exceptionOnCloseWhenBlocked() throws Exception { testChannelRead_exceptionOnCloseWhenBlocked(ChannelReader.Method.READ); } @@ -303,6 +342,43 @@ public class FileIOInterruptTest extends TestCase { fifoReader.tidyUp(); } + public void testChannelWrite_exceptionWhenAlreadyInterrupted() throws Exception { + testChannelWrite_exceptionWhenAlreadyInterrupted(ChannelWriter.Method.WRITE); + } + + public void testChannelWriteV_exceptionWhenAlreadyInterrupted() throws Exception { + testChannelWrite_exceptionWhenAlreadyInterrupted(ChannelWriter.Method.WRITEV); + } + + private void testChannelWrite_exceptionWhenAlreadyInterrupted(ChannelWriter.Method method) + throws Exception { + FifoReader fifoReader = new FifoReader(fifoFile); + fifoReader.start(); + FileOutputStream fos = new FileOutputStream(fifoFile); + FileChannel fileInputChannel = fos.getChannel(); + + Thread.currentThread().interrupt(); + + ByteBuffer buffer = ByteBuffer.allocateDirect(10); + try { + if (method == ChannelWriter.Method.WRITE) { + fileInputChannel.write(buffer); + } else { + ByteBuffer buffer2 = ByteBuffer.allocateDirect(10); + fileInputChannel.write(new ByteBuffer[] { buffer, buffer2 }); + } + fail(); + } catch (IOException expected) { + assertSame(ClosedByInterruptException.class, expected.getClass()); + } + + // Check but also clear the interrupted status, so we can wait for the FifoReader thread in + // tidyUp(). + assertTrue(Thread.interrupted()); + + fifoReader.tidyUp(); + } + public void testChannelWrite_exceptionOnCloseWhenBlocked() throws Exception { testChannelWrite_exceptionOnCloseWhenBlocked(ChannelWriter.Method.WRITE); } @@ -608,6 +684,9 @@ public class FileIOInterruptTest extends TestCase { } private static void waitToDie(Thread thread) { + // Protect against this thread already being interrupted, which would prevent the test waiting + // for the requested time. + assertFalse(Thread.currentThread().isInterrupted()); try { thread.join(5000); } catch (InterruptedException ignored) { @@ -619,6 +698,8 @@ public class FileIOInterruptTest extends TestCase { } private static void delay(int millis) { + // Protect against this thread being interrupted, which would prevent us waiting. + assertFalse(Thread.currentThread().isInterrupted()); try { Thread.sleep(millis); } catch (InterruptedException ignored) { diff --git a/luni/src/test/java/libcore/java/util/AbstractResourceLeakageDetectorTestCase.java b/luni/src/test/java/libcore/java/util/AbstractResourceLeakageDetectorTestCase.java new file mode 100644 index 0000000..5ea67d3 --- /dev/null +++ b/luni/src/test/java/libcore/java/util/AbstractResourceLeakageDetectorTestCase.java @@ -0,0 +1,44 @@ +/* + * 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 libcore.java.util; + +import junit.framework.TestCase; + +/** + * Ensures that resources used within a test are cleaned up; will detect problems with tests and + * also with runtime. + */ +public abstract class AbstractResourceLeakageDetectorTestCase extends TestCase { + /** + * The leakage detector. + */ + private ResourceLeakageDetector detector; + + @Override + protected void setUp() throws Exception { + detector = ResourceLeakageDetector.newDetector(); + } + + @Override + protected void tearDown() throws Exception { + // If available check for resource leakage. At this point it is impossible to determine + // whether the test has thrown an exception. If it has then the exception thrown by this + // could hide that test failure; it largely depends on the test runner. + if (detector != null) { + detector.checkForLeaks(); + } + } +} diff --git a/luni/src/test/java/libcore/java/util/ResourceLeakageDetector.java b/luni/src/test/java/libcore/java/util/ResourceLeakageDetector.java new file mode 100644 index 0000000..954665a --- /dev/null +++ b/luni/src/test/java/libcore/java/util/ResourceLeakageDetector.java @@ -0,0 +1,90 @@ +/* + * 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 libcore.java.util; + +/** + * Detects resource leakages for resources that are protected by <code>CloseGuard</code> mechanism. + * + * <p>If multiple instances of this are active at the same time, i.e. have been created but not yet + * had their {@link #checkForLeaks()} method called then while they will report all the leakages + * detected they may report the leakages caused by the code being tested by another detector. + * + * <p>The underlying CloseGuardMonitor is loaded using reflection to ensure that this will run, + * albeit doing nothing, on the reference implementation. + */ +public class ResourceLeakageDetector { + /** The class for the CloseGuardMonitor, null if not supported. */ + private static final Class<?> CLOSE_GUARD_MONITOR_CLASS; + + static { + ClassLoader classLoader = ResourceLeakageDetector.class.getClassLoader(); + Class<?> clazz; + try { + // Make sure that the CloseGuard class exists; this ensures that this is not running + // on a RI JVM. + classLoader.loadClass("dalvik.system.CloseGuard"); + + // Load the monitor class for later instantiation. + clazz = classLoader.loadClass("dalvik.system.CloseGuardMonitor"); + + } catch (ClassNotFoundException e) { + System.err.println("Resource leakage will not be detected; " + + "this is expected in the reference implementation"); + e.printStackTrace(System.err); + + // Ignore, probably running in reference implementation. + clazz = null; + } + + CLOSE_GUARD_MONITOR_CLASS = clazz; + } + + /** + * The underlying CloseGuardMonitor that will perform the post test checks for resource + * leakage. + */ + private Runnable postTestChecker; + + /** + * Create a new detector. + * + * @return The new {@link ResourceLeakageDetector}, its {@link #checkForLeaks()} method must be + * called otherwise it will not clean up properly after itself. + */ + public static ResourceLeakageDetector newDetector() + throws Exception { + return new ResourceLeakageDetector(); + } + + private ResourceLeakageDetector() + throws Exception { + if (CLOSE_GUARD_MONITOR_CLASS != null) { + postTestChecker = (Runnable) CLOSE_GUARD_MONITOR_CLASS.newInstance(); + } + } + + /** + * Detect any leaks that have arisen since this was created. + * + * @throws Exception If any leaks were detected. + */ + public void checkForLeaks() throws Exception { + // If available check for resource leakage. + if (postTestChecker != null) { + postTestChecker.run(); + } + } +} diff --git a/luni/src/test/java/libcore/java/util/ResourceLeakageDetectorTest.java b/luni/src/test/java/libcore/java/util/ResourceLeakageDetectorTest.java new file mode 100644 index 0000000..d86c9f2 --- /dev/null +++ b/luni/src/test/java/libcore/java/util/ResourceLeakageDetectorTest.java @@ -0,0 +1,73 @@ +/* + * 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 libcore.java.util; + +import dalvik.system.CloseGuard; +import junit.framework.TestCase; + +/** + * Test for {@link ResourceLeakageDetector} + */ +public class ResourceLeakageDetectorTest extends TestCase { + /** + * This test will not work on RI as it does not support the <code>CloseGuard</code> or similar + * mechanism. + */ + public void testDetectsUnclosedCloseGuard() throws Exception { + ResourceLeakageDetector detector = ResourceLeakageDetector.newDetector(); + try { + CloseGuard closeGuard = createCloseGuard(); + closeGuard.open("open"); + } finally { + try { + System.logI("Checking for leaks"); + detector.checkForLeaks(); + fail(); + } catch (AssertionError expected) { + } + } + } + + public void testIgnoresClosedCloseGuard() throws Exception { + ResourceLeakageDetector detector = ResourceLeakageDetector.newDetector(); + try { + CloseGuard closeGuard = createCloseGuard(); + closeGuard.open("open"); + closeGuard.close(); + } finally { + detector.checkForLeaks(); + } + } + + /** + * Private method to ensure that the CloseGuard object is garbage collected. + */ + private CloseGuard createCloseGuard() { + final CloseGuard closeGuard = CloseGuard.get(); + new Object() { + @Override + protected void finalize() throws Throwable { + try { + closeGuard.warnIfOpen(); + } finally { + super.finalize(); + } + } + }; + + return closeGuard; + } +} |