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 */
16package libcore.junit.util;
17
18import java.lang.annotation.ElementType;
19import java.lang.annotation.Retention;
20import java.lang.annotation.RetentionPolicy;
21import java.lang.annotation.Target;
22import java.lang.reflect.Method;
23import java.util.function.BiConsumer;
24import org.junit.rules.RuleChain;
25import org.junit.rules.TestRule;
26import org.junit.runner.Description;
27import 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 */
49public 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