• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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")
getFinalizerChecker(Class<?> closeGuardSupportClass)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      */
getRule()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 
LeakageDetectorRule(TestRule leakageDetectorRule)130         private LeakageDetectorRule(TestRule leakageDetectorRule) {
131             this.leakageDetectorRule = leakageDetectorRule;
132         }
133 
134         @Override
apply(Statement base, Description description)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          */
assertUnreleasedResourceCount(Object owner, int expectedCount)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          */
why()222         String why();
223 
224         /**
225          * The bug reference to the bug that was opened to fix the issue.
226          */
bug()227         String bug();
228     }
229 }
230