blob: 3b2aa8c2d495d6dfdf2c566ad750e21867d836bc [file] [log] [blame]
/*
* Copyright (C) 2020 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.cts.statsdatom.lib;
import static com.google.common.truth.Truth.assertWithMessage;
import com.android.os.AtomsProto;
import com.android.os.AtomsProto.AppBreadcrumbReported;
import com.android.os.StatsLog;
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.log.LogUtil;
import com.android.utils.SparseIntArray;
import com.google.common.collect.Range;
import java.util.List;
import java.util.Set;
import java.util.function.Function;
/**
* Contains miscellaneous helper functions that are used in statsd atom tests
*/
public final class AtomTestUtils {
public static final int WAIT_TIME_SHORT = 500;
public static final int WAIT_TIME_LONG = 1000;
public static final long NS_PER_SEC = (long) 1E+9;
/**
* Sends an AppBreadcrumbReported atom to statsd. For GaugeMetrics that are added using
* ConfigUtils, pulls are triggered when statsd receives an AppBreadcrumbReported atom, so
* calling this function is necessary for gauge data to be acquired.
*
* @param device test device can be retrieved using getDevice()
*/
public static void sendAppBreadcrumbReportedAtom(ITestDevice device)
throws DeviceNotAvailableException {
String cmd = String.format("cmd stats log-app-breadcrumb %d %d", /*label=*/1,
AppBreadcrumbReported.State.START.ordinal());
device.executeShellCommand(cmd);
}
/**
* Asserts that each set of states in {@code stateSets} occurs in {@code data} without assuming
* the order of occurrence.
*
* @param stateSets A list of set of states, where each set represents an equivalent
* state of the device for the purpose of CTS.
* @param data list of EventMetricData from statsd, produced by
* getReportMetricListData()
* @param getStateFromAtom expression that takes in an Atom and returns the state it contains
*/
public static void assertStatesOccurred(List<Set<Integer>> stateSets,
List<StatsLog.EventMetricData> data,
Function<AtomsProto.Atom, Integer> getStateFromAtom) {
// Sometimes, there are more events than there are states.
// Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
assertWithMessage("Number of result states").that(data.size()).isAtLeast(stateSets.size());
final SparseIntArray dataStateCount = new SparseIntArray();
for (StatsLog.EventMetricData emd : data) {
final int state = getStateFromAtom.apply(emd.getAtom());
dataStateCount.put(state, dataStateCount.get(state, 0) + 1);
}
for (Set<Integer> states : stateSets) {
for (int state : states) {
final int count = dataStateCount.get(state);
assertWithMessage("Remaining count of result state (%s)", state)
.that(count).isGreaterThan(0);
dataStateCount.put(state, count - 1);
}
}
}
/**
* Asserts that each set of states in stateSets occurs at least once in data.
* Asserts that the states in data occur in the same order as the sets in stateSets.
*
* @param stateSets A list of set of states, where each set represents an equivalent
* state of the device for the purpose of CTS.
* @param data list of EventMetricData from statsd, produced by
* getReportMetricListData()
* @param wait expected duration (in ms) between state changes; asserts that the
* actual wait
* time was wait/2 <= actual_wait <= 5*wait. Use 0 to ignore this
* assertion.
* @param getStateFromAtom expression that takes in an Atom and returns the state it contains
*/
public static void assertStatesOccurredInOrder(List<Set<Integer>> stateSets,
List<StatsLog.EventMetricData> data,
int wait, Function<AtomsProto.Atom, Integer> getStateFromAtom) {
// Sometimes, there are more events than there are states.
// Eg: When the screen turns off, it may go into OFF and then DOZE immediately.
assertWithMessage("Number of result states").that(data.size()).isAtLeast(stateSets.size());
int stateSetIndex = 0; // Tracks which state set we expect the data to be in.
for (int dataIndex = 0; dataIndex < data.size(); dataIndex++) {
AtomsProto.Atom atom = data.get(dataIndex).getAtom();
int state = getStateFromAtom.apply(atom);
// If state is in the current state set, we do not assert anything.
// If it is not, we expect to have transitioned to the next state set.
if (stateSets.get(stateSetIndex).contains(state)) {
// No need to assert anything. Just log it.
LogUtil.CLog.i("The following atom at dataIndex=" + dataIndex + " is "
+ "in stateSetIndex " + stateSetIndex + ":\n"
+ data.get(dataIndex).getAtom().toString());
} else {
stateSetIndex += 1;
LogUtil.CLog.i("Assert that the following atom at dataIndex=" + dataIndex + " is"
+ " in stateSetIndex " + stateSetIndex + ":\n"
+ data.get(dataIndex).getAtom().toString());
assertWithMessage("Missed first state").that(dataIndex).isNotEqualTo(0);
assertWithMessage("Too many states").that(stateSetIndex)
.isLessThan(stateSets.size());
assertWithMessage(String.format("Is in wrong state (%d)", state))
.that(stateSets.get(stateSetIndex)).contains(state);
if (wait > 0) {
assertTimeDiffBetween(data.get(dataIndex - 1), data.get(dataIndex),
wait / 2, wait * 5);
}
}
}
assertWithMessage("Too few states").that(stateSetIndex).isEqualTo(stateSets.size() - 1);
}
/**
* Asserts that the two events are within the specified range of each other.
*
* @param d0 the event that should occur first
* @param d1 the event that should occur second
* @param minDiffMs d0 should precede d1 by at least this amount
* @param maxDiffMs d0 should precede d1 by at most this amount
*/
public static void assertTimeDiffBetween(
StatsLog.EventMetricData d0, StatsLog.EventMetricData d1,
int minDiffMs, int maxDiffMs) {
long diffMs = (d1.getElapsedTimestampNanos() - d0.getElapsedTimestampNanos()) / 1_000_000;
assertWithMessage("Illegal time difference")
.that(diffMs).isIn(Range.closed((long) minDiffMs, (long) maxDiffMs));
}
// Checks that a timestamp has been truncated to be a multiple of 5 min
public static void assertTimestampIsTruncated(long timestampNs) {
long fiveMinutesInNs = NS_PER_SEC * 5 * 60;
assertWithMessage("Timestamp is not truncated")
.that(timestampNs % fiveMinutesInNs).isEqualTo(0);
}
/**
* Removes all elements from data prior to the first occurrence of an element of state. After
* this method is called, the first element of data (if non-empty) is guaranteed to be an
* element in state.
*
* @param getStateFromAtom expression that takes in an Atom and returns the state it contains
*/
public static void popUntilFind(List<StatsLog.EventMetricData> data, Set<Integer> state,
Function<AtomsProto.Atom, Integer> getStateFromAtom) {
int firstStateIdx;
for (firstStateIdx = 0; firstStateIdx < data.size(); firstStateIdx++) {
AtomsProto.Atom atom = data.get(firstStateIdx).getAtom();
if (state.contains(getStateFromAtom.apply(atom))) {
break;
}
}
if (firstStateIdx == 0) {
// First first element already is in state, so there's nothing to do.
return;
}
data.subList(0, firstStateIdx).clear();
}
/**
* Removes all elements from data after the last occurrence of an element of state. After this
* method is called, the last element of data (if non-empty) is guaranteed to be an element in
* state.
*
* @param getStateFromAtom expression that takes in an Atom and returns the state it contains
*/
public static void popUntilFindFromEnd(List<StatsLog.EventMetricData> data, Set<Integer> state,
Function<AtomsProto.Atom, Integer> getStateFromAtom) {
int lastStateIdx;
for (lastStateIdx = data.size() - 1; lastStateIdx >= 0; lastStateIdx--) {
AtomsProto.Atom atom = data.get(lastStateIdx).getAtom();
if (state.contains(getStateFromAtom.apply(atom))) {
break;
}
}
if (lastStateIdx == data.size() - 1) {
// Last element already is in state, so there's nothing to do.
return;
}
data.subList(lastStateIdx + 1, data.size()).clear();
}
private AtomTestUtils() {}
}