blob: 28434cf41a5aa00bb9dabf74fcd59bbc6bac88ec [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.server.wm;
import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.function.Consumer;
/** A helper utility to track or manage object after a test method is done. */
public class ObjectTracker {
private static final boolean DEBUG = "eng".equals(Build.TYPE);
private LinkedList<AutoCloseable> mAutoCloseables;
private LinkedList<ConsumableEntry> mConsumables;
/** The interface used for tracking whether an object is consumed. */
public interface Consumable {
boolean isConsumed();
}
private static class ConsumableEntry {
@NonNull
final Consumable mConsumable;
@Nullable
final Throwable mStackTrace;
ConsumableEntry(Consumable consumable, Throwable stackTrace) {
mConsumable = consumable;
mStackTrace = stackTrace;
}
}
/**
* If a {@link AutoCloseable} should be closed at the end of test, or it is not important when
* to close, then we can use this method to manage the {@link AutoCloseable}. Then the extra
* indents of try-with-resource can be eliminated. If the caller want to close the object
* manually, it should use {@link ObjectTracker#close} to cancel the management.
*/
public <T extends AutoCloseable> T manage(@NonNull T autoCloseable) {
if (mAutoCloseables == null) {
mAutoCloseables = new LinkedList<>();
}
mAutoCloseables.add(autoCloseable);
return autoCloseable;
}
/**
* Closes the {@link AutoCloseable} and remove from the managed list so it won't be closed twice
* when leaving a test method.
*/
public void close(@NonNull AutoCloseable autoCloseable) {
if (mAutoCloseables == null) {
return;
}
mAutoCloseables.remove(autoCloseable);
try {
autoCloseable.close();
} catch (Throwable e) {
throw new AssertionError("Failed to close " + autoCloseable, e);
}
}
/** Tracks the {@link Consumable} to avoid misusing of the object that should do something. */
public void track(@NonNull Consumable consumable) {
if (mConsumables == null) {
mConsumables = new LinkedList<>();
}
mConsumables.add(new ConsumableEntry(consumable,
DEBUG ? new Throwable().fillInStackTrace() : null));
}
/**
* Cleans up the managed object and make sure all tracked {@link Consumable} are consumed.
* This method must be called after each test method.
*/
public void tearDown(@NonNull Consumer<Throwable> errorConsumer) {
ArrayList<Throwable> errors = null;
if (mAutoCloseables != null) {
while (!mAutoCloseables.isEmpty()) {
final AutoCloseable autoCloseable = mAutoCloseables.removeLast();
try {
autoCloseable.close();
} catch (Throwable t) {
StateLogger.logE("Failed to close " + autoCloseable, t);
if (errors == null) {
errors = new ArrayList<>();
}
errors.add(t);
}
}
}
if (mConsumables != null) {
while (!mConsumables.isEmpty()) {
final ConsumableEntry entry = mConsumables.removeFirst();
if (!entry.mConsumable.isConsumed()) {
StateLogger.logE("Found unconsumed object " + entry.mConsumable
+ " that was created from:", entry.mStackTrace);
final Throwable t =
new IllegalStateException("Unconsumed object " + entry.mConsumable);
if (entry.mStackTrace != null) {
t.initCause(entry.mStackTrace);
}
if (errors == null) {
errors = new ArrayList<>();
}
errors.add(t);
}
}
}
if (errors != null) {
errors.forEach(errorConsumer);
}
}
}