/* * 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 libcore.junit.util; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.function.BiConsumer; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runner.Description; import org.junit.runners.model.Statement; /** * Provides support for testing classes that own resources (using {@code CloseGuard} mechanism) * which must not leak. * *

This will not detect any resource leakages in OpenJDK

* *

Typical usage for developers that want to ensure that their tests do not leak resources: *

 * public class ResourceTest {
 *  {@code @Rule}
 *   public LeakageDetectorRule leakageDetectorRule = ResourceLeakageDetector.getRule();
 *
 *  ...
 * }
 * 
* *

Developers that need to test the resource itself to ensure it is properly protected can * use {@link LeakageDetectorRule#assertUnreleasedResourceCount(Object, int) * assertUnreleasedResourceCount(Object, int)}. */ public class ResourceLeakageDetector { private static final LeakageDetectorRule LEAKAGE_DETECTOR_RULE; private static final BiConsumer FINALIZER_CHECKER; static { LeakageDetectorRule leakageDetectorRule; BiConsumer finalizerChecker; try { // Make sure that the CloseGuard class exists; this ensures that this is not // running on a RI JVM. Class.forName("dalvik.system.CloseGuard"); // Access the underlying support class using reflection in order to prevent any compile // time dependencies on it so as to allow this to compile on OpenJDK. Class closeGuardSupportClass = Class.forName( "libcore.dalvik.system.CloseGuardSupport"); Method method = closeGuardSupportClass.getMethod("getRule"); leakageDetectorRule = new LeakageDetectorRule((TestRule) method.invoke(null)); finalizerChecker = getFinalizerChecker(closeGuardSupportClass); } catch (ReflectiveOperationException e) { System.err.println("Resource leakage will not be detected; " + "this is expected in the reference implementation"); e.printStackTrace(System.err); // Could not access the class for some reason so have a rule that does nothing and a // finalizer checker that checks nothing. This should ensure that tests work properly // on OpenJDK even though it does not support CloseGuard. leakageDetectorRule = new LeakageDetectorRule(RuleChain.emptyRuleChain()); finalizerChecker = new BiConsumer() { @Override public void accept(Object o, Integer integer) { // Do nothing. } }; } LEAKAGE_DETECTOR_RULE = leakageDetectorRule; FINALIZER_CHECKER = finalizerChecker; } @SuppressWarnings("unchecked") private static BiConsumer getFinalizerChecker(Class closeGuardSupportClass) throws ReflectiveOperationException { Method method = closeGuardSupportClass.getMethod("getFinalizerChecker"); return (BiConsumer) method.invoke(null); } /** * @return the {@link LeakageDetectorRule} */ public static LeakageDetectorRule getRule() { return LEAKAGE_DETECTOR_RULE; } /** * A {@link TestRule} that will fail a test if it detects any resources that were allocated * during the test but were not released. * *

This only tracks resources that were allocated on the test thread, although it does not * care what thread they were released on. This avoids flaky false positives where a background * thread allocates a resource during a test but releases it after the test. * *

It is still possible to have a false positive in the case where the test causes a caching * mechanism to open a resource and hold it open past the end of the test. In that case if there * is no way to clear the cached data then it should be relatively simple to move the code that * invokes the caching mechanism to outside the scope of this rule. i.e. * *

     *    {@code @Rule}
     *     public final TestRule ruleChain = org.junit.rules.RuleChain
     *         .outerRule(new ...invoke caching mechanism...)
     *         .around(ResourceLeakageDetector.getRule());
     * 
*/ public static class LeakageDetectorRule implements TestRule { private final TestRule leakageDetectorRule; private boolean leakageDetectionEnabledForTest; private LeakageDetectorRule(TestRule leakageDetectorRule) { this.leakageDetectorRule = leakageDetectorRule; } @Override public Statement apply(Statement base, Description description) { // Make the resource leakage detector rule optional based on the presence of an // annotation. if (description.getAnnotation(DisableResourceLeakageDetection.class) != null) { leakageDetectionEnabledForTest = false; return base; } else { leakageDetectionEnabledForTest = true; return leakageDetectorRule.apply(base, description); } } /** * Ensure that when the supplied object is finalized that it detects the expected number of * unreleased resources. * *

This helps ensure that classes which own resources protected using {@code CloseGuard} * support leakage detection. * *

This must only be called as part of the currently running test and the test must not * be annotated with {@link DisableResourceLeakageDetection} as that will disable leakage * detection. Attempting to use it with leakage detection disabled by the annotation will * result in a test failure. * *

Use as follows, 'open' and 'close' refer to the methods in {@code CloseGuard}: *

         * public class ResourceTest {
         *  {@code @Rule}
         *   public LeakageDetectorRule leakageDetectorRule = ResourceLeakageDetector.getRule();
         *
         *  {@code @Test}
         *   public void testAutoCloseableResourceIsProtected() {
         *     try (AutoCloseable object = ...open a protected resource...) {
         *       leakageDetectorRule.assertUnreleasedResourceCount(object, 1);
         *     }
         *   }
         *
         *  {@code @Test}
         *   public void testResourceIsProtected() {
         *     NonAutoCloseable object = ...open a protected resource...;
         *     leakageDetectorRule.assertUnreleasedResourceCount(object, 1);
         *     object.release();
         *   }
         * }
         * 
* *

There are two test method templates, the one to use depends on whether the resource is * {@link AutoCloseable} or not. Each method tests the following:

* * *

The above tests will work on the reference implementation as this method does nothing * when {@code CloseGuard} is not supported. * * @param owner the object that owns the resource and uses {@code CloseGuard} object to * detect when the resource is not released. * @param expectedCount the expected number of unreleased resources, i.e. the number of * {@code CloseGuard} objects owned by the resource, and on which it calls * {@code CloseGuard.warnIfOpen()} in its {@link #finalize()} method; usually 1. */ public void assertUnreleasedResourceCount(Object owner, int expectedCount) { if (leakageDetectionEnabledForTest) { FINALIZER_CHECKER.accept(owner, expectedCount); } else { throw new IllegalStateException( "Does not work when leakage detection has been disabled; remove the " + "@DisableResourceLeakageDetection from the test method"); } } } /** * An annotation that indicates that the test should not be run with resource leakage detection * enabled. */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface DisableResourceLeakageDetection { /** * The explanation as to why resource leakage detection is disabled for this test. */ String why(); /** * The bug reference to the bug that was opened to fix the issue. */ String bug(); } }