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