1340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk/*
2340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * Copyright (C) 2017 The Android Open Source Project
3340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk *
4340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * except in compliance with the License. You may obtain a copy of the License at
6340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk *
7340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk *      http://www.apache.org/licenses/LICENSE-2.0
8340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk *
9340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * Unless required by applicable law or agreed to in writing, software distributed under the
10340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * KIND, either express or implied. See the License for the specific language governing
12340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk * permissions and limitations under the License.
13340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk */
14340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
15340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkpackage android.testing;
16340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
170c408008f1061f9421872e4407a539688b21b79eJason Monkimport android.content.Context;
18340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkimport android.util.ArrayMap;
19340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkimport android.util.Log;
20340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
21340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkimport org.junit.Assert;
22340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkimport org.junit.rules.TestWatcher;
23340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkimport org.junit.runner.Description;
24340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
25340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkimport java.io.PrintWriter;
26340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkimport java.io.StringWriter;
27340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkimport java.util.ArrayList;
28340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkimport java.util.HashMap;
29340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkimport java.util.List;
30340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkimport java.util.Map;
31340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
320c408008f1061f9421872e4407a539688b21b79eJason Monk/**
330c408008f1061f9421872e4407a539688b21b79eJason Monk * Utility for dealing with the facts of Lifecycle. Creates trackers to check that for every
340c408008f1061f9421872e4407a539688b21b79eJason Monk * call to registerX, addX, bindX, a corresponding call to unregisterX, removeX, and unbindX
350c408008f1061f9421872e4407a539688b21b79eJason Monk * is performed. This should be applied to a test as a {@link org.junit.rules.TestRule}
360c408008f1061f9421872e4407a539688b21b79eJason Monk * and will only check for leaks on successful tests.
370c408008f1061f9421872e4407a539688b21b79eJason Monk * <p>
380c408008f1061f9421872e4407a539688b21b79eJason Monk * Example that will catch an allocation and fail:
390c408008f1061f9421872e4407a539688b21b79eJason Monk * <pre class="prettyprint">
400c408008f1061f9421872e4407a539688b21b79eJason Monk * public class LeakCheckTest {
410c408008f1061f9421872e4407a539688b21b79eJason Monk *    &#064;Rule public LeakCheck mLeakChecker = new LeakCheck();
420c408008f1061f9421872e4407a539688b21b79eJason Monk *
430c408008f1061f9421872e4407a539688b21b79eJason Monk *    &#064;Test
440c408008f1061f9421872e4407a539688b21b79eJason Monk *    public void testLeak() {
450c408008f1061f9421872e4407a539688b21b79eJason Monk *        Context context = new ContextWrapper(...) {
460c408008f1061f9421872e4407a539688b21b79eJason Monk *            public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
470c408008f1061f9421872e4407a539688b21b79eJason Monk *                mLeakChecker.getTracker("receivers").addAllocation(new Throwable());
480c408008f1061f9421872e4407a539688b21b79eJason Monk *            }
490c408008f1061f9421872e4407a539688b21b79eJason Monk *            public void unregisterReceiver(BroadcastReceiver receiver) {
500c408008f1061f9421872e4407a539688b21b79eJason Monk *                mLeakChecker.getTracker("receivers").clearAllocations();
510c408008f1061f9421872e4407a539688b21b79eJason Monk *            }
520c408008f1061f9421872e4407a539688b21b79eJason Monk *        };
530c408008f1061f9421872e4407a539688b21b79eJason Monk *        context.registerReceiver(...);
540c408008f1061f9421872e4407a539688b21b79eJason Monk *    }
550c408008f1061f9421872e4407a539688b21b79eJason Monk *  }
560c408008f1061f9421872e4407a539688b21b79eJason Monk * </pre>
570c408008f1061f9421872e4407a539688b21b79eJason Monk *
580c408008f1061f9421872e4407a539688b21b79eJason Monk * Note: {@link TestableContext} supports leak tracking when using
590c408008f1061f9421872e4407a539688b21b79eJason Monk * {@link TestableContext#TestableContext(Context, LeakCheck)}.
600c408008f1061f9421872e4407a539688b21b79eJason Monk */
61340b0e5216b4fcc435e0459b1ca46155a572100dJason Monkpublic class LeakCheck extends TestWatcher {
62340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
63340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    private final Map<String, Tracker> mTrackers = new HashMap<>();
64340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
65340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    public LeakCheck() {
66340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    }
67340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
68340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    @Override
69340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    protected void succeeded(Description description) {
70340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        verify();
71340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    }
72340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
730c408008f1061f9421872e4407a539688b21b79eJason Monk    /**
740c408008f1061f9421872e4407a539688b21b79eJason Monk     * Acquire a {@link Tracker}. Gets a tracker for the specified tag, creating one if necessary.
750c408008f1061f9421872e4407a539688b21b79eJason Monk     * There should be one tracker for each pair of add/remove callbacks (e.g. one tracker for
760c408008f1061f9421872e4407a539688b21b79eJason Monk     * registerReceiver/unregisterReceiver).
770c408008f1061f9421872e4407a539688b21b79eJason Monk     *
780c408008f1061f9421872e4407a539688b21b79eJason Monk     * @param tag Unique tag to use for this set of allocation tracking.
790c408008f1061f9421872e4407a539688b21b79eJason Monk     */
80340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    public Tracker getTracker(String tag) {
81340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        Tracker t = mTrackers.get(tag);
82340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        if (t == null) {
83340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            t = new Tracker();
84340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            mTrackers.put(tag, t);
85340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        }
86340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        return t;
87340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    }
88340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
890c408008f1061f9421872e4407a539688b21b79eJason Monk    private void verify() {
90340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        mTrackers.values().forEach(Tracker::verify);
91340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    }
92340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
930c408008f1061f9421872e4407a539688b21b79eJason Monk    /**
940c408008f1061f9421872e4407a539688b21b79eJason Monk     * Holds allocations associated with a specific callback (such as a BroadcastReceiver).
950c408008f1061f9421872e4407a539688b21b79eJason Monk     */
96340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    public static class LeakInfo {
97340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        private static final String TAG = "LeakInfo";
98340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        private List<Throwable> mThrowables = new ArrayList<>();
99340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
100340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        LeakInfo() {
101340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        }
102340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
1030c408008f1061f9421872e4407a539688b21b79eJason Monk        /**
1040c408008f1061f9421872e4407a539688b21b79eJason Monk         * Should be called once for each callback/listener added. addAllocation may be
1050c408008f1061f9421872e4407a539688b21b79eJason Monk         * called several times, but it only takes one clearAllocations call to remove all
1060c408008f1061f9421872e4407a539688b21b79eJason Monk         * of them.
1070c408008f1061f9421872e4407a539688b21b79eJason Monk         */
108340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        public void addAllocation(Throwable t) {
109340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            // TODO: Drop off the first element in the stack trace here to have a cleaner stack.
110340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            mThrowables.add(t);
111340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        }
112340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
1130c408008f1061f9421872e4407a539688b21b79eJason Monk        /**
1140c408008f1061f9421872e4407a539688b21b79eJason Monk         * Should be called when the callback/listener has been removed. One call to
1150c408008f1061f9421872e4407a539688b21b79eJason Monk         * clearAllocations will counteract any number of calls to addAllocation.
1160c408008f1061f9421872e4407a539688b21b79eJason Monk         */
117340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        public void clearAllocations() {
118340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            mThrowables.clear();
119340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        }
120340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
121340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        void verify() {
122340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            if (mThrowables.size() == 0) return;
123340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            Log.e(TAG, "Listener or binding not properly released");
124340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            for (Throwable t : mThrowables) {
125340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk                Log.e(TAG, "Allocation found", t);
126340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            }
127340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            StringWriter writer = new StringWriter();
128340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            mThrowables.get(0).printStackTrace(new PrintWriter(writer));
129340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            Assert.fail("Listener or binding not properly released\n"
130340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk                    + writer.toString());
131340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        }
132340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    }
133340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
1340c408008f1061f9421872e4407a539688b21b79eJason Monk    /**
1350c408008f1061f9421872e4407a539688b21b79eJason Monk     * Tracks allocations related to a specific tag or method(s).
1360c408008f1061f9421872e4407a539688b21b79eJason Monk     * @see #getTracker(String)
1370c408008f1061f9421872e4407a539688b21b79eJason Monk     */
138340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    public static class Tracker {
139340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        private Map<Object, LeakInfo> mObjects = new ArrayMap<>();
140340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
1410c408008f1061f9421872e4407a539688b21b79eJason Monk        private Tracker() {
1420c408008f1061f9421872e4407a539688b21b79eJason Monk        }
1430c408008f1061f9421872e4407a539688b21b79eJason Monk
144340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        public LeakInfo getLeakInfo(Object object) {
145340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            LeakInfo leakInfo = mObjects.get(object);
146340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            if (leakInfo == null) {
147340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk                leakInfo = new LeakInfo();
148340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk                mObjects.put(object, leakInfo);
149340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            }
150340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            return leakInfo;
151340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        }
152340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk
153340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        void verify() {
154340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk            mObjects.values().forEach(LeakInfo::verify);
155340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk        }
156340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk    }
157340b0e5216b4fcc435e0459b1ca46155a572100dJason Monk}
158