Home | History | Annotate | Download | only in util
      1 /*
      2  * Copyright (C) 2016 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 package libcore.junit.util;
     17 
     18 import java.lang.annotation.ElementType;
     19 import java.lang.annotation.Retention;
     20 import java.lang.annotation.RetentionPolicy;
     21 import java.lang.annotation.Target;
     22 import java.lang.reflect.Method;
     23 import java.util.function.BiConsumer;
     24 import org.junit.rules.RuleChain;
     25 import org.junit.rules.TestRule;
     26 import org.junit.runner.Description;
     27 import org.junit.runners.model.Statement;
     28 
     29 /**
     30  * Provides support for testing classes that own resources (using {@code CloseGuard} mechanism)
     31  * which must not leak.
     32  *
     33  * <p><strong>This will not detect any resource leakages in OpenJDK</strong></p>
     34  *
     35  * <p>Typical usage for developers that want to ensure that their tests do not leak resources:
     36  * <pre>
     37  * public class ResourceTest {
     38  *  {@code @Rule}
     39  *   public LeakageDetectorRule leakageDetectorRule = ResourceLeakageDetector.getRule();
     40  *
     41  *  ...
     42  * }
     43  * </pre>
     44  *
     45  * <p>Developers that need to test the resource itself to ensure it is properly protected can
     46  * use {@link LeakageDetectorRule#assertUnreleasedResourceCount(Object, int)
     47  * assertUnreleasedResourceCount(Object, int)}.
     48  */
     49 public class ResourceLeakageDetector {
     50     private static final LeakageDetectorRule LEAKAGE_DETECTOR_RULE;
     51     private static final BiConsumer<Object, Integer> FINALIZER_CHECKER;
     52 
     53     static {
     54         LeakageDetectorRule leakageDetectorRule;
     55         BiConsumer<Object, Integer> finalizerChecker;
     56         try {
     57             // Make sure that the CloseGuard class exists; this ensures that this is not
     58             // running on a RI JVM.
     59             Class.forName("dalvik.system.CloseGuard");
     60 
     61             // Access the underlying support class using reflection in order to prevent any compile
     62             // time dependencies on it so as to allow this to compile on OpenJDK.
     63             Class<?> closeGuardSupportClass = Class.forName(
     64                     "libcore.dalvik.system.CloseGuardSupport");
     65             Method method = closeGuardSupportClass.getMethod("getRule");
     66             leakageDetectorRule = new LeakageDetectorRule((TestRule) method.invoke(null));
     67 
     68             finalizerChecker = getFinalizerChecker(closeGuardSupportClass);
     69 
     70         } catch (ReflectiveOperationException e) {
     71             System.err.println("Resource leakage will not be detected; "
     72                     + "this is expected in the reference implementation");
     73             e.printStackTrace(System.err);
     74 
     75             // Could not access the class for some reason so have a rule that does nothing and a
     76             // finalizer checker that checks nothing. This should ensure that tests work properly
     77             // on OpenJDK even though it does not support CloseGuard.
     78             leakageDetectorRule = new LeakageDetectorRule(RuleChain.emptyRuleChain());
     79             finalizerChecker = new BiConsumer<Object, Integer>() {
     80                 @Override
     81                 public void accept(Object o, Integer integer) {
     82                     // Do nothing.
     83                 }
     84             };
     85         }
     86 
     87         LEAKAGE_DETECTOR_RULE = leakageDetectorRule;
     88         FINALIZER_CHECKER = finalizerChecker;
     89     }
     90 
     91     @SuppressWarnings("unchecked")
     92     private static BiConsumer<Object, Integer> getFinalizerChecker(Class<?> closeGuardSupportClass)
     93             throws ReflectiveOperationException {
     94         Method method = closeGuardSupportClass.getMethod("getFinalizerChecker");
     95         return (BiConsumer<Object, Integer>) method.invoke(null);
     96     }
     97 
     98     /**
     99      * @return the {@link LeakageDetectorRule}
    100      */
    101     public static LeakageDetectorRule getRule() {
    102        return LEAKAGE_DETECTOR_RULE;
    103     }
    104 
    105     /**
    106      * A {@link TestRule} that will fail a test if it detects any resources that were allocated
    107      * during the test but were not released.
    108      *
    109      * <p>This only tracks resources that were allocated on the test thread, although it does not
    110      * care what thread they were released on. This avoids flaky false positives where a background
    111      * thread allocates a resource during a test but releases it after the test.
    112      *
    113      * <p>It is still possible to have a false positive in the case where the test causes a caching
    114      * mechanism to open a resource and hold it open past the end of the test. In that case if there
    115      * is no way to clear the cached data then it should be relatively simple to move the code that
    116      * invokes the caching mechanism to outside the scope of this rule. i.e.
    117      *
    118      * <pre>
    119      *    {@code @Rule}
    120      *     public final TestRule ruleChain = org.junit.rules.RuleChain
    121      *         .outerRule(new ...invoke caching mechanism...)
    122      *         .around(ResourceLeakageDetector.getRule());
    123      * </pre>
    124      */
    125     public static class LeakageDetectorRule implements TestRule {
    126 
    127         private final TestRule leakageDetectorRule;
    128         private boolean leakageDetectionEnabledForTest;
    129 
    130         private LeakageDetectorRule(TestRule leakageDetectorRule) {
    131             this.leakageDetectorRule = leakageDetectorRule;
    132         }
    133 
    134         @Override
    135         public Statement apply(Statement base, Description description) {
    136             // Make the resource leakage detector rule optional based on the presence of an
    137             // annotation.
    138             if (description.getAnnotation(DisableResourceLeakageDetection.class) != null) {
    139                 leakageDetectionEnabledForTest = false;
    140                 return base;
    141             } else {
    142                 leakageDetectionEnabledForTest = true;
    143                 return leakageDetectorRule.apply(base, description);
    144             }
    145         }
    146 
    147         /**
    148          * Ensure that when the supplied object is finalized that it detects the expected number of
    149          * unreleased resources.
    150          *
    151          * <p>This helps ensure that classes which own resources protected using {@code CloseGuard}
    152          * support leakage detection.
    153          *
    154          * <p>This must only be called as part of the currently running test and the test must not
    155          * be annotated with {@link DisableResourceLeakageDetection} as that will disable leakage
    156          * detection. Attempting to use it with leakage detection disabled by the annotation will
    157          * result in a test failure.
    158          *
    159          * <p>Use as follows, 'open' and 'close' refer to the methods in {@code CloseGuard}:
    160          * <pre>
    161          * public class ResourceTest {
    162          *  {@code @Rule}
    163          *   public LeakageDetectorRule leakageDetectorRule = ResourceLeakageDetector.getRule();
    164          *
    165          *  {@code @Test}
    166          *   public void testAutoCloseableResourceIsProtected() {
    167          *     try (AutoCloseable object = ...open a protected resource...) {
    168          *       leakageDetectorRule.assertUnreleasedResourceCount(object, 1);
    169          *     }
    170          *   }
    171          *
    172          *  {@code @Test}
    173          *   public void testResourceIsProtected() {
    174          *     NonAutoCloseable object = ...open a protected resource...;
    175          *     leakageDetectorRule.assertUnreleasedResourceCount(object, 1);
    176          *     object.release();
    177          *   }
    178          * }
    179          * </pre>
    180          *
    181          * <p>There are two test method templates, the one to use depends on whether the resource is
    182          * {@link AutoCloseable} or not. Each method tests the following:</p>
    183          * <ul>
    184          * <li>The {@code @Rule} will ensure that the test method does not leak any resources. That
    185          * will make sure that if it actually is protected by {@code CloseGuard} that it correctly
    186          * closes it. It does not actually ensure that it is protected.
    187          * <li>The call to this method will ensure that the resource is actually protected by
    188          * {@code CloseGuard}.
    189          * </ul>
    190          *
    191          * <p>The above tests will work on the reference implementation as this method does nothing
    192          * when {@code CloseGuard} is not supported.
    193          *
    194          * @param owner the object that owns the resource and uses {@code CloseGuard} object to
    195          *         detect when the resource is not released.
    196          * @param expectedCount the expected number of unreleased resources, i.e. the number of
    197          *         {@code CloseGuard} objects owned by the resource, and on which it calls
    198          *         {@code CloseGuard.warnIfOpen()} in its {@link #finalize()} method; usually 1.
    199          */
    200         public void assertUnreleasedResourceCount(Object owner, int expectedCount) {
    201             if (leakageDetectionEnabledForTest) {
    202                 FINALIZER_CHECKER.accept(owner, expectedCount);
    203             } else {
    204                 throw new IllegalStateException(
    205                         "Does not work when leakage detection has been disabled; remove the "
    206                                 + "@DisableResourceLeakageDetection from the test method");
    207             }
    208         }
    209     }
    210 
    211     /**
    212      * An annotation that indicates that the test should not be run with resource leakage detection
    213      * enabled.
    214      */
    215     @Retention(RetentionPolicy.RUNTIME)
    216     @Target(ElementType.METHOD)
    217     public @interface DisableResourceLeakageDetection {
    218 
    219         /**
    220          * The explanation as to why resource leakage detection is disabled for this test.
    221          */
    222         String why();
    223 
    224         /**
    225          * The bug reference to the bug that was opened to fix the issue.
    226          */
    227         String bug();
    228     }
    229 }
    230