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 * @Rule public LeakCheck mLeakChecker = new LeakCheck(); 420c408008f1061f9421872e4407a539688b21b79eJason Monk * 430c408008f1061f9421872e4407a539688b21b79eJason Monk * @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