1 /*
2  * Copyright (C) 2019 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 
17 package android.server.wm;
18 
19 import android.os.Build;
20 
21 import androidx.annotation.NonNull;
22 import androidx.annotation.Nullable;
23 
24 import java.util.ArrayList;
25 import java.util.LinkedList;
26 import java.util.function.Consumer;
27 
28 /** A helper utility to track or manage object after a test method is done. */
29 public class ObjectTracker {
30     private static final boolean DEBUG = "eng".equals(Build.TYPE);
31     private LinkedList<AutoCloseable> mAutoCloseables;
32     private LinkedList<ConsumableEntry> mConsumables;
33 
34     /** The interface used for tracking whether an object is consumed. */
35     public interface Consumable {
isConsumed()36         boolean isConsumed();
37     }
38 
39     private static class ConsumableEntry {
40         @NonNull
41         final Consumable mConsumable;
42         @Nullable
43         final Throwable mStackTrace;
44 
ConsumableEntry(Consumable consumable, Throwable stackTrace)45         ConsumableEntry(Consumable consumable, Throwable stackTrace) {
46             mConsumable = consumable;
47             mStackTrace = stackTrace;
48         }
49     }
50 
51     /**
52      * If a {@link AutoCloseable} should be closed at the end of test, or it is not important when
53      * to close, then we can use this method to manage the {@link AutoCloseable}. Then the extra
54      * indents of try-with-resource can be eliminated. If the caller want to close the object
55      * manually, it should use {@link ObjectTracker#close} to cancel the management.
56      */
manage(@onNull T autoCloseable)57     public <T extends AutoCloseable> T manage(@NonNull T autoCloseable) {
58         if (mAutoCloseables == null) {
59             mAutoCloseables = new LinkedList<>();
60         }
61         mAutoCloseables.add(autoCloseable);
62         return autoCloseable;
63     }
64 
65     /**
66      * Closes the {@link AutoCloseable} and remove from the managed list so it won't be closed twice
67      * when leaving a test method.
68      */
close(@onNull AutoCloseable autoCloseable)69     public void close(@NonNull AutoCloseable autoCloseable) {
70         if (mAutoCloseables == null) {
71             return;
72         }
73         mAutoCloseables.remove(autoCloseable);
74         try {
75             autoCloseable.close();
76         } catch (Throwable e) {
77             throw new AssertionError("Failed to close " + autoCloseable, e);
78         }
79     }
80 
81     /** Tracks the {@link Consumable} to avoid misusing of the object that should do something. */
track(@onNull Consumable consumable)82     public void track(@NonNull Consumable consumable) {
83         if (mConsumables == null) {
84             mConsumables = new LinkedList<>();
85         }
86         mConsumables.add(new ConsumableEntry(consumable,
87                 DEBUG ? new Throwable().fillInStackTrace() : null));
88     }
89 
90     /**
91      * Cleans up the managed object and make sure all tracked {@link Consumable} are consumed.
92      * This method must be called after each test method.
93      */
tearDown(@onNull Consumer<Throwable> errorConsumer)94     public void tearDown(@NonNull Consumer<Throwable> errorConsumer) {
95         ArrayList<Throwable> errors = null;
96         if (mAutoCloseables != null) {
97             while (!mAutoCloseables.isEmpty()) {
98                 final AutoCloseable autoCloseable = mAutoCloseables.removeLast();
99                 try {
100                     autoCloseable.close();
101                 } catch (Throwable t) {
102                     StateLogger.logE("Failed to close " + autoCloseable, t);
103                     if (errors == null) {
104                         errors = new ArrayList<>();
105                     }
106                     errors.add(t);
107                 }
108             }
109         }
110 
111         if (mConsumables != null) {
112             while (!mConsumables.isEmpty()) {
113                 final ConsumableEntry entry = mConsumables.removeFirst();
114                 if (!entry.mConsumable.isConsumed()) {
115                     StateLogger.logE("Found unconsumed object " + entry.mConsumable
116                             + " that was created from:", entry.mStackTrace);
117                     final Throwable t =
118                             new IllegalStateException("Unconsumed object " + entry.mConsumable);
119                     if (entry.mStackTrace != null) {
120                         t.initCause(entry.mStackTrace);
121                     }
122                     if (errors == null) {
123                         errors = new ArrayList<>();
124                     }
125                     errors.add(t);
126                 }
127             }
128         }
129 
130         if (errors != null) {
131             errors.forEach(errorConsumer);
132         }
133     }
134 }
135