Merge changes from topic "flicker-move"
am: 7943776f9a
Change-Id: Ia8a8f429c52afef9c115f373f36217f63ae2c094
This commit is contained in:
@@ -23,6 +23,8 @@
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<!-- Capture screen contents -->
|
||||
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
|
||||
<!-- Enable / Disable tracing !-->
|
||||
<uses-permission android:name="android.permission.DUMP" />
|
||||
<!-- Run layers trace -->
|
||||
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
|
||||
<application>
|
||||
@@ -33,4 +35,4 @@
|
||||
android:targetPackage="com.android.server.wm.flicker"
|
||||
android:label="WindowManager Flicker Tests">
|
||||
</instrumentation>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -25,5 +25,6 @@
|
||||
<metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
|
||||
<option name="directory-keys" value="/sdcard/flicker" />
|
||||
<option name="collect-on-run-ended-only" value="true" />
|
||||
<option name="clean-up" value="false" />
|
||||
</metrics_collector>
|
||||
</configuration>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2018 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.
|
||||
//
|
||||
|
||||
java_test {
|
||||
name: "flickerlib",
|
||||
platform_apis: true,
|
||||
srcs: ["src/**/*.java"],
|
||||
static_libs: [
|
||||
"androidx.test.janktesthelper",
|
||||
"cts-wm-util",
|
||||
"platformprotosnano",
|
||||
"layersprotosnano",
|
||||
"truth-prebuilt",
|
||||
"sysui-helper",
|
||||
"launcher-helper-lib",
|
||||
],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "flickerlib_without_helpers",
|
||||
platform_apis: true,
|
||||
srcs: ["src/**/*.java"],
|
||||
exclude_srcs: ["src/**/helpers/*.java"],
|
||||
static_libs: [
|
||||
"cts-wm-util",
|
||||
"platformprotosnano",
|
||||
"layersprotosnano",
|
||||
"truth-prebuilt"
|
||||
],
|
||||
}
|
||||
|
||||
java_library {
|
||||
name: "flickerautomationhelperlib",
|
||||
sdk_version: "test_current",
|
||||
srcs: [
|
||||
"src/com/android/server/wm/flicker/helpers/AutomationUtils.java",
|
||||
"src/com/android/server/wm/flicker/WindowUtils.java",
|
||||
],
|
||||
static_libs: [
|
||||
"sysui-helper",
|
||||
"launcher-helper-lib",
|
||||
"compatibility-device-util-axt",
|
||||
],
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Collection of functional interfaces and classes representing assertions and their associated
|
||||
* results. Assertions are functions that are applied over a single trace entry and returns a
|
||||
* result which includes a detailed reason if the assertion fails.
|
||||
*/
|
||||
public class Assertions {
|
||||
/**
|
||||
* Checks assertion on a single trace entry.
|
||||
*
|
||||
* @param <T> trace entry type to perform the assertion on.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface TraceAssertion<T> extends Function<T, Result> {
|
||||
/**
|
||||
* Returns an assertion that represents the logical negation of this assertion.
|
||||
*
|
||||
* @return a assertion that represents the logical negation of this assertion
|
||||
*/
|
||||
default TraceAssertion<T> negate() {
|
||||
return (T t) -> apply(t).negate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks assertion on a single layers trace entry.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface LayersTraceAssertion extends TraceAssertion<LayersTrace.Entry> {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to store assertions with an identifier to help generate more useful debug
|
||||
* data when dealing with multiple assertions.
|
||||
*/
|
||||
public static class NamedAssertion<T> {
|
||||
public final TraceAssertion<T> assertion;
|
||||
public final String name;
|
||||
|
||||
public NamedAssertion(TraceAssertion<T> assertion, String name) {
|
||||
this.assertion = assertion;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains the result of an assertion including the reason for failed assertions.
|
||||
*/
|
||||
public static class Result {
|
||||
public static final String NEGATION_PREFIX = "!";
|
||||
public final boolean success;
|
||||
public final long timestamp;
|
||||
public final String assertionName;
|
||||
public final String reason;
|
||||
|
||||
public Result(boolean success, long timestamp, String assertionName, String reason) {
|
||||
this.success = success;
|
||||
this.timestamp = timestamp;
|
||||
this.assertionName = assertionName;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public Result(boolean success, String reason) {
|
||||
this.success = success;
|
||||
this.reason = reason;
|
||||
this.assertionName = "";
|
||||
this.timestamp = 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the negated {@code Result} and adds a negation prefix to the assertion name.
|
||||
*/
|
||||
public Result negate() {
|
||||
String negatedAssertionName;
|
||||
if (this.assertionName.startsWith(NEGATION_PREFIX)) {
|
||||
negatedAssertionName = this.assertionName.substring(NEGATION_PREFIX.length() + 1);
|
||||
} else {
|
||||
negatedAssertionName = NEGATION_PREFIX + this.assertionName;
|
||||
}
|
||||
return new Result(!this.success, this.timestamp, negatedAssertionName, this.reason);
|
||||
}
|
||||
|
||||
public boolean passed() {
|
||||
return this.success;
|
||||
}
|
||||
|
||||
public boolean failed() {
|
||||
return !this.success;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Timestamp: " + prettyTimestamp(timestamp)
|
||||
+ "\nAssertion: " + assertionName
|
||||
+ "\nReason: " + reason;
|
||||
}
|
||||
|
||||
private String prettyTimestamp(long timestamp_ns) {
|
||||
StringBuilder prettyTimestamp = new StringBuilder();
|
||||
TimeUnit[] timeUnits = {TimeUnit.HOURS, TimeUnit.MINUTES, TimeUnit.SECONDS, TimeUnit
|
||||
.MILLISECONDS};
|
||||
String[] unitSuffixes = {"h", "m", "s", "ms"};
|
||||
|
||||
for (int i = 0; i < timeUnits.length; i++) {
|
||||
long convertedTime = timeUnits[i].convert(timestamp_ns, TimeUnit.NANOSECONDS);
|
||||
timestamp_ns -= TimeUnit.NANOSECONDS.convert(convertedTime, timeUnits[i]);
|
||||
prettyTimestamp.append(convertedTime).append(unitSuffixes[i]);
|
||||
}
|
||||
|
||||
return prettyTimestamp.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,183 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import com.android.server.wm.flicker.Assertions.NamedAssertion;
|
||||
import com.android.server.wm.flicker.Assertions.Result;
|
||||
import com.android.server.wm.flicker.Assertions.TraceAssertion;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Captures some of the common logic in {@link LayersTraceSubject} and {@link WmTraceSubject}
|
||||
* used to filter trace entries and combine multiple assertions.
|
||||
*
|
||||
* @param <T> trace entry type
|
||||
*/
|
||||
public class AssertionsChecker<T extends ITraceEntry> {
|
||||
private boolean mFilterEntriesByRange = false;
|
||||
private long mFilterStartTime = 0;
|
||||
private long mFilterEndTime = 0;
|
||||
private AssertionOption mOption = AssertionOption.NONE;
|
||||
private List<NamedAssertion<T>> mAssertions = new LinkedList<>();
|
||||
|
||||
public void add(Assertions.TraceAssertion<T> assertion, String name) {
|
||||
mAssertions.add(new NamedAssertion<>(assertion, name));
|
||||
}
|
||||
|
||||
public void filterByRange(long startTime, long endTime) {
|
||||
mFilterEntriesByRange = true;
|
||||
mFilterStartTime = startTime;
|
||||
mFilterEndTime = endTime;
|
||||
}
|
||||
|
||||
private void setOption(AssertionOption option) {
|
||||
if (mOption != AssertionOption.NONE && option != mOption) {
|
||||
throw new IllegalArgumentException("Cannot use " + mOption + " option with "
|
||||
+ option + " option.");
|
||||
}
|
||||
mOption = option;
|
||||
}
|
||||
|
||||
public void checkFirstEntry() {
|
||||
setOption(AssertionOption.CHECK_FIRST_ENTRY);
|
||||
}
|
||||
|
||||
public void checkLastEntry() {
|
||||
setOption(AssertionOption.CHECK_LAST_ENTRY);
|
||||
}
|
||||
|
||||
public void checkChangingAssertions() {
|
||||
setOption(AssertionOption.CHECK_CHANGING_ASSERTIONS);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Filters trace entries then runs assertions returning a list of failures.
|
||||
*
|
||||
* @param entries list of entries to perform assertions on
|
||||
* @return list of failed assertion results
|
||||
*/
|
||||
public List<Result> test(List<T> entries) {
|
||||
List<T> filteredEntries;
|
||||
List<Result> failures;
|
||||
|
||||
if (mFilterEntriesByRange) {
|
||||
filteredEntries = entries.stream()
|
||||
.filter(e -> ((e.getTimestamp() >= mFilterStartTime)
|
||||
&& (e.getTimestamp() <= mFilterEndTime)))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
filteredEntries = entries;
|
||||
}
|
||||
|
||||
switch (mOption) {
|
||||
case CHECK_CHANGING_ASSERTIONS:
|
||||
return assertChanges(filteredEntries);
|
||||
case CHECK_FIRST_ENTRY:
|
||||
return assertEntry(filteredEntries.get(0));
|
||||
case CHECK_LAST_ENTRY:
|
||||
return assertEntry(filteredEntries.get(filteredEntries.size() - 1));
|
||||
}
|
||||
return assertAll(filteredEntries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Steps through each trace entry checking if provided assertions are true in the order they
|
||||
* are added. Each assertion must be true for at least a single trace entry.
|
||||
*
|
||||
* This can be used to check for asserting a change in property over a trace. Such as visibility
|
||||
* for a window changes from true to false or top-most window changes from A to Bb and back to A
|
||||
* again.
|
||||
*/
|
||||
private List<Result> assertChanges(List<T> entries) {
|
||||
List<Result> failures = new ArrayList<>();
|
||||
int entryIndex = 0;
|
||||
int assertionIndex = 0;
|
||||
int lastPassedAssertionIndex = -1;
|
||||
|
||||
if (mAssertions.size() == 0) {
|
||||
return failures;
|
||||
}
|
||||
|
||||
while (assertionIndex < mAssertions.size() && entryIndex < entries.size()) {
|
||||
TraceAssertion<T> currentAssertion = mAssertions.get(assertionIndex).assertion;
|
||||
Result result = currentAssertion.apply(entries.get(entryIndex));
|
||||
if (result.passed()) {
|
||||
lastPassedAssertionIndex = assertionIndex;
|
||||
entryIndex++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastPassedAssertionIndex != assertionIndex) {
|
||||
failures.add(result);
|
||||
break;
|
||||
}
|
||||
assertionIndex++;
|
||||
|
||||
if (assertionIndex == mAssertions.size()) {
|
||||
failures.add(result);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (failures.isEmpty()) {
|
||||
if (assertionIndex != mAssertions.size() - 1) {
|
||||
String reason = "\nAssertion " + mAssertions.get(assertionIndex).name
|
||||
+ " never became false";
|
||||
reason += "\nPassed assertions: " + mAssertions.stream().limit(assertionIndex)
|
||||
.map(assertion -> assertion.name).collect(Collectors.joining(","));
|
||||
reason += "\nUntested assertions: " + mAssertions.stream().skip(assertionIndex + 1)
|
||||
.map(assertion -> assertion.name).collect(Collectors.joining(","));
|
||||
|
||||
Result result = new Result(false /* success */, 0 /* timestamp */,
|
||||
"assertChanges", "Not all assertions passed." + reason);
|
||||
failures.add(result);
|
||||
}
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
private List<Result> assertEntry(T entry) {
|
||||
List<Result> failures = new ArrayList<>();
|
||||
for (NamedAssertion<T> assertion : mAssertions) {
|
||||
Result result = assertion.assertion.apply(entry);
|
||||
if (result.failed()) {
|
||||
failures.add(result);
|
||||
}
|
||||
}
|
||||
return failures;
|
||||
}
|
||||
|
||||
private List<Result> assertAll(List<T> entries) {
|
||||
return mAssertions.stream().flatMap(
|
||||
assertion -> entries.stream()
|
||||
.map(assertion.assertion)
|
||||
.filter(Result::failed))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private enum AssertionOption {
|
||||
NONE,
|
||||
CHECK_CHANGING_ASSERTIONS,
|
||||
CHECK_FIRST_ENTRY,
|
||||
CHECK_LAST_ENTRY,
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
/**
|
||||
* Common interface for Layer and WindowManager trace entries.
|
||||
*/
|
||||
public interface ITraceEntry {
|
||||
/**
|
||||
* @return timestamp of current entry
|
||||
*/
|
||||
long getTimestamp();
|
||||
}
|
||||
@@ -1,420 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.surfaceflinger.nano.Layers.LayerProto;
|
||||
import android.surfaceflinger.nano.Layers.RectProto;
|
||||
import android.surfaceflinger.nano.Layers.RegionProto;
|
||||
import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto;
|
||||
import android.surfaceflinger.nano.Layerstrace.LayersTraceProto;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.server.wm.flicker.Assertions.Result;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Contains a collection of parsed Layers trace entries and assertions to apply over
|
||||
* a single entry.
|
||||
*
|
||||
* Each entry is parsed into a list of {@link LayersTrace.Entry} objects.
|
||||
*/
|
||||
public class LayersTrace {
|
||||
final private List<Entry> mEntries;
|
||||
@Nullable
|
||||
final private Path mSource;
|
||||
|
||||
private LayersTrace(List<Entry> entries, Path source) {
|
||||
this.mEntries = entries;
|
||||
this.mSource = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
|
||||
* of trace entries, storing the flattened layers into its hierarchical structure.
|
||||
*
|
||||
* @param data binary proto data
|
||||
* @param source Path to source of data for additional debug information
|
||||
*/
|
||||
public static LayersTrace parseFrom(byte[] data, Path source) {
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
LayersTraceFileProto fileProto;
|
||||
try {
|
||||
fileProto = LayersTraceFileProto.parseFrom(data);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
for (LayersTraceProto traceProto : fileProto.entry) {
|
||||
Entry entry = Entry.fromFlattenedLayers(traceProto.elapsedRealtimeNanos,
|
||||
traceProto.layers.layers);
|
||||
entries.add(entry);
|
||||
}
|
||||
return new LayersTrace(entries, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses {@code LayersTraceFileProto} from {@code data} and uses the proto to generates a list
|
||||
* of trace entries, storing the flattened layers into its hierarchical structure.
|
||||
*
|
||||
* @param data binary proto data
|
||||
*/
|
||||
public static LayersTrace parseFrom(byte[] data) {
|
||||
return parseFrom(data, null);
|
||||
}
|
||||
|
||||
public List<Entry> getEntries() {
|
||||
return mEntries;
|
||||
}
|
||||
|
||||
public Entry getEntry(long timestamp) {
|
||||
Optional<Entry> entry = mEntries.stream()
|
||||
.filter(e -> e.getTimestamp() == timestamp)
|
||||
.findFirst();
|
||||
if (!entry.isPresent()) {
|
||||
throw new RuntimeException("Entry does not exist for timestamp " + timestamp);
|
||||
}
|
||||
return entry.get();
|
||||
}
|
||||
|
||||
public Optional<Path> getSource() {
|
||||
return Optional.ofNullable(mSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single Layer trace entry.
|
||||
*/
|
||||
public static class Entry implements ITraceEntry {
|
||||
private long mTimestamp;
|
||||
private List<Layer> mRootLayers; // hierarchical representation of layers
|
||||
private List<Layer> mFlattenedLayers = null;
|
||||
|
||||
private Entry(long timestamp, List<Layer> rootLayers) {
|
||||
this.mTimestamp = timestamp;
|
||||
this.mRootLayers = rootLayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs the layer hierarchy from a flattened list of layers.
|
||||
*/
|
||||
public static Entry fromFlattenedLayers(long timestamp, LayerProto[] protos) {
|
||||
SparseArray<Layer> layerMap = new SparseArray<>();
|
||||
ArrayList<Layer> orphans = new ArrayList<>();
|
||||
for (LayerProto proto : protos) {
|
||||
int id = proto.id;
|
||||
int parentId = proto.parent;
|
||||
|
||||
Layer newLayer = layerMap.get(id);
|
||||
if (newLayer == null) {
|
||||
newLayer = new Layer(proto);
|
||||
layerMap.append(id, newLayer);
|
||||
} else if (newLayer.mProto != null) {
|
||||
throw new RuntimeException("Duplicate layer id found:" + id);
|
||||
} else {
|
||||
newLayer.mProto = proto;
|
||||
orphans.remove(newLayer);
|
||||
}
|
||||
|
||||
// add parent placeholder
|
||||
if (layerMap.get(parentId) == null) {
|
||||
Layer orphanLayer = new Layer(null);
|
||||
layerMap.append(parentId, orphanLayer);
|
||||
orphans.add(orphanLayer);
|
||||
}
|
||||
layerMap.get(parentId).addChild(newLayer);
|
||||
newLayer.addParent(layerMap.get(parentId));
|
||||
}
|
||||
|
||||
// Fail if we find orphan layers.
|
||||
orphans.remove(layerMap.get(-1));
|
||||
orphans.forEach(orphan -> {
|
||||
String childNodes = orphan.mChildren.stream().map(node ->
|
||||
Integer.toString(node.getId())).collect(Collectors.joining(", "));
|
||||
int orphanId = orphan.mChildren.get(0).mProto.parent;
|
||||
throw new RuntimeException(
|
||||
"Failed to parse layers trace. Found orphan layers with parent "
|
||||
+ "layer id:" + orphanId + " : " + childNodes);
|
||||
});
|
||||
|
||||
return new Entry(timestamp, layerMap.get(-1).mChildren);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts {@link Rect} from {@link RectProto}.
|
||||
*/
|
||||
private static Rect extract(RectProto proto) {
|
||||
return new Rect(proto.left, proto.top, proto.right, proto.bottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts {@link Rect} from {@link RegionProto} by returning a rect that encompasses all
|
||||
* the rects making up the region.
|
||||
*/
|
||||
private static Rect extract(RegionProto regionProto) {
|
||||
Rect region = new Rect();
|
||||
for (RectProto proto : regionProto.rect) {
|
||||
region.union(proto.left, proto.top, proto.right, proto.bottom);
|
||||
}
|
||||
return region;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a region specified by {@code testRect} is covered by all visible layers.
|
||||
*/
|
||||
public Result coversRegion(Rect testRect) {
|
||||
String assertionName = "coversRegion";
|
||||
Collection<Layer> layers = asFlattenedLayers();
|
||||
|
||||
for (int x = testRect.left; x < testRect.right; x++) {
|
||||
for (int y = testRect.top; y < testRect.bottom; y++) {
|
||||
boolean emptyRegionFound = true;
|
||||
for (Layer layer : layers) {
|
||||
if (layer.isInvisible() || layer.isHiddenByParent()) {
|
||||
continue;
|
||||
}
|
||||
for (RectProto rectProto : layer.mProto.visibleRegion.rect) {
|
||||
Rect r = extract(rectProto);
|
||||
if (r.contains(x, y)) {
|
||||
y = r.bottom;
|
||||
emptyRegionFound = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (emptyRegionFound) {
|
||||
String reason = "Region to test: " + testRect
|
||||
+ "\nfirst empty point: " + x + ", " + y;
|
||||
reason += "\nvisible regions:";
|
||||
for (Layer layer : layers) {
|
||||
if (layer.isInvisible() || layer.isHiddenByParent()) {
|
||||
continue;
|
||||
}
|
||||
Rect r = extract(layer.mProto.visibleRegion);
|
||||
reason += "\n" + layer.mProto.name + r.toString();
|
||||
}
|
||||
return new Result(false /* success */, this.mTimestamp, assertionName,
|
||||
reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
String info = "Region covered: " + testRect;
|
||||
return new Result(true /* success */, this.mTimestamp, assertionName, info);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a layer with name {@code layerName} has a visible region
|
||||
* {@code expectedVisibleRegion}.
|
||||
*/
|
||||
public Result hasVisibleRegion(String layerName, Rect expectedVisibleRegion) {
|
||||
String assertionName = "hasVisibleRegion";
|
||||
String reason = "Could not find " + layerName;
|
||||
for (Layer layer : asFlattenedLayers()) {
|
||||
if (layer.mProto.name.contains(layerName)) {
|
||||
if (layer.isHiddenByParent()) {
|
||||
reason = layer.getHiddenByParentReason();
|
||||
continue;
|
||||
}
|
||||
if (layer.isInvisible()) {
|
||||
reason = layer.getVisibilityReason();
|
||||
continue;
|
||||
}
|
||||
Rect visibleRegion = extract(layer.mProto.visibleRegion);
|
||||
if (visibleRegion.equals(expectedVisibleRegion)) {
|
||||
return new Result(true /* success */, this.mTimestamp, assertionName,
|
||||
layer.mProto.name + "has visible region " + expectedVisibleRegion);
|
||||
}
|
||||
reason = layer.mProto.name + " has visible region:" + visibleRegion + " "
|
||||
+ "expected:" + expectedVisibleRegion;
|
||||
}
|
||||
}
|
||||
return new Result(false /* success */, this.mTimestamp, assertionName, reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a layer with name {@code layerName} is visible.
|
||||
*/
|
||||
public Result isVisible(String layerName) {
|
||||
String assertionName = "isVisible";
|
||||
String reason = "Could not find " + layerName;
|
||||
for (Layer layer : asFlattenedLayers()) {
|
||||
if (layer.mProto.name.contains(layerName)) {
|
||||
if (layer.isHiddenByParent()) {
|
||||
reason = layer.getHiddenByParentReason();
|
||||
continue;
|
||||
}
|
||||
if (layer.isInvisible()) {
|
||||
reason = layer.getVisibilityReason();
|
||||
continue;
|
||||
}
|
||||
return new Result(true /* success */, this.mTimestamp, assertionName,
|
||||
layer.mProto.name + " is visible");
|
||||
}
|
||||
}
|
||||
return new Result(false /* success */, this.mTimestamp, assertionName, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
public List<Layer> getRootLayers() {
|
||||
return mRootLayers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all layers as a flattened list using a depth first traversal.
|
||||
*/
|
||||
public List<Layer> asFlattenedLayers() {
|
||||
if (mFlattenedLayers == null) {
|
||||
mFlattenedLayers = new LinkedList<>();
|
||||
ArrayList<Layer> pendingLayers = new ArrayList<>(this.mRootLayers);
|
||||
while (!pendingLayers.isEmpty()) {
|
||||
Layer layer = pendingLayers.remove(0);
|
||||
mFlattenedLayers.add(layer);
|
||||
pendingLayers.addAll(0, layer.mChildren);
|
||||
}
|
||||
}
|
||||
return mFlattenedLayers;
|
||||
}
|
||||
|
||||
public Rect getVisibleBounds(String layerName) {
|
||||
List<Layer> layers = asFlattenedLayers();
|
||||
for (Layer layer : layers) {
|
||||
if (layer.mProto.name.contains(layerName) && layer.isVisible()) {
|
||||
return extract(layer.mProto.visibleRegion);
|
||||
}
|
||||
}
|
||||
return new Rect(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single layer with links to its parent and child layers.
|
||||
*/
|
||||
public static class Layer {
|
||||
@Nullable
|
||||
public LayerProto mProto;
|
||||
public List<Layer> mChildren;
|
||||
@Nullable
|
||||
public Layer mParent = null;
|
||||
|
||||
private Layer(LayerProto proto) {
|
||||
this.mProto = proto;
|
||||
this.mChildren = new ArrayList<>();
|
||||
}
|
||||
|
||||
private void addChild(Layer childLayer) {
|
||||
this.mChildren.add(childLayer);
|
||||
}
|
||||
|
||||
private void addParent(Layer parentLayer) {
|
||||
this.mParent = parentLayer;
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return mProto.id;
|
||||
}
|
||||
|
||||
public boolean isActiveBufferEmpty() {
|
||||
return this.mProto.activeBuffer == null || this.mProto.activeBuffer.height == 0
|
||||
|| this.mProto.activeBuffer.width == 0;
|
||||
}
|
||||
|
||||
public boolean isVisibleRegionEmpty() {
|
||||
if (this.mProto.visibleRegion == null) {
|
||||
return true;
|
||||
}
|
||||
Rect visibleRect = Entry.extract(this.mProto.visibleRegion);
|
||||
return visibleRect.height() == 0 || visibleRect.width() == 0;
|
||||
}
|
||||
|
||||
public boolean isHidden() {
|
||||
return (this.mProto.flags & /* FLAG_HIDDEN */ 0x1) != 0x0;
|
||||
}
|
||||
|
||||
public boolean isVisible() {
|
||||
return (!isActiveBufferEmpty() || isColorLayer())
|
||||
&& !isHidden()
|
||||
&& this.mProto.color != null
|
||||
&& this.mProto.color.a > 0
|
||||
&& !isVisibleRegionEmpty();
|
||||
}
|
||||
|
||||
public boolean isColorLayer() {
|
||||
return this.mProto.type.equals("ColorLayer");
|
||||
}
|
||||
|
||||
public boolean isRootLayer() {
|
||||
return mParent == null || mParent.mProto == null;
|
||||
}
|
||||
|
||||
public boolean isInvisible() {
|
||||
return !isVisible();
|
||||
}
|
||||
|
||||
public boolean isHiddenByParent() {
|
||||
return !isRootLayer() && (mParent.isHidden() || mParent.isHiddenByParent());
|
||||
}
|
||||
|
||||
public String getHiddenByParentReason() {
|
||||
String reason = "Layer " + mProto.name;
|
||||
if (isHiddenByParent()) {
|
||||
reason += " is hidden by parent: " + mParent.mProto.name;
|
||||
} else {
|
||||
reason += " is not hidden by parent: " + mParent.mProto.name;
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
|
||||
public String getVisibilityReason() {
|
||||
String reason = "Layer " + mProto.name;
|
||||
if (isVisible()) {
|
||||
reason += " is visible:";
|
||||
} else {
|
||||
reason += " is invisible:";
|
||||
if (this.mProto.activeBuffer == null) {
|
||||
reason += " activeBuffer=null";
|
||||
} else if (this.mProto.activeBuffer.height == 0) {
|
||||
reason += " activeBuffer.height=0";
|
||||
} else if (this.mProto.activeBuffer.width == 0) {
|
||||
reason += " activeBuffer.width=0";
|
||||
}
|
||||
if (!isColorLayer()) {
|
||||
reason += " type != ColorLayer";
|
||||
}
|
||||
if (isHidden()) {
|
||||
reason += " flags=" + this.mProto.flags + " (FLAG_HIDDEN set)";
|
||||
}
|
||||
if (this.mProto.color == null || this.mProto.color.a == 0) {
|
||||
reason += " color.a=0";
|
||||
}
|
||||
if (isVisibleRegionEmpty()) {
|
||||
reason += " visible region is empty";
|
||||
}
|
||||
}
|
||||
return reason;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import static com.google.common.truth.Truth.assertAbout;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.server.wm.flicker.Assertions.Result;
|
||||
import com.android.server.wm.flicker.LayersTrace.Entry;
|
||||
import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
|
||||
|
||||
import com.google.common.truth.FailureMetadata;
|
||||
import com.google.common.truth.Subject;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Truth subject for {@link LayersTrace} objects.
|
||||
*/
|
||||
public class LayersTraceSubject extends Subject<LayersTraceSubject, LayersTrace> {
|
||||
// Boiler-plate Subject.Factory for LayersTraceSubject
|
||||
private static final Subject.Factory<LayersTraceSubject, LayersTrace> FACTORY =
|
||||
new Subject.Factory<LayersTraceSubject, LayersTrace>() {
|
||||
@Override
|
||||
public LayersTraceSubject createSubject(
|
||||
FailureMetadata fm, @Nullable LayersTrace target) {
|
||||
return new LayersTraceSubject(fm, target);
|
||||
}
|
||||
};
|
||||
|
||||
private AssertionsChecker<Entry> mChecker = new AssertionsChecker<>();
|
||||
|
||||
private LayersTraceSubject(FailureMetadata fm, @Nullable LayersTrace subject) {
|
||||
super(fm, subject);
|
||||
}
|
||||
|
||||
// User-defined entry point
|
||||
public static LayersTraceSubject assertThat(@Nullable LayersTrace entry) {
|
||||
return assertAbout(FACTORY).that(entry);
|
||||
}
|
||||
|
||||
// User-defined entry point
|
||||
public static LayersTraceSubject assertThat(@Nullable TransitionResult result) {
|
||||
LayersTrace entries = LayersTrace.parseFrom(result.getLayersTrace(),
|
||||
result.getLayersTracePath());
|
||||
return assertWithMessage(result.toString()).about(FACTORY).that(entries);
|
||||
}
|
||||
|
||||
// Static method for getting the subject factory (for use with assertAbout())
|
||||
public static Subject.Factory<LayersTraceSubject, LayersTrace> entries() {
|
||||
return FACTORY;
|
||||
}
|
||||
|
||||
public void forAllEntries() {
|
||||
test();
|
||||
}
|
||||
|
||||
public void forRange(long startTime, long endTime) {
|
||||
mChecker.filterByRange(startTime, endTime);
|
||||
test();
|
||||
}
|
||||
|
||||
public LayersTraceSubject then() {
|
||||
mChecker.checkChangingAssertions();
|
||||
return this;
|
||||
}
|
||||
|
||||
public void inTheBeginning() {
|
||||
if (getSubject().getEntries().isEmpty()) {
|
||||
fail("No entries found.");
|
||||
}
|
||||
mChecker.checkFirstEntry();
|
||||
test();
|
||||
}
|
||||
|
||||
public void atTheEnd() {
|
||||
if (getSubject().getEntries().isEmpty()) {
|
||||
fail("No entries found.");
|
||||
}
|
||||
mChecker.checkLastEntry();
|
||||
test();
|
||||
}
|
||||
|
||||
private void test() {
|
||||
List<Result> failures = mChecker.test(getSubject().getEntries());
|
||||
if (!failures.isEmpty()) {
|
||||
String failureLogs = failures.stream().map(Result::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
String tracePath = "";
|
||||
if (getSubject().getSource().isPresent()) {
|
||||
tracePath = "\nLayers Trace can be found in: "
|
||||
+ getSubject().getSource().get().toAbsolutePath() + "\n";
|
||||
}
|
||||
fail(tracePath + failureLogs);
|
||||
}
|
||||
}
|
||||
|
||||
public LayersTraceSubject coversRegion(Rect rect) {
|
||||
mChecker.add(entry -> entry.coversRegion(rect),
|
||||
"coversRegion(" + rect + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
public LayersTraceSubject hasVisibleRegion(String layerName, Rect size) {
|
||||
mChecker.add(entry -> entry.hasVisibleRegion(layerName, size),
|
||||
"hasVisibleRegion(" + layerName + size + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
public LayersTraceSubject showsLayer(String layerName) {
|
||||
mChecker.add(entry -> entry.isVisible(layerName),
|
||||
"showsLayer(" + layerName + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
public LayersTraceSubject hidesLayer(String layerName) {
|
||||
mChecker.add(entry -> entry.isVisible(layerName).negate(),
|
||||
"hidesLayer(" + layerName + ")");
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,433 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import static com.android.server.wm.flicker.monitor.ITransitionMonitor.OUTPUT_DIR;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.server.wm.flicker.monitor.ITransitionMonitor;
|
||||
import com.android.server.wm.flicker.monitor.LayersTraceMonitor;
|
||||
import com.android.server.wm.flicker.monitor.ScreenRecorder;
|
||||
import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor;
|
||||
import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Builds and runs UI transitions capturing test artifacts.
|
||||
*
|
||||
* User can compose a transition from simpler steps, specifying setup and teardown steps. During
|
||||
* a transition, Layers trace, WindowManager trace, screen recordings and window animation frame
|
||||
* stats can be captured.
|
||||
*
|
||||
* <pre>
|
||||
* Transition builder options:
|
||||
* {@link TransitionBuilder#run(Runnable)} run transition under test. Monitors will be started
|
||||
* before the transition and stopped after the transition is completed.
|
||||
* {@link TransitionBuilder#repeat(int)} repeat transitions under test multiple times recording
|
||||
* result for each run.
|
||||
* {@link TransitionBuilder#withTag(String)} specify a string identifier used to prefix logs and
|
||||
* artifacts generated.
|
||||
* {@link TransitionBuilder#runBeforeAll(Runnable)} run setup transitions once before all other
|
||||
* transition are run to set up an initial state on device.
|
||||
* {@link TransitionBuilder#runBefore(Runnable)} run setup transitions before each test transition
|
||||
* run.
|
||||
* {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions after each test
|
||||
* transition.
|
||||
* {@link TransitionBuilder#runAfter(Runnable)} run teardown transitions once after all
|
||||
* other transition are run.
|
||||
* {@link TransitionBuilder#includeJankyRuns()} disables {@link WindowAnimationFrameStatsMonitor}
|
||||
* to monitor janky frames. If janky frames are detected, then the test run is skipped. This
|
||||
* monitor is enabled by default.
|
||||
* {@link TransitionBuilder#skipLayersTrace()} disables {@link LayersTraceMonitor} used to
|
||||
* capture Layers trace during a transition. This monitor is enabled by default.
|
||||
* {@link TransitionBuilder#skipWindowManagerTrace()} disables {@link WindowManagerTraceMonitor}
|
||||
* used to capture WindowManager trace during a transition. This monitor is enabled by
|
||||
* default.
|
||||
* {@link TransitionBuilder#recordAllRuns()} records the screen contents and saves it to a file.
|
||||
* All the runs including setup and teardown transitions are included in the recording. This
|
||||
* monitor is used for debugging purposes.
|
||||
* {@link TransitionBuilder#recordEachRun()} records the screen contents during test transitions
|
||||
* and saves it to a file for each run. This monitor is used for debugging purposes.
|
||||
*
|
||||
* Example transition to capture WindowManager and Layers trace when opening a test app:
|
||||
* {@code
|
||||
* TransitionRunner.newBuilder()
|
||||
* .withTag("OpenTestAppFast")
|
||||
* .runBeforeAll(UiAutomationLib::wakeUp)
|
||||
* .runBeforeAll(UiAutomationLib::UnlockDevice)
|
||||
* .runBeforeAll(UiAutomationLib::openTestApp)
|
||||
* .runBefore(UiAutomationLib::closeTestApp)
|
||||
* .run(UiAutomationLib::openTestApp)
|
||||
* .runAfterAll(UiAutomationLib::closeTestApp)
|
||||
* .repeat(5)
|
||||
* .build()
|
||||
* .run();
|
||||
* }
|
||||
* </pre>
|
||||
*/
|
||||
public class TransitionRunner {
|
||||
private static final String TAG = "FLICKER";
|
||||
private final ScreenRecorder mScreenRecorder;
|
||||
private final WindowManagerTraceMonitor mWmTraceMonitor;
|
||||
private final LayersTraceMonitor mLayersTraceMonitor;
|
||||
private final WindowAnimationFrameStatsMonitor mFrameStatsMonitor;
|
||||
|
||||
private final List<ITransitionMonitor> mAllRunsMonitors;
|
||||
private final List<ITransitionMonitor> mPerRunMonitors;
|
||||
private final List<Runnable> mBeforeAlls;
|
||||
private final List<Runnable> mBefores;
|
||||
private final List<Runnable> mTransitions;
|
||||
private final List<Runnable> mAfters;
|
||||
private final List<Runnable> mAfterAlls;
|
||||
|
||||
private final int mIterations;
|
||||
private final String mTestTag;
|
||||
|
||||
@Nullable
|
||||
private List<TransitionResult> mResults = null;
|
||||
|
||||
private TransitionRunner(TransitionBuilder builder) {
|
||||
mScreenRecorder = builder.mScreenRecorder;
|
||||
mWmTraceMonitor = builder.mWmTraceMonitor;
|
||||
mLayersTraceMonitor = builder.mLayersTraceMonitor;
|
||||
mFrameStatsMonitor = builder.mFrameStatsMonitor;
|
||||
|
||||
mAllRunsMonitors = builder.mAllRunsMonitors;
|
||||
mPerRunMonitors = builder.mPerRunMonitors;
|
||||
mBeforeAlls = builder.mBeforeAlls;
|
||||
mBefores = builder.mBefores;
|
||||
mTransitions = builder.mTransitions;
|
||||
mAfters = builder.mAfters;
|
||||
mAfterAlls = builder.mAfterAlls;
|
||||
|
||||
mIterations = builder.mIterations;
|
||||
mTestTag = builder.mTestTag;
|
||||
}
|
||||
|
||||
public static TransitionBuilder newBuilder() {
|
||||
return newBuilder(OUTPUT_DIR.toString());
|
||||
}
|
||||
|
||||
public static TransitionBuilder newBuilder(String outputDir) {
|
||||
return new TransitionBuilder(outputDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs the composed transition and calls monitors at the appropriate stages. If jank monitor
|
||||
* is enabled, transitions with jank are skipped.
|
||||
*
|
||||
* @return itself
|
||||
*/
|
||||
public TransitionRunner run() {
|
||||
mResults = new ArrayList<>();
|
||||
mAllRunsMonitors.forEach(ITransitionMonitor::start);
|
||||
mBeforeAlls.forEach(Runnable::run);
|
||||
for (int iteration = 0; iteration < mIterations; iteration++) {
|
||||
mBefores.forEach(Runnable::run);
|
||||
mPerRunMonitors.forEach(ITransitionMonitor::start);
|
||||
mTransitions.forEach(Runnable::run);
|
||||
mPerRunMonitors.forEach(ITransitionMonitor::stop);
|
||||
mAfters.forEach(Runnable::run);
|
||||
if (runJankFree() && mFrameStatsMonitor.jankyFramesDetected()) {
|
||||
String msg = String.format("Skipping iteration %d/%d for test %s due to jank. %s",
|
||||
iteration, mIterations - 1, mTestTag, mFrameStatsMonitor.toString());
|
||||
Log.e(TAG, msg);
|
||||
continue;
|
||||
}
|
||||
mResults.add(saveResult(iteration));
|
||||
}
|
||||
mAfterAlls.forEach(Runnable::run);
|
||||
mAllRunsMonitors.forEach(monitor -> {
|
||||
monitor.stop();
|
||||
monitor.save(mTestTag);
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of transition results.
|
||||
*
|
||||
* @return list of transition results.
|
||||
*/
|
||||
public List<TransitionResult> getResults() {
|
||||
if (mResults == null) {
|
||||
throw new IllegalStateException("Results do not exist!");
|
||||
}
|
||||
return mResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all transition results that are not marked for saving.
|
||||
*
|
||||
* @return list of transition results.
|
||||
*/
|
||||
public void deleteResults() {
|
||||
if (mResults == null) {
|
||||
return;
|
||||
}
|
||||
mResults.stream()
|
||||
.filter(TransitionResult::canDelete)
|
||||
.forEach(TransitionResult::delete);
|
||||
mResults = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves monitor results to file.
|
||||
*
|
||||
* @return object containing paths to test artifacts
|
||||
*/
|
||||
private TransitionResult saveResult(int iteration) {
|
||||
Path windowTrace = null;
|
||||
Path layerTrace = null;
|
||||
Path screenCaptureVideo = null;
|
||||
|
||||
if (mPerRunMonitors.contains(mWmTraceMonitor)) {
|
||||
windowTrace = mWmTraceMonitor.save(mTestTag, iteration);
|
||||
}
|
||||
if (mPerRunMonitors.contains(mLayersTraceMonitor)) {
|
||||
layerTrace = mLayersTraceMonitor.save(mTestTag, iteration);
|
||||
}
|
||||
if (mPerRunMonitors.contains(mScreenRecorder)) {
|
||||
screenCaptureVideo = mScreenRecorder.save(mTestTag, iteration);
|
||||
}
|
||||
return new TransitionResult(layerTrace, windowTrace, screenCaptureVideo);
|
||||
}
|
||||
|
||||
private boolean runJankFree() {
|
||||
return mPerRunMonitors.contains(mFrameStatsMonitor);
|
||||
}
|
||||
|
||||
public String getTestTag() {
|
||||
return mTestTag;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores paths to all test artifacts.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static class TransitionResult {
|
||||
@Nullable
|
||||
public final Path layersTrace;
|
||||
@Nullable
|
||||
public final Path windowManagerTrace;
|
||||
@Nullable
|
||||
public final Path screenCaptureVideo;
|
||||
private boolean flaggedForSaving;
|
||||
|
||||
public TransitionResult(@Nullable Path layersTrace, @Nullable Path windowManagerTrace,
|
||||
@Nullable Path screenCaptureVideo) {
|
||||
this.layersTrace = layersTrace;
|
||||
this.windowManagerTrace = windowManagerTrace;
|
||||
this.screenCaptureVideo = screenCaptureVideo;
|
||||
}
|
||||
|
||||
public void flagForSaving() {
|
||||
flaggedForSaving = true;
|
||||
}
|
||||
|
||||
public boolean canDelete() {
|
||||
return !flaggedForSaving;
|
||||
}
|
||||
|
||||
public boolean layersTraceExists() {
|
||||
return layersTrace != null && layersTrace.toFile().exists();
|
||||
}
|
||||
|
||||
public byte[] getLayersTrace() {
|
||||
try {
|
||||
return Files.toByteArray(this.layersTrace.toFile());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Path getLayersTracePath() {
|
||||
return layersTrace;
|
||||
}
|
||||
|
||||
public boolean windowManagerTraceExists() {
|
||||
return windowManagerTrace != null && windowManagerTrace.toFile().exists();
|
||||
}
|
||||
|
||||
public byte[] getWindowManagerTrace() {
|
||||
try {
|
||||
return Files.toByteArray(this.windowManagerTrace.toFile());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public Path getWindowManagerTracePath() {
|
||||
return windowManagerTrace;
|
||||
}
|
||||
|
||||
public boolean screenCaptureVideoExists() {
|
||||
return screenCaptureVideo != null && screenCaptureVideo.toFile().exists();
|
||||
}
|
||||
|
||||
public Path screenCaptureVideoPath() {
|
||||
return screenCaptureVideo;
|
||||
}
|
||||
|
||||
public void delete() {
|
||||
if (layersTraceExists()) layersTrace.toFile().delete();
|
||||
if (windowManagerTraceExists()) windowManagerTrace.toFile().delete();
|
||||
if (screenCaptureVideoExists()) screenCaptureVideo.toFile().delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link TransitionRunner} instance.
|
||||
*/
|
||||
public static class TransitionBuilder {
|
||||
private ScreenRecorder mScreenRecorder;
|
||||
private WindowManagerTraceMonitor mWmTraceMonitor;
|
||||
private LayersTraceMonitor mLayersTraceMonitor;
|
||||
private WindowAnimationFrameStatsMonitor mFrameStatsMonitor;
|
||||
|
||||
private List<ITransitionMonitor> mAllRunsMonitors = new LinkedList<>();
|
||||
private List<ITransitionMonitor> mPerRunMonitors = new LinkedList<>();
|
||||
private List<Runnable> mBeforeAlls = new LinkedList<>();
|
||||
private List<Runnable> mBefores = new LinkedList<>();
|
||||
private List<Runnable> mTransitions = new LinkedList<>();
|
||||
private List<Runnable> mAfters = new LinkedList<>();
|
||||
private List<Runnable> mAfterAlls = new LinkedList<>();
|
||||
|
||||
private boolean mRunJankFree = true;
|
||||
private boolean mCaptureWindowManagerTrace = true;
|
||||
private boolean mCaptureLayersTrace = true;
|
||||
private boolean mRecordEachRun = false;
|
||||
private int mIterations = 1;
|
||||
private String mTestTag = "";
|
||||
|
||||
private boolean mRecordAllRuns = false;
|
||||
|
||||
public TransitionBuilder(String outputDir) {
|
||||
mScreenRecorder = new ScreenRecorder();
|
||||
mWmTraceMonitor = new WindowManagerTraceMonitor(outputDir);
|
||||
mLayersTraceMonitor = new LayersTraceMonitor(outputDir);
|
||||
mFrameStatsMonitor = new
|
||||
WindowAnimationFrameStatsMonitor(InstrumentationRegistry.getInstrumentation());
|
||||
}
|
||||
|
||||
public TransitionRunner build() {
|
||||
if (mCaptureWindowManagerTrace) {
|
||||
mPerRunMonitors.add(mWmTraceMonitor);
|
||||
}
|
||||
|
||||
if (mCaptureLayersTrace) {
|
||||
mPerRunMonitors.add(mLayersTraceMonitor);
|
||||
}
|
||||
|
||||
if (mRunJankFree) {
|
||||
mPerRunMonitors.add(mFrameStatsMonitor);
|
||||
}
|
||||
|
||||
if (mRecordAllRuns) {
|
||||
mAllRunsMonitors.add(mScreenRecorder);
|
||||
}
|
||||
|
||||
if (mRecordEachRun) {
|
||||
mPerRunMonitors.add(mScreenRecorder);
|
||||
}
|
||||
|
||||
return new TransitionRunner(this);
|
||||
}
|
||||
|
||||
public TransitionBuilder runBeforeAll(Runnable runnable) {
|
||||
mBeforeAlls.add(runnable);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransitionBuilder runBefore(Runnable runnable) {
|
||||
mBefores.add(runnable);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransitionBuilder run(Runnable runnable) {
|
||||
mTransitions.add(runnable);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransitionBuilder runAfter(Runnable runnable) {
|
||||
mAfters.add(runnable);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransitionBuilder runAfterAll(Runnable runnable) {
|
||||
mAfterAlls.add(runnable);
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransitionBuilder repeat(int iterations) {
|
||||
mIterations = iterations;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransitionBuilder skipWindowManagerTrace() {
|
||||
mCaptureWindowManagerTrace = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransitionBuilder skipLayersTrace() {
|
||||
mCaptureLayersTrace = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransitionBuilder includeJankyRuns() {
|
||||
mRunJankFree = false;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransitionBuilder recordEachRun() {
|
||||
if (mRecordAllRuns) {
|
||||
throw new IllegalArgumentException("Invalid option with recordAllRuns");
|
||||
}
|
||||
mRecordEachRun = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransitionBuilder recordAllRuns() {
|
||||
if (mRecordEachRun) {
|
||||
throw new IllegalArgumentException("Invalid option with recordEachRun");
|
||||
}
|
||||
mRecordAllRuns = true;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TransitionBuilder withTag(String testTag) {
|
||||
if (testTag.contains(" ")) {
|
||||
throw new IllegalArgumentException("The test tag can not contain spaces since it "
|
||||
+ "is a part of the file name");
|
||||
}
|
||||
mTestTag = testTag;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,240 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.server.wm.flicker.Assertions.Result;
|
||||
import com.android.server.wm.nano.AppWindowTokenProto;
|
||||
import com.android.server.wm.nano.StackProto;
|
||||
import com.android.server.wm.nano.TaskProto;
|
||||
import com.android.server.wm.nano.WindowManagerTraceFileProto;
|
||||
import com.android.server.wm.nano.WindowManagerTraceProto;
|
||||
import com.android.server.wm.nano.WindowStateProto;
|
||||
import com.android.server.wm.nano.WindowTokenProto;
|
||||
|
||||
import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Contains a collection of parsed WindowManager trace entries and assertions to apply over
|
||||
* a single entry.
|
||||
*
|
||||
* Each entry is parsed into a list of {@link WindowManagerTrace.Entry} objects.
|
||||
*/
|
||||
public class WindowManagerTrace {
|
||||
private static final int DEFAULT_DISPLAY = 0;
|
||||
private final List<Entry> mEntries;
|
||||
@Nullable
|
||||
final private Path mSource;
|
||||
|
||||
private WindowManagerTrace(List<Entry> entries, Path source) {
|
||||
this.mEntries = entries;
|
||||
this.mSource = source;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses {@code WindowManagerTraceFileProto} from {@code data} and uses the proto to
|
||||
* generates a list of trace entries.
|
||||
*
|
||||
* @param data binary proto data
|
||||
* @param source Path to source of data for additional debug information
|
||||
*/
|
||||
public static WindowManagerTrace parseFrom(byte[] data, Path source) {
|
||||
List<Entry> entries = new ArrayList<>();
|
||||
|
||||
WindowManagerTraceFileProto fileProto;
|
||||
try {
|
||||
fileProto = WindowManagerTraceFileProto.parseFrom(data);
|
||||
} catch (InvalidProtocolBufferNanoException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
for (WindowManagerTraceProto entryProto : fileProto.entry) {
|
||||
entries.add(new Entry(entryProto));
|
||||
}
|
||||
return new WindowManagerTrace(entries, source);
|
||||
}
|
||||
|
||||
public static WindowManagerTrace parseFrom(byte[] data) {
|
||||
return parseFrom(data, null);
|
||||
}
|
||||
|
||||
public List<Entry> getEntries() {
|
||||
return mEntries;
|
||||
}
|
||||
|
||||
public Entry getEntry(long timestamp) {
|
||||
Optional<Entry> entry = mEntries.stream()
|
||||
.filter(e -> e.getTimestamp() == timestamp)
|
||||
.findFirst();
|
||||
if (!entry.isPresent()) {
|
||||
throw new RuntimeException("Entry does not exist for timestamp " + timestamp);
|
||||
}
|
||||
return entry.get();
|
||||
}
|
||||
|
||||
public Optional<Path> getSource() {
|
||||
return Optional.ofNullable(mSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a single WindowManager trace entry.
|
||||
*/
|
||||
public static class Entry implements ITraceEntry {
|
||||
private final WindowManagerTraceProto mProto;
|
||||
|
||||
public Entry(WindowManagerTraceProto proto) {
|
||||
mProto = proto;
|
||||
}
|
||||
|
||||
private static Result isWindowVisible(String windowTitle,
|
||||
WindowTokenProto[] windowTokenProtos) {
|
||||
boolean titleFound = false;
|
||||
for (WindowTokenProto windowToken : windowTokenProtos) {
|
||||
for (WindowStateProto windowState : windowToken.windows) {
|
||||
if (windowState.identifier.title.contains(windowTitle)) {
|
||||
titleFound = true;
|
||||
if (isVisible(windowState)) {
|
||||
return new Result(true /* success */,
|
||||
windowState.identifier.title + " is visible");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String reason;
|
||||
if (!titleFound) {
|
||||
reason = windowTitle + " cannot be found";
|
||||
} else {
|
||||
reason = windowTitle + " is invisible";
|
||||
}
|
||||
return new Result(false /* success */, reason);
|
||||
}
|
||||
|
||||
private static boolean isVisible(WindowStateProto windowState) {
|
||||
return windowState.windowContainer.visible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return mProto.elapsedRealtimeNanos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns window title of the top most visible app window.
|
||||
*/
|
||||
private String getTopVisibleAppWindow() {
|
||||
StackProto[] stacks = mProto.windowManagerService.rootWindowContainer
|
||||
.displays[DEFAULT_DISPLAY].stacks;
|
||||
for (StackProto stack : stacks) {
|
||||
for (TaskProto task : stack.tasks) {
|
||||
for (AppWindowTokenProto token : task.appWindowTokens) {
|
||||
for (WindowStateProto windowState : token.windowToken.windows) {
|
||||
if (windowState.windowContainer.visible) {
|
||||
return task.appWindowTokens[0].name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if aboveAppWindow with {@code windowTitle} is visible.
|
||||
*/
|
||||
public Result isAboveAppWindowVisible(String windowTitle) {
|
||||
WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
|
||||
.rootWindowContainer
|
||||
.displays[DEFAULT_DISPLAY].aboveAppWindows;
|
||||
Result result = isWindowVisible(windowTitle, windowTokenProtos);
|
||||
return new Result(result.success, getTimestamp(), "showsAboveAppWindow", result.reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if belowAppWindow with {@code windowTitle} is visible.
|
||||
*/
|
||||
public Result isBelowAppWindowVisible(String windowTitle) {
|
||||
WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
|
||||
.rootWindowContainer
|
||||
.displays[DEFAULT_DISPLAY].belowAppWindows;
|
||||
Result result = isWindowVisible(windowTitle, windowTokenProtos);
|
||||
return new Result(result.success, getTimestamp(), "isBelowAppWindowVisible",
|
||||
result.reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if imeWindow with {@code windowTitle} is visible.
|
||||
*/
|
||||
public Result isImeWindowVisible(String windowTitle) {
|
||||
WindowTokenProto[] windowTokenProtos = mProto.windowManagerService
|
||||
.rootWindowContainer
|
||||
.displays[DEFAULT_DISPLAY].imeWindows;
|
||||
Result result = isWindowVisible(windowTitle, windowTokenProtos);
|
||||
return new Result(result.success, getTimestamp(), "isImeWindowVisible",
|
||||
result.reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if app window with {@code windowTitle} is on top.
|
||||
*/
|
||||
public Result isVisibleAppWindowOnTop(String windowTitle) {
|
||||
String topAppWindow = getTopVisibleAppWindow();
|
||||
boolean success = topAppWindow.contains(windowTitle);
|
||||
String reason = "wanted=" + windowTitle + " found=" + topAppWindow;
|
||||
return new Result(success, getTimestamp(), "isAppWindowOnTop", reason);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if app window with {@code windowTitle} is visible.
|
||||
*/
|
||||
public Result isAppWindowVisible(String windowTitle) {
|
||||
final String assertionName = "isAppWindowVisible";
|
||||
boolean titleFound = false;
|
||||
StackProto[] stacks = mProto.windowManagerService.rootWindowContainer
|
||||
.displays[DEFAULT_DISPLAY].stacks;
|
||||
for (StackProto stack : stacks) {
|
||||
for (TaskProto task : stack.tasks) {
|
||||
for (AppWindowTokenProto token : task.appWindowTokens) {
|
||||
if (token.name.contains(windowTitle)) {
|
||||
titleFound = true;
|
||||
for (WindowStateProto windowState : token.windowToken.windows) {
|
||||
if (windowState.windowContainer.visible) {
|
||||
return new Result(true /* success */, getTimestamp(),
|
||||
assertionName, "Window " + token.name +
|
||||
"is visible");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
String reason;
|
||||
if (!titleFound) {
|
||||
reason = "Window " + windowTitle + " cannot be found";
|
||||
} else {
|
||||
reason = "Window " + windowTitle + " is invisible";
|
||||
}
|
||||
return new Result(false /* success */, getTimestamp(), assertionName, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.view.Surface;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
/**
|
||||
* Helper functions to retrieve system window sizes and positions.
|
||||
*/
|
||||
public class WindowUtils {
|
||||
|
||||
public static Rect getDisplayBounds() {
|
||||
Point display = new Point();
|
||||
WindowManager wm =
|
||||
(WindowManager) InstrumentationRegistry.getContext().getSystemService(
|
||||
Context.WINDOW_SERVICE);
|
||||
wm.getDefaultDisplay().getRealSize(display);
|
||||
return new Rect(0, 0, display.x, display.y);
|
||||
}
|
||||
|
||||
private static int getCurrentRotation() {
|
||||
WindowManager wm =
|
||||
(WindowManager) InstrumentationRegistry.getContext().getSystemService(
|
||||
Context.WINDOW_SERVICE);
|
||||
return wm.getDefaultDisplay().getRotation();
|
||||
}
|
||||
|
||||
public static Rect getDisplayBounds(int requestedRotation) {
|
||||
Rect displayBounds = getDisplayBounds();
|
||||
int currentDisplayRotation = getCurrentRotation();
|
||||
|
||||
boolean displayIsRotated = (currentDisplayRotation == Surface.ROTATION_90 ||
|
||||
currentDisplayRotation == Surface.ROTATION_270);
|
||||
|
||||
boolean requestedDisplayIsRotated = requestedRotation == Surface.ROTATION_90 ||
|
||||
requestedRotation == Surface.ROTATION_270;
|
||||
|
||||
// if the current orientation changes with the requested rotation,
|
||||
// flip height and width of display bounds.
|
||||
if (displayIsRotated != requestedDisplayIsRotated) {
|
||||
return new Rect(0, 0, displayBounds.height(), displayBounds.width());
|
||||
}
|
||||
|
||||
return new Rect(0, 0, displayBounds.width(), displayBounds.height());
|
||||
}
|
||||
|
||||
|
||||
public static Rect getAppPosition(int requestedRotation) {
|
||||
Rect displayBounds = getDisplayBounds();
|
||||
int currentDisplayRotation = getCurrentRotation();
|
||||
|
||||
boolean displayIsRotated = currentDisplayRotation == Surface.ROTATION_90 ||
|
||||
currentDisplayRotation == Surface.ROTATION_270;
|
||||
|
||||
boolean requestedAppIsRotated = requestedRotation == Surface.ROTATION_90 ||
|
||||
requestedRotation == Surface.ROTATION_270;
|
||||
|
||||
// display size will change if the display is reflected. Flip height and width of app if the
|
||||
// requested rotation is different from the current rotation.
|
||||
if (displayIsRotated != requestedAppIsRotated) {
|
||||
return new Rect(0, 0, displayBounds.height(), displayBounds.width());
|
||||
}
|
||||
|
||||
return new Rect(0, 0, displayBounds.width(), displayBounds.height());
|
||||
}
|
||||
|
||||
public static Rect getStatusBarPosition(int requestedRotation) {
|
||||
Resources resources = InstrumentationRegistry.getContext().getResources();
|
||||
String resourceName;
|
||||
Rect displayBounds = getDisplayBounds();
|
||||
int width;
|
||||
if (requestedRotation == Surface.ROTATION_0 || requestedRotation == Surface.ROTATION_180) {
|
||||
resourceName = "status_bar_height_portrait";
|
||||
width = Math.min(displayBounds.width(), displayBounds.height());
|
||||
} else {
|
||||
resourceName = "status_bar_height_landscape";
|
||||
width = Math.max(displayBounds.width(), displayBounds.height());
|
||||
}
|
||||
|
||||
int resourceId = resources.getIdentifier(resourceName, "dimen", "android");
|
||||
int height = resources.getDimensionPixelSize(resourceId);
|
||||
|
||||
return new Rect(0, 0, width, height);
|
||||
}
|
||||
|
||||
public static Rect getNavigationBarPosition(int requestedRotation) {
|
||||
Resources resources = InstrumentationRegistry.getContext().getResources();
|
||||
Rect displayBounds = getDisplayBounds();
|
||||
int displayWidth = Math.min(displayBounds.width(), displayBounds.height());
|
||||
int displayHeight = Math.max(displayBounds.width(), displayBounds.height());
|
||||
int resourceId;
|
||||
if (requestedRotation == Surface.ROTATION_0 || requestedRotation == Surface.ROTATION_180) {
|
||||
resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
|
||||
int height = resources.getDimensionPixelSize(resourceId);
|
||||
return new Rect(0, displayHeight - height, displayWidth, displayHeight);
|
||||
} else {
|
||||
resourceId = resources.getIdentifier("navigation_bar_width", "dimen", "android");
|
||||
int width = resources.getDimensionPixelSize(resourceId);
|
||||
// swap display dimensions in landscape or seascape mode
|
||||
int temp = displayHeight;
|
||||
displayHeight = displayWidth;
|
||||
displayWidth = temp;
|
||||
if (requestedRotation == Surface.ROTATION_90) {
|
||||
return new Rect(0, 0, width, displayHeight);
|
||||
} else {
|
||||
return new Rect(displayWidth - width, 0, displayWidth, displayHeight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int getNavigationBarHeight() {
|
||||
Resources resources = InstrumentationRegistry.getContext().getResources();
|
||||
int resourceId = resources.getIdentifier("navigation_bar_height", "dimen", "android");
|
||||
return resources.getDimensionPixelSize(resourceId);
|
||||
}
|
||||
|
||||
public static int getDockedStackDividerInset() {
|
||||
Resources resources = InstrumentationRegistry.getContext().getResources();
|
||||
int resourceId = resources.getIdentifier("docked_stack_divider_insets", "dimen",
|
||||
"android");
|
||||
return resources.getDimensionPixelSize(resourceId);
|
||||
}
|
||||
}
|
||||
@@ -1,192 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import static com.google.common.truth.Truth.assertAbout;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.server.wm.flicker.Assertions.Result;
|
||||
import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
|
||||
|
||||
import com.google.common.truth.FailureMetadata;
|
||||
import com.google.common.truth.Subject;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Truth subject for {@link WindowManagerTrace} objects.
|
||||
*/
|
||||
public class WmTraceSubject extends Subject<WmTraceSubject, WindowManagerTrace> {
|
||||
// Boiler-plate Subject.Factory for WmTraceSubject
|
||||
private static final Subject.Factory<WmTraceSubject, WindowManagerTrace> FACTORY =
|
||||
new Subject.Factory<WmTraceSubject, WindowManagerTrace>() {
|
||||
@Override
|
||||
public WmTraceSubject createSubject(
|
||||
FailureMetadata fm, @Nullable WindowManagerTrace target) {
|
||||
return new WmTraceSubject(fm, target);
|
||||
}
|
||||
};
|
||||
|
||||
private AssertionsChecker<WindowManagerTrace.Entry> mChecker = new AssertionsChecker<>();
|
||||
|
||||
private WmTraceSubject(FailureMetadata fm, @Nullable WindowManagerTrace subject) {
|
||||
super(fm, subject);
|
||||
}
|
||||
|
||||
// User-defined entry point
|
||||
public static WmTraceSubject assertThat(@Nullable WindowManagerTrace entry) {
|
||||
return assertAbout(FACTORY).that(entry);
|
||||
}
|
||||
|
||||
// User-defined entry point
|
||||
public static WmTraceSubject assertThat(@Nullable TransitionResult result) {
|
||||
WindowManagerTrace entries = WindowManagerTrace.parseFrom(result.getWindowManagerTrace(),
|
||||
result.getWindowManagerTracePath());
|
||||
return assertWithMessage(result.toString()).about(FACTORY).that(entries);
|
||||
}
|
||||
|
||||
// Static method for getting the subject factory (for use with assertAbout())
|
||||
public static Subject.Factory<WmTraceSubject, WindowManagerTrace> entries() {
|
||||
return FACTORY;
|
||||
}
|
||||
|
||||
public void forAllEntries() {
|
||||
test();
|
||||
}
|
||||
|
||||
public void forRange(long startTime, long endTime) {
|
||||
mChecker.filterByRange(startTime, endTime);
|
||||
test();
|
||||
}
|
||||
|
||||
public WmTraceSubject then() {
|
||||
mChecker.checkChangingAssertions();
|
||||
return this;
|
||||
}
|
||||
|
||||
public void inTheBeginning() {
|
||||
if (getSubject().getEntries().isEmpty()) {
|
||||
fail("No entries found.");
|
||||
}
|
||||
mChecker.checkFirstEntry();
|
||||
test();
|
||||
}
|
||||
|
||||
public void atTheEnd() {
|
||||
if (getSubject().getEntries().isEmpty()) {
|
||||
fail("No entries found.");
|
||||
}
|
||||
mChecker.checkLastEntry();
|
||||
test();
|
||||
}
|
||||
|
||||
private void test() {
|
||||
List<Result> failures = mChecker.test(getSubject().getEntries());
|
||||
if (!failures.isEmpty()) {
|
||||
Optional<Path> failureTracePath = getSubject().getSource();
|
||||
String failureLogs = failures.stream().map(Result::toString)
|
||||
.collect(Collectors.joining("\n"));
|
||||
String tracePath = "";
|
||||
if (failureTracePath.isPresent()) {
|
||||
tracePath = "\nWindowManager Trace can be found in: "
|
||||
+ failureTracePath.get().toAbsolutePath() + "\n";
|
||||
}
|
||||
fail(tracePath + failureLogs);
|
||||
}
|
||||
}
|
||||
|
||||
public WmTraceSubject showsAboveAppWindow(String partialWindowTitle) {
|
||||
mChecker.add(entry -> entry.isAboveAppWindowVisible(partialWindowTitle),
|
||||
"showsAboveAppWindow(" + partialWindowTitle + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
public WmTraceSubject hidesAboveAppWindow(String partialWindowTitle) {
|
||||
mChecker.add(entry -> entry.isAboveAppWindowVisible(partialWindowTitle).negate(),
|
||||
"hidesAboveAppWindow" + "(" + partialWindowTitle + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
public WmTraceSubject showsBelowAppWindow(String partialWindowTitle) {
|
||||
mChecker.add(entry -> entry.isBelowAppWindowVisible(partialWindowTitle),
|
||||
"showsBelowAppWindow(" + partialWindowTitle + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
public WmTraceSubject hidesBelowAppWindow(String partialWindowTitle) {
|
||||
mChecker.add(entry -> entry.isBelowAppWindowVisible(partialWindowTitle).negate(),
|
||||
"hidesBelowAppWindow" + "(" + partialWindowTitle + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
public WmTraceSubject showsImeWindow(String partialWindowTitle) {
|
||||
mChecker.add(entry -> entry.isImeWindowVisible(partialWindowTitle),
|
||||
"showsBelowAppWindow(" + partialWindowTitle + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
public WmTraceSubject hidesImeWindow(String partialWindowTitle) {
|
||||
mChecker.add(entry -> entry.isImeWindowVisible(partialWindowTitle).negate(),
|
||||
"hidesImeWindow" + "(" + partialWindowTitle + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
public WmTraceSubject showsAppWindowOnTop(String partialWindowTitle) {
|
||||
mChecker.add(
|
||||
entry -> {
|
||||
Result result = entry.isAppWindowVisible(partialWindowTitle);
|
||||
if (result.passed()) {
|
||||
result = entry.isVisibleAppWindowOnTop(partialWindowTitle);
|
||||
}
|
||||
return result;
|
||||
},
|
||||
"showsAppWindowOnTop(" + partialWindowTitle + ")"
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public WmTraceSubject hidesAppWindowOnTop(String partialWindowTitle) {
|
||||
mChecker.add(
|
||||
entry -> {
|
||||
Result result = entry.isAppWindowVisible(partialWindowTitle).negate();
|
||||
if (result.failed()) {
|
||||
result = entry.isVisibleAppWindowOnTop(partialWindowTitle).negate();
|
||||
}
|
||||
return result;
|
||||
},
|
||||
"hidesAppWindowOnTop(" + partialWindowTitle + ")"
|
||||
);
|
||||
return this;
|
||||
}
|
||||
|
||||
public WmTraceSubject showsAppWindow(String partialWindowTitle) {
|
||||
mChecker.add(entry -> entry.isAppWindowVisible(partialWindowTitle),
|
||||
"showsAppWindow(" + partialWindowTitle + ")");
|
||||
return this;
|
||||
}
|
||||
|
||||
public WmTraceSubject hidesAppWindow(String partialWindowTitle) {
|
||||
mChecker.add(entry -> entry.isAppWindowVisible(partialWindowTitle).negate(),
|
||||
"hidesAppWindow(" + partialWindowTitle + ")");
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -1,263 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker.helpers;
|
||||
|
||||
import static android.os.SystemClock.sleep;
|
||||
import static android.system.helpers.OverviewHelper.isRecentsInLauncher;
|
||||
import static android.view.Surface.ROTATION_0;
|
||||
|
||||
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
|
||||
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.RemoteException;
|
||||
import android.support.test.launcherhelper.LauncherStrategyFactory;
|
||||
import android.support.test.uiautomator.By;
|
||||
import android.support.test.uiautomator.BySelector;
|
||||
import android.support.test.uiautomator.Configurator;
|
||||
import android.support.test.uiautomator.UiDevice;
|
||||
import android.support.test.uiautomator.UiObject2;
|
||||
import android.support.test.uiautomator.Until;
|
||||
import android.util.Log;
|
||||
import android.util.Rational;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.server.wm.flicker.WindowUtils;
|
||||
|
||||
/**
|
||||
* Collection of UI Automation helper functions.
|
||||
*/
|
||||
public class AutomationUtils {
|
||||
private static final String SYSTEMUI_PACKAGE = "com.android.systemui";
|
||||
private static final long FIND_TIMEOUT = 10000;
|
||||
private static final long LONG_PRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout() * 2L;
|
||||
private static final String TAG = "FLICKER";
|
||||
|
||||
public static void wakeUpAndGoToHomeScreen() {
|
||||
UiDevice device = UiDevice.getInstance(InstrumentationRegistry
|
||||
.getInstrumentation());
|
||||
try {
|
||||
device.wakeUp();
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
device.pressHome();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets {@link android.app.UiAutomation#waitForIdle(long, long)} global timeout to 0 causing
|
||||
* the {@link android.app.UiAutomation#waitForIdle(long, long)} function to timeout instantly.
|
||||
* This removes some delays when using the UIAutomator library required to create fast UI
|
||||
* transitions.
|
||||
*/
|
||||
public static void setFastWait() {
|
||||
Configurator.getInstance().setWaitForIdleTimeout(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts {@link android.app.UiAutomation#waitForIdle(long, long)} to default behavior.
|
||||
*/
|
||||
public static void setDefaultWait() {
|
||||
Configurator.getInstance().setWaitForIdleTimeout(10000);
|
||||
}
|
||||
|
||||
public static boolean isQuickstepEnabled(UiDevice device) {
|
||||
return device.findObject(By.res(SYSTEMUI_PACKAGE, "recent_apps")) == null;
|
||||
}
|
||||
|
||||
public static void openQuickstep(UiDevice device) {
|
||||
if (isQuickstepEnabled(device)) {
|
||||
int height = device.getDisplayHeight();
|
||||
UiObject2 navBar = device.findObject(By.res(SYSTEMUI_PACKAGE, "navigation_bar_frame"));
|
||||
|
||||
Rect navBarVisibleBounds;
|
||||
|
||||
// TODO(vishnun) investigate why this object cannot be found.
|
||||
if (navBar != null) {
|
||||
navBarVisibleBounds = navBar.getVisibleBounds();
|
||||
} else {
|
||||
Log.e(TAG, "Could not find nav bar, infer location");
|
||||
navBarVisibleBounds = WindowUtils.getNavigationBarPosition(ROTATION_0);
|
||||
}
|
||||
|
||||
// Swipe from nav bar to 2/3rd down the screen.
|
||||
device.swipe(
|
||||
navBarVisibleBounds.centerX(), navBarVisibleBounds.centerY(),
|
||||
navBarVisibleBounds.centerX(), height * 2 / 3,
|
||||
(navBarVisibleBounds.centerY() - height * 2 / 3) / 100); // 100 px/step
|
||||
} else {
|
||||
try {
|
||||
device.pressRecentApps();
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
BySelector RECENTS = By.res(SYSTEMUI_PACKAGE, "recents_view");
|
||||
|
||||
// use a long timeout to wait until recents populated
|
||||
if (device.wait(
|
||||
Until.findObject(isRecentsInLauncher()
|
||||
? getLauncherOverviewSelector(device) : RECENTS),
|
||||
10000) == null) {
|
||||
fail("Recents didn't appear");
|
||||
}
|
||||
device.waitForIdle();
|
||||
}
|
||||
|
||||
public static void clearRecents(UiDevice device) {
|
||||
if (isQuickstepEnabled(device)) {
|
||||
openQuickstep(device);
|
||||
|
||||
for (int i = 0; i < 5; i++) {
|
||||
device.swipe(device.getDisplayWidth() / 2,
|
||||
device.getDisplayHeight() / 2, device.getDisplayWidth(),
|
||||
device.getDisplayHeight() / 2,
|
||||
5);
|
||||
|
||||
BySelector clearAllSelector = By.res("com.google.android.apps.nexuslauncher",
|
||||
"clear_all_button");
|
||||
UiObject2 clearAllButton = device.wait(Until.findObject(clearAllSelector), 100);
|
||||
if (clearAllButton != null) {
|
||||
clearAllButton.click();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static BySelector getLauncherOverviewSelector(UiDevice device) {
|
||||
return By.res(device.getLauncherPackageName(), "overview_panel");
|
||||
}
|
||||
|
||||
private static void longPressRecents(UiDevice device) {
|
||||
BySelector recentsSelector = By.res(SYSTEMUI_PACKAGE, "recent_apps");
|
||||
UiObject2 recentsButton = device.wait(Until.findObject(recentsSelector), FIND_TIMEOUT);
|
||||
assertNotNull("Unable to find recents button", recentsButton);
|
||||
recentsButton.click(LONG_PRESS_TIMEOUT);
|
||||
}
|
||||
|
||||
public static void launchSplitScreen(UiDevice device) {
|
||||
String mLauncherPackage = LauncherStrategyFactory.getInstance(device)
|
||||
.getLauncherStrategy().getSupportedLauncherPackage();
|
||||
|
||||
if (isQuickstepEnabled(device)) {
|
||||
// Quickstep enabled
|
||||
openQuickstep(device);
|
||||
|
||||
BySelector overviewIconSelector = By.res(mLauncherPackage, "icon")
|
||||
.clazz(View.class);
|
||||
UiObject2 overviewIcon = device.wait(Until.findObject(overviewIconSelector),
|
||||
FIND_TIMEOUT);
|
||||
assertNotNull("Unable to find app icon in Overview", overviewIcon);
|
||||
overviewIcon.click();
|
||||
|
||||
BySelector splitscreenButtonSelector = By.text("Split screen");
|
||||
UiObject2 splitscreenButton = device.wait(Until.findObject(splitscreenButtonSelector),
|
||||
FIND_TIMEOUT);
|
||||
assertNotNull("Unable to find Split screen button in Overview", splitscreenButton);
|
||||
splitscreenButton.click();
|
||||
} else {
|
||||
// Classic long press recents
|
||||
longPressRecents(device);
|
||||
}
|
||||
// Wait for animation to complete.
|
||||
sleep(2000);
|
||||
}
|
||||
|
||||
public static void exitSplitScreen(UiDevice device) {
|
||||
if (isQuickstepEnabled(device)) {
|
||||
// Quickstep enabled
|
||||
BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle");
|
||||
UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
|
||||
assertNotNull("Unable to find Split screen divider", divider);
|
||||
|
||||
// Drag the split screen divider to the top of the screen
|
||||
divider.drag(new Point(device.getDisplayWidth() / 2, 0), 400);
|
||||
} else {
|
||||
// Classic long press recents
|
||||
longPressRecents(device);
|
||||
}
|
||||
// Wait for animation to complete.
|
||||
sleep(2000);
|
||||
}
|
||||
|
||||
public static void resizeSplitScreen(UiDevice device, Rational windowHeightRatio) {
|
||||
BySelector dividerSelector = By.res(SYSTEMUI_PACKAGE, "docked_divider_handle");
|
||||
UiObject2 divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
|
||||
assertNotNull("Unable to find Split screen divider", divider);
|
||||
int destHeight =
|
||||
(int) (WindowUtils.getDisplayBounds().height() * windowHeightRatio.floatValue());
|
||||
// Drag the split screen divider to so that the ratio of top window height and bottom
|
||||
// window height is windowHeightRatio
|
||||
device.drag(divider.getVisibleBounds().centerX(), divider.getVisibleBounds().centerY(),
|
||||
device.getDisplayWidth() / 2, destHeight, 10);
|
||||
//divider.drag(new Point(device.getDisplayWidth() / 2, destHeight), 400)
|
||||
divider = device.wait(Until.findObject(dividerSelector), FIND_TIMEOUT);
|
||||
|
||||
// Wait for animation to complete.
|
||||
sleep(2000);
|
||||
}
|
||||
|
||||
public static void closePipWindow(UiDevice device) {
|
||||
UiObject2 pipWindow = device.findObject(
|
||||
By.res(SYSTEMUI_PACKAGE, "background"));
|
||||
pipWindow.click();
|
||||
UiObject2 exitPipObject = device.findObject(
|
||||
By.res(SYSTEMUI_PACKAGE, "dismiss"));
|
||||
exitPipObject.click();
|
||||
// Wait for animation to complete.
|
||||
sleep(2000);
|
||||
}
|
||||
|
||||
public static void expandPipWindow(UiDevice device) {
|
||||
UiObject2 pipWindow = device.findObject(
|
||||
By.res(SYSTEMUI_PACKAGE, "background"));
|
||||
pipWindow.click();
|
||||
pipWindow.click();
|
||||
}
|
||||
|
||||
public static void stopPackage(Context context, String packageName) {
|
||||
runShellCommand("am force-stop " + packageName);
|
||||
int packageUid;
|
||||
try {
|
||||
packageUid = context.getPackageManager().getPackageUid(packageName, /* flags= */0);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return;
|
||||
}
|
||||
while (targetPackageIsRunning(packageUid)) {
|
||||
try {
|
||||
Thread.sleep(100);
|
||||
} catch (InterruptedException e) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean targetPackageIsRunning(int uid) {
|
||||
final String result = runShellCommand(
|
||||
String.format("cmd activity get-uid-state %d", uid));
|
||||
return !result.contains("(NONEXISTENT)");
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker.monitor;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Collects test artifacts during a UI transition.
|
||||
*/
|
||||
public interface ITransitionMonitor {
|
||||
Path OUTPUT_DIR = Paths.get(Environment.getExternalStorageDirectory().toString(), "flicker");
|
||||
|
||||
/**
|
||||
* Starts monitor.
|
||||
*/
|
||||
void start();
|
||||
|
||||
/**
|
||||
* Stops monitor.
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/**
|
||||
* Saves any monitor artifacts to file adding {@code testTag} and {@code iteration}
|
||||
* to the file name.
|
||||
*
|
||||
* @param testTag suffix added to artifact name
|
||||
* @param iteration suffix added to artifact name
|
||||
*
|
||||
* @return Path to saved artifact
|
||||
*/
|
||||
default Path save(String testTag, int iteration) {
|
||||
return save(testTag + "_" + iteration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves any monitor artifacts to file adding {@code testTag} to the file name.
|
||||
*
|
||||
* @param testTag suffix added to artifact name
|
||||
*
|
||||
* @return Path to saved artifact
|
||||
*/
|
||||
default Path save(String testTag) {
|
||||
throw new UnsupportedOperationException("Save not implemented for this monitor");
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker.monitor;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.view.IWindowManager;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
/**
|
||||
* Captures Layers trace from SurfaceFlinger.
|
||||
*/
|
||||
public class LayersTraceMonitor extends TraceMonitor {
|
||||
private IWindowManager mWm = WindowManagerGlobal.getWindowManagerService();
|
||||
|
||||
public LayersTraceMonitor() {
|
||||
this(OUTPUT_DIR.toString());
|
||||
}
|
||||
|
||||
public LayersTraceMonitor(String outputDir) {
|
||||
super(outputDir, "layers_trace.pb");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() throws RemoteException {
|
||||
try {
|
||||
return mWm.isLayerTracing();
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void setEnabled(boolean isEnabled) {
|
||||
try {
|
||||
mWm.setLayerTracing(isEnabled);
|
||||
} catch (RemoteException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker.monitor;
|
||||
|
||||
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
/**
|
||||
* Captures screen contents and saves it as a mp4 video file.
|
||||
*/
|
||||
public class ScreenRecorder implements ITransitionMonitor {
|
||||
@VisibleForTesting
|
||||
public static final Path DEFAULT_OUTPUT_PATH = OUTPUT_DIR.resolve("transition.mp4");
|
||||
private static final String TAG = "FLICKER";
|
||||
private Thread recorderThread;
|
||||
|
||||
@VisibleForTesting
|
||||
public static Path getPath(String testTag) {
|
||||
return OUTPUT_DIR.resolve(testTag + ".mp4");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
OUTPUT_DIR.toFile().mkdirs();
|
||||
String command = "screenrecord " + DEFAULT_OUTPUT_PATH;
|
||||
recorderThread = new Thread(() -> {
|
||||
try {
|
||||
Runtime.getRuntime().exec(command);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Error executing " + command, e);
|
||||
}
|
||||
});
|
||||
recorderThread.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
runShellCommand("killall -s 2 screenrecord");
|
||||
try {
|
||||
recorderThread.join();
|
||||
} catch (InterruptedException e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Path save(String testTag) {
|
||||
try {
|
||||
Path targetPath = Files.move(DEFAULT_OUTPUT_PATH, getPath(testTag),
|
||||
REPLACE_EXISTING);
|
||||
Log.i(TAG, "Video saved to " + targetPath.toString());
|
||||
return targetPath;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker.monitor;
|
||||
|
||||
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
|
||||
|
||||
import android.os.RemoteException;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* Base class for monitors containing common logic to read the trace
|
||||
* as a byte array and save the trace to another location.
|
||||
*/
|
||||
public abstract class TraceMonitor implements ITransitionMonitor {
|
||||
public static final String TAG = "FLICKER";
|
||||
private static final String TRACE_DIR = "/data/misc/wmtrace/";
|
||||
|
||||
private Path mOutputDir;
|
||||
public String mTraceFileName;
|
||||
|
||||
public abstract boolean isEnabled() throws RemoteException;
|
||||
|
||||
public TraceMonitor(String outputDir, String traceFileName) {
|
||||
mOutputDir = Paths.get(outputDir);
|
||||
mTraceFileName = traceFileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves trace file to the external storage directory suffixing the name with the testtag
|
||||
* and iteration.
|
||||
*
|
||||
* Moves the trace file from the default location via a shell command since the test app
|
||||
* does not have security privileges to access /data/misc/wmtrace.
|
||||
*
|
||||
* @param testTag suffix added to trace name used to identify trace
|
||||
*
|
||||
* @return Path to saved trace file
|
||||
*/
|
||||
@Override
|
||||
public Path save(String testTag) {
|
||||
OUTPUT_DIR.toFile().mkdirs();
|
||||
Path traceFileCopy = getOutputTraceFilePath(testTag);
|
||||
|
||||
// Read the input stream fully.
|
||||
String copyCommand = String.format(Locale.getDefault(), "mv %s%s %s", TRACE_DIR,
|
||||
mTraceFileName, traceFileCopy.toString());
|
||||
runShellCommand(copyCommand);
|
||||
return traceFileCopy;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public Path getOutputTraceFilePath(String testTag) {
|
||||
return mOutputDir.resolve(mTraceFileName + "_" + testTag);
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker.monitor;
|
||||
|
||||
import static android.view.FrameStats.UNDEFINED_TIME_NANO;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.util.Log;
|
||||
import android.view.FrameStats;
|
||||
|
||||
/**
|
||||
* Monitors {@link android.view.WindowAnimationFrameStats} to detect janky frames.
|
||||
*
|
||||
* Adapted from {@link androidx.test.jank.internal.WindowAnimationFrameStatsMonitorImpl}
|
||||
* using the same threshold to determine jank.
|
||||
*/
|
||||
public class WindowAnimationFrameStatsMonitor implements ITransitionMonitor {
|
||||
|
||||
private static final String TAG = "FLICKER";
|
||||
// Maximum normalized error in frame duration before the frame is considered janky
|
||||
private static final double MAX_ERROR = 0.5f;
|
||||
// Maximum normalized frame duration before the frame is considered a pause
|
||||
private static final double PAUSE_THRESHOLD = 15.0f;
|
||||
private Instrumentation mInstrumentation;
|
||||
private FrameStats stats;
|
||||
private int numJankyFrames;
|
||||
private long mLongestFrameNano = 0L;
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a WindowAnimationFrameStatsMonitor instance.
|
||||
*/
|
||||
public WindowAnimationFrameStatsMonitor(Instrumentation instrumentation) {
|
||||
mInstrumentation = instrumentation;
|
||||
}
|
||||
|
||||
private void analyze() {
|
||||
int frameCount = stats.getFrameCount();
|
||||
long refreshPeriodNano = stats.getRefreshPeriodNano();
|
||||
|
||||
// Skip first frame
|
||||
for (int i = 2; i < frameCount; i++) {
|
||||
// Handle frames that have not been presented.
|
||||
if (stats.getFramePresentedTimeNano(i) == UNDEFINED_TIME_NANO) {
|
||||
// The animation must not have completed. Warn and break out of the loop.
|
||||
Log.w(TAG, "Skipping fenced frame.");
|
||||
break;
|
||||
}
|
||||
long frameDurationNano = stats.getFramePresentedTimeNano(i) -
|
||||
stats.getFramePresentedTimeNano(i - 1);
|
||||
double normalized = (double) frameDurationNano / refreshPeriodNano;
|
||||
if (normalized < PAUSE_THRESHOLD) {
|
||||
if (normalized > 1.0f + MAX_ERROR) {
|
||||
numJankyFrames++;
|
||||
}
|
||||
mLongestFrameNano = Math.max(mLongestFrameNano, frameDurationNano);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
// Clear out any previous data
|
||||
numJankyFrames = 0;
|
||||
mLongestFrameNano = 0;
|
||||
mInstrumentation.getUiAutomation().clearWindowAnimationFrameStats();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
stats = mInstrumentation.getUiAutomation().getWindowAnimationFrameStats();
|
||||
analyze();
|
||||
}
|
||||
|
||||
public boolean jankyFramesDetected() {
|
||||
return stats.getFrameCount() > 0 && numJankyFrames > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return stats.toString() +
|
||||
" RefreshPeriodNano:" + stats.getRefreshPeriodNano() +
|
||||
" NumJankyFrames:" + numJankyFrames +
|
||||
" LongestFrameNano:" + mLongestFrameNano;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker.monitor;
|
||||
|
||||
import android.os.RemoteException;
|
||||
import android.view.IWindowManager;
|
||||
import android.view.WindowManagerGlobal;
|
||||
|
||||
/**
|
||||
* Captures WindowManager trace from WindowManager.
|
||||
*/
|
||||
public class WindowManagerTraceMonitor extends TraceMonitor {
|
||||
private IWindowManager mWm = WindowManagerGlobal.getWindowManagerService();
|
||||
|
||||
public WindowManagerTraceMonitor() {
|
||||
this(OUTPUT_DIR.toString());
|
||||
}
|
||||
|
||||
public WindowManagerTraceMonitor(String outputDir) {
|
||||
super(outputDir, "wm_trace.pb");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void start() {
|
||||
try {
|
||||
mWm.startWindowTrace();
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("Could not start trace", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stop() {
|
||||
try {
|
||||
mWm.stopWindowTrace();
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException("Could not stop trace", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEnabled() throws RemoteException{
|
||||
return mWm.isWindowTraceEnabled();
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
//
|
||||
// Copyright (C) 2018 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.
|
||||
//
|
||||
|
||||
android_test {
|
||||
name: "FlickerLibTest",
|
||||
// sign this with platform cert, so this test is allowed to call private platform apis
|
||||
certificate: "platform",
|
||||
platform_apis: true,
|
||||
test_suites: ["tests"],
|
||||
srcs: ["src/**/*.java"],
|
||||
libs: ["android.test.runner"],
|
||||
static_libs: [
|
||||
"androidx.test.rules",
|
||||
"platform-test-annotations",
|
||||
"truth-prebuilt",
|
||||
"platformprotosnano",
|
||||
"layersprotosnano",
|
||||
"flickerlib",
|
||||
],
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||
-->
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.android.server.wm.flicker">
|
||||
|
||||
<uses-sdk android:minSdkVersion="27" android:targetSdkVersion="27"/>
|
||||
<!-- Read and write traces from external storage -->
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<!-- Capture screen contents -->
|
||||
<uses-permission android:name="android.permission.ACCESS_SURFACE_FLINGER" />
|
||||
<!-- Run layers trace -->
|
||||
<uses-permission android:name="android.permission.HARDWARE_TEST"/>
|
||||
<application android:label="FlickerLibTest">
|
||||
<uses-library android:name="android.test.runner"/>
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
|
||||
android:targetPackage="com.android.server.wm.flicker"
|
||||
android:label="WindowManager Flicker Lib Test">
|
||||
</instrumentation>
|
||||
|
||||
</manifest>
|
||||
@@ -1,20 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
* Copyright 2018 Google Inc. All Rights Reserved.
|
||||
-->
|
||||
<configuration description="Config for WindowManager Flicker Tests">
|
||||
<target_preparer class="com.google.android.tradefed.targetprep.GoogleDeviceSetup">
|
||||
<!-- keeps the screen on during tests -->
|
||||
<option name="screen-always-on" value="on" />
|
||||
<!-- prevents the phone from restarting -->
|
||||
<option name="force-skip-system-props" value="true" />
|
||||
</target_preparer>
|
||||
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
|
||||
<option name="cleanup-apks" value="true"/>
|
||||
<option name="test-file-name" value="FlickerLibTest.apk"/>
|
||||
</target_preparer>
|
||||
<test class="com.android.tradefed.testtype.AndroidJUnitTest">
|
||||
<option name="package" value="com.android.server.wm.flicker"/>
|
||||
<option name="hidden-api-checks" value="false" />
|
||||
</test>
|
||||
</configuration>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,181 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.android.server.wm.flicker.Assertions.Result;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Contains {@link AssertionsChecker} tests.
|
||||
* To run this test: {@code atest FlickerLibTest:AssertionsCheckerTest}
|
||||
*/
|
||||
public class AssertionsCheckerTest {
|
||||
|
||||
/**
|
||||
* Returns a list of SimpleEntry objects with {@code data} and incremental timestamps starting
|
||||
* at 0.
|
||||
*/
|
||||
private static List<SimpleEntry> getTestEntries(int... data) {
|
||||
List<SimpleEntry> entries = new ArrayList<>();
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
entries.add(new SimpleEntry(i, data[i]));
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCheckAllEntries() {
|
||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
||||
checker.add(SimpleEntry::isData42, "isData42");
|
||||
|
||||
List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
|
||||
|
||||
assertThat(failures).hasSize(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCheckFirstEntry() {
|
||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
||||
checker.checkFirstEntry();
|
||||
checker.add(SimpleEntry::isData42, "isData42");
|
||||
|
||||
List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
|
||||
|
||||
assertThat(failures).hasSize(1);
|
||||
assertThat(failures.get(0).timestamp).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCheckLastEntry() {
|
||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
||||
checker.checkLastEntry();
|
||||
checker.add(SimpleEntry::isData42, "isData42");
|
||||
|
||||
List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
|
||||
|
||||
assertThat(failures).hasSize(1);
|
||||
assertThat(failures.get(0).timestamp).isEqualTo(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCheckRangeOfEntries() {
|
||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
||||
checker.filterByRange(1, 2);
|
||||
checker.add(SimpleEntry::isData42, "isData42");
|
||||
|
||||
List<Result> failures = checker.test(getTestEntries(1, 42, 42, 1, 1));
|
||||
|
||||
assertThat(failures).hasSize(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyRangePasses() {
|
||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
||||
checker.filterByRange(9, 10);
|
||||
checker.add(SimpleEntry::isData42, "isData42");
|
||||
|
||||
List<Result> failures = checker.test(getTestEntries(1, 1, 1, 1, 1));
|
||||
|
||||
assertThat(failures).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCheckChangingAssertions() {
|
||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
||||
checker.add(SimpleEntry::isData42, "isData42");
|
||||
checker.add(SimpleEntry::isData0, "isData0");
|
||||
checker.checkChangingAssertions();
|
||||
|
||||
List<Result> failures = checker.test(getTestEntries(42, 0, 0, 0, 0));
|
||||
|
||||
assertThat(failures).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCheckChangingAssertions_withNoAssertions() {
|
||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
||||
checker.checkChangingAssertions();
|
||||
|
||||
List<Result> failures = checker.test(getTestEntries(42, 0, 0, 0, 0));
|
||||
|
||||
assertThat(failures).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCheckChangingAssertions_withSingleAssertion() {
|
||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
||||
checker.add(SimpleEntry::isData42, "isData42");
|
||||
checker.checkChangingAssertions();
|
||||
|
||||
List<Result> failures = checker.test(getTestEntries(42, 42, 42, 42, 42));
|
||||
|
||||
assertThat(failures).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canFailCheckChangingAssertions_ifStartingAssertionFails() {
|
||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
||||
checker.add(SimpleEntry::isData42, "isData42");
|
||||
checker.add(SimpleEntry::isData0, "isData0");
|
||||
checker.checkChangingAssertions();
|
||||
|
||||
List<Result> failures = checker.test(getTestEntries(0, 0, 0, 0, 0));
|
||||
|
||||
assertThat(failures).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canFailCheckChangingAssertions_ifStartingAssertionAlwaysPasses() {
|
||||
AssertionsChecker<SimpleEntry> checker = new AssertionsChecker<>();
|
||||
checker.add(SimpleEntry::isData42, "isData42");
|
||||
checker.add(SimpleEntry::isData0, "isData0");
|
||||
checker.checkChangingAssertions();
|
||||
|
||||
List<Result> failures = checker.test(getTestEntries(0, 0, 0, 0, 0));
|
||||
|
||||
assertThat(failures).hasSize(1);
|
||||
}
|
||||
|
||||
static class SimpleEntry implements ITraceEntry {
|
||||
long timestamp;
|
||||
int data;
|
||||
|
||||
SimpleEntry(long timestamp, int data) {
|
||||
this.timestamp = timestamp;
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
|
||||
Result isData42() {
|
||||
return new Result(this.data == 42, this.timestamp, "is42", "");
|
||||
}
|
||||
|
||||
Result isData0() {
|
||||
return new Result(this.data == 0, this.timestamp, "is42", "");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.android.server.wm.flicker.Assertions.Result;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Contains {@link Assertions} tests.
|
||||
* To run this test: {@code atest FlickerLibTest:AssertionsTest}
|
||||
*/
|
||||
public class AssertionsTest {
|
||||
@Test
|
||||
public void traceEntryAssertionCanNegateResult() {
|
||||
Assertions.TraceAssertion<Integer> assertNumEquals42 =
|
||||
getIntegerTraceEntryAssertion();
|
||||
|
||||
assertThat(assertNumEquals42.apply(1).success).isFalse();
|
||||
assertThat(assertNumEquals42.negate().apply(1).success).isTrue();
|
||||
|
||||
assertThat(assertNumEquals42.apply(42).success).isTrue();
|
||||
assertThat(assertNumEquals42.negate().apply(42).success).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resultCanBeNegated() {
|
||||
String reason = "Everything is fine!";
|
||||
Result result = new Result(true, 0, "TestAssert", reason);
|
||||
Result negatedResult = result.negate();
|
||||
assertThat(negatedResult.success).isFalse();
|
||||
assertThat(negatedResult.reason).isEqualTo(reason);
|
||||
assertThat(negatedResult.assertionName).isEqualTo("!TestAssert");
|
||||
}
|
||||
|
||||
private Assertions.TraceAssertion<Integer> getIntegerTraceEntryAssertion() {
|
||||
return (num) -> {
|
||||
if (num == 42) {
|
||||
return new Result(true, "Num equals 42");
|
||||
}
|
||||
return new Result(false, "Num doesn't equal 42, actual:" + num);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import static com.android.server.wm.flicker.LayersTraceSubject.assertThat;
|
||||
import static com.android.server.wm.flicker.TestFileUtils.readTestFile;
|
||||
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.graphics.Rect;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* Contains {@link LayersTraceSubject} tests.
|
||||
* To run this test: {@code atest FlickerLibTest:LayersTraceSubjectTest}
|
||||
*/
|
||||
public class LayersTraceSubjectTest {
|
||||
private static final Rect displayRect = new Rect(0, 0, 1440, 2880);
|
||||
|
||||
private static LayersTrace readLayerTraceFromFile(String relativePath) {
|
||||
try {
|
||||
return LayersTrace.parseFrom(readTestFile(relativePath), Paths.get(relativePath));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanDetectEmptyRegionFromLayerTrace() {
|
||||
LayersTrace layersTraceEntries = readLayerTraceFromFile("layers_trace_emptyregion.pb");
|
||||
try {
|
||||
assertThat(layersTraceEntries).coversRegion(displayRect).forAllEntries();
|
||||
fail("Assertion passed");
|
||||
} catch (AssertionError e) {
|
||||
assertWithMessage("Contains path to trace")
|
||||
.that(e.getMessage()).contains("layers_trace_emptyregion.pb");
|
||||
assertWithMessage("Contains timestamp")
|
||||
.that(e.getMessage()).contains("0h38m28s8ms");
|
||||
assertWithMessage("Contains assertion function")
|
||||
.that(e.getMessage()).contains("coversRegion");
|
||||
assertWithMessage("Contains debug info")
|
||||
.that(e.getMessage()).contains("Region to test: " + displayRect);
|
||||
assertWithMessage("Contains debug info")
|
||||
.that(e.getMessage()).contains("first empty point: 0, 99");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanDetectIncorrectVisibilityFromLayerTrace() {
|
||||
LayersTrace layersTraceEntries = readLayerTraceFromFile(
|
||||
"layers_trace_invalid_layer_visibility.pb");
|
||||
try {
|
||||
assertThat(layersTraceEntries).showsLayer("com.android.server.wm.flicker.testapp")
|
||||
.then().hidesLayer("com.android.server.wm.flicker.testapp").forAllEntries();
|
||||
fail("Assertion passed");
|
||||
} catch (AssertionError e) {
|
||||
assertWithMessage("Contains path to trace")
|
||||
.that(e.getMessage()).contains("layers_trace_invalid_layer_visibility.pb");
|
||||
assertWithMessage("Contains timestamp")
|
||||
.that(e.getMessage()).contains("70h13m14s303ms");
|
||||
assertWithMessage("Contains assertion function")
|
||||
.that(e.getMessage()).contains("!isVisible");
|
||||
assertWithMessage("Contains debug info")
|
||||
.that(e.getMessage()).contains(
|
||||
"com.android.server.wm.flicker.testapp/com.android.server.wm.flicker.testapp"
|
||||
+ ".SimpleActivity#0 is visible");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import static com.android.server.wm.flicker.TestFileUtils.readTestFile;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Contains {@link LayersTrace} tests.
|
||||
* To run this test: {@code atest FlickerLibTest:LayersTraceTest}
|
||||
*/
|
||||
public class LayersTraceTest {
|
||||
private static LayersTrace readLayerTraceFromFile(String relativePath) {
|
||||
try {
|
||||
return LayersTrace.parseFrom(readTestFile(relativePath));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static Rect getDisplayBounds() {
|
||||
Point display = new Point();
|
||||
WindowManager wm =
|
||||
(WindowManager) InstrumentationRegistry.getContext().getSystemService(
|
||||
Context.WINDOW_SERVICE);
|
||||
wm.getDefaultDisplay().getRealSize(display);
|
||||
return new Rect(0, 0, display.x, display.y);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canParseAllLayers() {
|
||||
LayersTrace trace = readLayerTraceFromFile(
|
||||
"layers_trace_emptyregion.pb");
|
||||
assertThat(trace.getEntries()).isNotEmpty();
|
||||
assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L);
|
||||
assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp())
|
||||
.isEqualTo(2308521813510L);
|
||||
List<LayersTrace.Layer> flattenedLayers = trace.getEntries().get(0).asFlattenedLayers();
|
||||
String msg = "Layers:\n" + flattenedLayers.stream().map(layer -> layer.mProto.name)
|
||||
.collect(Collectors.joining("\n\t"));
|
||||
assertWithMessage(msg).that(flattenedLayers).hasSize(47);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canParseVisibleLayers() {
|
||||
LayersTrace trace = readLayerTraceFromFile(
|
||||
"layers_trace_emptyregion.pb");
|
||||
assertThat(trace.getEntries()).isNotEmpty();
|
||||
assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L);
|
||||
assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp())
|
||||
.isEqualTo(2308521813510L);
|
||||
List<LayersTrace.Layer> flattenedLayers = trace.getEntries().get(0).asFlattenedLayers();
|
||||
List<LayersTrace.Layer> visibleLayers = flattenedLayers.stream()
|
||||
.filter(layer -> layer.isVisible() && !layer.isHiddenByParent())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
String msg = "Visible Layers:\n" + visibleLayers.stream()
|
||||
.map(layer -> layer.mProto.name)
|
||||
.collect(Collectors.joining("\n\t"));
|
||||
|
||||
assertWithMessage(msg).that(visibleLayers).hasSize(9);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canParseLayerHierarchy() {
|
||||
LayersTrace trace = readLayerTraceFromFile(
|
||||
"layers_trace_emptyregion.pb");
|
||||
assertThat(trace.getEntries()).isNotEmpty();
|
||||
assertThat(trace.getEntries().get(0).getTimestamp()).isEqualTo(2307984557311L);
|
||||
assertThat(trace.getEntries().get(trace.getEntries().size() - 1).getTimestamp())
|
||||
.isEqualTo(2308521813510L);
|
||||
List<LayersTrace.Layer> layers = trace.getEntries().get(0).getRootLayers();
|
||||
assertThat(layers).hasSize(2);
|
||||
assertThat(layers.get(0).mChildren).hasSize(layers.get(0).mProto.children.length);
|
||||
assertThat(layers.get(1).mChildren).hasSize(layers.get(1).mProto.children.length);
|
||||
}
|
||||
|
||||
// b/76099859
|
||||
@Test
|
||||
public void canDetectOrphanLayers() {
|
||||
try {
|
||||
readLayerTraceFromFile(
|
||||
"layers_trace_orphanlayers.pb");
|
||||
fail("Failed to detect orphaned layers.");
|
||||
} catch (RuntimeException exception) {
|
||||
assertThat(exception.getMessage()).contains(
|
||||
"Failed to parse layers trace. Found orphan layers "
|
||||
+ "with parent layer id:1006 : 49");
|
||||
}
|
||||
}
|
||||
|
||||
// b/75276931
|
||||
@Test
|
||||
public void canDetectUncoveredRegion() {
|
||||
LayersTrace trace = readLayerTraceFromFile(
|
||||
"layers_trace_emptyregion.pb");
|
||||
LayersTrace.Entry entry = trace.getEntry(2308008331271L);
|
||||
|
||||
Assertions.Result result = entry.coversRegion(getDisplayBounds());
|
||||
|
||||
assertThat(result.failed()).isTrue();
|
||||
assertThat(result.reason).contains("Region to test: Rect(0, 0 - 1440, 2880)");
|
||||
assertThat(result.reason).contains("first empty point: 0, 99");
|
||||
assertThat(result.reason).contains("visible regions:");
|
||||
assertWithMessage("Reason contains list of visible regions")
|
||||
.that(result.reason).contains("StatusBar#0Rect(0, 0 - 1440, 98");
|
||||
}
|
||||
|
||||
// Visible region tests
|
||||
@Test
|
||||
public void canTestLayerVisibleRegion_layerDoesNotExist() {
|
||||
LayersTrace trace = readLayerTraceFromFile(
|
||||
"layers_trace_emptyregion.pb");
|
||||
LayersTrace.Entry entry = trace.getEntry(2308008331271L);
|
||||
|
||||
final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1);
|
||||
Assertions.Result result = entry.hasVisibleRegion("ImaginaryLayer",
|
||||
expectedVisibleRegion);
|
||||
|
||||
assertThat(result.failed()).isTrue();
|
||||
assertThat(result.reason).contains("Could not find ImaginaryLayer");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canTestLayerVisibleRegion_layerDoesNotHaveExpectedVisibleRegion() {
|
||||
LayersTrace trace = readLayerTraceFromFile(
|
||||
"layers_trace_emptyregion.pb");
|
||||
LayersTrace.Entry entry = trace.getEntry(2307993020072L);
|
||||
|
||||
final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1);
|
||||
Assertions.Result result = entry.hasVisibleRegion("NexusLauncherActivity#2",
|
||||
expectedVisibleRegion);
|
||||
|
||||
assertThat(result.failed()).isTrue();
|
||||
assertThat(result.reason).contains(
|
||||
"Layer com.google.android.apps.nexuslauncher/com.google.android.apps"
|
||||
+ ".nexuslauncher.NexusLauncherActivity#2 is invisible: activeBuffer=null"
|
||||
+ " type != ColorLayer flags=1 (FLAG_HIDDEN set) visible region is empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canTestLayerVisibleRegion_layerIsHiddenByParent() {
|
||||
LayersTrace trace = readLayerTraceFromFile(
|
||||
"layers_trace_emptyregion.pb");
|
||||
LayersTrace.Entry entry = trace.getEntry(2308455948035L);
|
||||
|
||||
final Rect expectedVisibleRegion = new Rect(0, 0, 1, 1);
|
||||
Assertions.Result result = entry.hasVisibleRegion(
|
||||
"SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main",
|
||||
expectedVisibleRegion);
|
||||
|
||||
assertThat(result.failed()).isTrue();
|
||||
assertThat(result.reason).contains(
|
||||
"Layer SurfaceView - com.android.chrome/com.google.android.apps.chrome.Main#0 is "
|
||||
+ "hidden by parent: com.android.chrome/com.google.android.apps.chrome"
|
||||
+ ".Main#0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canTestLayerVisibleRegion_incorrectRegionSize() {
|
||||
LayersTrace trace = readLayerTraceFromFile(
|
||||
"layers_trace_emptyregion.pb");
|
||||
LayersTrace.Entry entry = trace.getEntry(2308008331271L);
|
||||
|
||||
final Rect expectedVisibleRegion = new Rect(0, 0, 1440, 99);
|
||||
Assertions.Result result = entry.hasVisibleRegion(
|
||||
"StatusBar",
|
||||
expectedVisibleRegion);
|
||||
|
||||
assertThat(result.failed()).isTrue();
|
||||
assertThat(result.reason).contains("StatusBar#0 has visible "
|
||||
+ "region:Rect(0, 0 - 1440, 98) expected:Rect(0, 0 - 1440, 99)");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canTestLayerVisibleRegion() {
|
||||
LayersTrace trace = readLayerTraceFromFile(
|
||||
"layers_trace_emptyregion.pb");
|
||||
LayersTrace.Entry entry = trace.getEntry(2308008331271L);
|
||||
|
||||
final Rect expectedVisibleRegion = new Rect(0, 0, 1440, 98);
|
||||
Assertions.Result result = entry.hasVisibleRegion("StatusBar", expectedVisibleRegion);
|
||||
|
||||
assertThat(result.passed()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canTestLayerVisibleRegion_layerIsNotVisible() {
|
||||
LayersTrace trace = readLayerTraceFromFile(
|
||||
"layers_trace_invalid_layer_visibility.pb");
|
||||
LayersTrace.Entry entry = trace.getEntry(252794268378458L);
|
||||
|
||||
Assertions.Result result = entry.isVisible("com.android.server.wm.flicker.testapp");
|
||||
assertThat(result.failed()).isTrue();
|
||||
assertThat(result.reason).contains(
|
||||
"Layer com.android.server.wm.flicker.testapp/com.android.server.wm.flicker"
|
||||
+ ".testapp.SimpleActivity#0 is invisible: type != ColorLayer visible "
|
||||
+ "region is empty");
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Helper functions for test file resources.
|
||||
*/
|
||||
class TestFileUtils {
|
||||
static byte[] readTestFile(String relativePath) throws Exception {
|
||||
Context context = InstrumentationRegistry.getContext();
|
||||
InputStream in = context.getResources().getAssets().open("testdata/" + relativePath);
|
||||
return ByteStreams.toByteArray(in);
|
||||
}
|
||||
}
|
||||
@@ -1,258 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.inOrder;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions;
|
||||
|
||||
import android.os.Environment;
|
||||
|
||||
import com.android.server.wm.flicker.TransitionRunner.TransitionBuilder;
|
||||
import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
|
||||
import com.android.server.wm.flicker.monitor.LayersTraceMonitor;
|
||||
import com.android.server.wm.flicker.monitor.ScreenRecorder;
|
||||
import com.android.server.wm.flicker.monitor.WindowAnimationFrameStatsMonitor;
|
||||
import com.android.server.wm.flicker.monitor.WindowManagerTraceMonitor;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.InOrder;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Contains {@link TransitionRunner} tests.
|
||||
* {@code atest FlickerLibTest:TransitionRunnerTest}
|
||||
*/
|
||||
public class TransitionRunnerTest {
|
||||
@Mock
|
||||
private SimpleUiTransitions mTransitionsMock;
|
||||
@Mock
|
||||
private ScreenRecorder mScreenRecorderMock;
|
||||
@Mock
|
||||
private WindowManagerTraceMonitor mWindowManagerTraceMonitorMock;
|
||||
@Mock
|
||||
private LayersTraceMonitor mLayersTraceMonitorMock;
|
||||
@Mock
|
||||
private WindowAnimationFrameStatsMonitor mWindowAnimationFrameStatsMonitor;
|
||||
@InjectMocks
|
||||
private TransitionBuilder mTransitionBuilder;
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
MockitoAnnotations.initMocks(this);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void transitionsRunInOrder() {
|
||||
TransitionRunner.newBuilder()
|
||||
.runBeforeAll(mTransitionsMock::turnOnDevice)
|
||||
.runBefore(mTransitionsMock::openApp)
|
||||
.run(mTransitionsMock::performMagic)
|
||||
.runAfter(mTransitionsMock::closeApp)
|
||||
.runAfterAll(mTransitionsMock::cleanUpTracks)
|
||||
.skipLayersTrace()
|
||||
.skipWindowManagerTrace()
|
||||
.build()
|
||||
.run();
|
||||
|
||||
InOrder orderVerifier = inOrder(mTransitionsMock);
|
||||
orderVerifier.verify(mTransitionsMock).turnOnDevice();
|
||||
orderVerifier.verify(mTransitionsMock).openApp();
|
||||
orderVerifier.verify(mTransitionsMock).performMagic();
|
||||
orderVerifier.verify(mTransitionsMock).closeApp();
|
||||
orderVerifier.verify(mTransitionsMock).cleanUpTracks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCombineTransitions() {
|
||||
TransitionRunner.newBuilder()
|
||||
.runBeforeAll(mTransitionsMock::turnOnDevice)
|
||||
.runBeforeAll(mTransitionsMock::turnOnDevice)
|
||||
.runBefore(mTransitionsMock::openApp)
|
||||
.runBefore(mTransitionsMock::openApp)
|
||||
.run(mTransitionsMock::performMagic)
|
||||
.run(mTransitionsMock::performMagic)
|
||||
.runAfter(mTransitionsMock::closeApp)
|
||||
.runAfter(mTransitionsMock::closeApp)
|
||||
.runAfterAll(mTransitionsMock::cleanUpTracks)
|
||||
.runAfterAll(mTransitionsMock::cleanUpTracks)
|
||||
.skipLayersTrace()
|
||||
.skipWindowManagerTrace()
|
||||
.build()
|
||||
.run();
|
||||
|
||||
final int wantedNumberOfInvocations = 2;
|
||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).turnOnDevice();
|
||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).openApp();
|
||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).performMagic();
|
||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).closeApp();
|
||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).cleanUpTracks();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyTransitionPasses() {
|
||||
List<TransitionResult> results = TransitionRunner.newBuilder()
|
||||
.skipLayersTrace()
|
||||
.skipWindowManagerTrace()
|
||||
.build()
|
||||
.run()
|
||||
.getResults();
|
||||
assertThat(results).hasSize(1);
|
||||
assertThat(results.get(0).layersTraceExists()).isFalse();
|
||||
assertThat(results.get(0).windowManagerTraceExists()).isFalse();
|
||||
assertThat(results.get(0).screenCaptureVideoExists()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canRepeatTransitions() {
|
||||
final int wantedNumberOfInvocations = 10;
|
||||
TransitionRunner.newBuilder()
|
||||
.runBeforeAll(mTransitionsMock::turnOnDevice)
|
||||
.runBefore(mTransitionsMock::openApp)
|
||||
.run(mTransitionsMock::performMagic)
|
||||
.runAfter(mTransitionsMock::closeApp)
|
||||
.runAfterAll(mTransitionsMock::cleanUpTracks)
|
||||
.repeat(wantedNumberOfInvocations)
|
||||
.skipLayersTrace()
|
||||
.skipWindowManagerTrace()
|
||||
.build()
|
||||
.run();
|
||||
verify(mTransitionsMock).turnOnDevice();
|
||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).openApp();
|
||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).performMagic();
|
||||
verify(mTransitionsMock, times(wantedNumberOfInvocations)).closeApp();
|
||||
verify(mTransitionsMock).cleanUpTracks();
|
||||
}
|
||||
|
||||
private void emptyTask() {
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCaptureWindowManagerTrace() {
|
||||
mTransitionBuilder
|
||||
.run(this::emptyTask)
|
||||
.includeJankyRuns()
|
||||
.skipLayersTrace()
|
||||
.withTag("mCaptureWmTraceTransitionRunner")
|
||||
.build().run();
|
||||
InOrder orderVerifier = inOrder(mWindowManagerTraceMonitorMock);
|
||||
orderVerifier.verify(mWindowManagerTraceMonitorMock).start();
|
||||
orderVerifier.verify(mWindowManagerTraceMonitorMock).stop();
|
||||
orderVerifier.verify(mWindowManagerTraceMonitorMock)
|
||||
.save("mCaptureWmTraceTransitionRunner", 0);
|
||||
verifyNoMoreInteractions(mWindowManagerTraceMonitorMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCaptureLayersTrace() {
|
||||
mTransitionBuilder
|
||||
.run(this::emptyTask)
|
||||
.includeJankyRuns()
|
||||
.skipWindowManagerTrace()
|
||||
.withTag("mCaptureLayersTraceTransitionRunner")
|
||||
.build().run();
|
||||
InOrder orderVerifier = inOrder(mLayersTraceMonitorMock);
|
||||
orderVerifier.verify(mLayersTraceMonitorMock).start();
|
||||
orderVerifier.verify(mLayersTraceMonitorMock).stop();
|
||||
orderVerifier.verify(mLayersTraceMonitorMock)
|
||||
.save("mCaptureLayersTraceTransitionRunner", 0);
|
||||
verifyNoMoreInteractions(mLayersTraceMonitorMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canRecordEachRun() throws IOException {
|
||||
mTransitionBuilder
|
||||
.run(this::emptyTask)
|
||||
.withTag("mRecordEachRun")
|
||||
.recordEachRun()
|
||||
.includeJankyRuns()
|
||||
.skipLayersTrace()
|
||||
.skipWindowManagerTrace()
|
||||
.repeat(2)
|
||||
.build().run();
|
||||
InOrder orderVerifier = inOrder(mScreenRecorderMock);
|
||||
orderVerifier.verify(mScreenRecorderMock).start();
|
||||
orderVerifier.verify(mScreenRecorderMock).stop();
|
||||
orderVerifier.verify(mScreenRecorderMock).save("mRecordEachRun", 0);
|
||||
orderVerifier.verify(mScreenRecorderMock).start();
|
||||
orderVerifier.verify(mScreenRecorderMock).stop();
|
||||
orderVerifier.verify(mScreenRecorderMock).save("mRecordEachRun", 1);
|
||||
verifyNoMoreInteractions(mScreenRecorderMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canRecordAllRuns() throws IOException {
|
||||
doReturn(Paths.get(Environment.getExternalStorageDirectory().getAbsolutePath(),
|
||||
"mRecordAllRuns.mp4")).when(mScreenRecorderMock).save("mRecordAllRuns");
|
||||
mTransitionBuilder
|
||||
.run(this::emptyTask)
|
||||
.recordAllRuns()
|
||||
.includeJankyRuns()
|
||||
.skipLayersTrace()
|
||||
.skipWindowManagerTrace()
|
||||
.withTag("mRecordAllRuns")
|
||||
.repeat(2)
|
||||
.build().run();
|
||||
InOrder orderVerifier = inOrder(mScreenRecorderMock);
|
||||
orderVerifier.verify(mScreenRecorderMock).start();
|
||||
orderVerifier.verify(mScreenRecorderMock).stop();
|
||||
orderVerifier.verify(mScreenRecorderMock).save("mRecordAllRuns");
|
||||
verifyNoMoreInteractions(mScreenRecorderMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canSkipJankyRuns() {
|
||||
doReturn(false).doReturn(true).doReturn(false)
|
||||
.when(mWindowAnimationFrameStatsMonitor).jankyFramesDetected();
|
||||
List<TransitionResult> results = mTransitionBuilder
|
||||
.run(this::emptyTask)
|
||||
.skipLayersTrace()
|
||||
.skipWindowManagerTrace()
|
||||
.repeat(3)
|
||||
.build().run().getResults();
|
||||
assertThat(results).hasSize(2);
|
||||
}
|
||||
|
||||
public static class SimpleUiTransitions {
|
||||
public void turnOnDevice() {
|
||||
}
|
||||
|
||||
public void openApp() {
|
||||
}
|
||||
|
||||
public void performMagic() {
|
||||
}
|
||||
|
||||
public void closeApp() {
|
||||
}
|
||||
|
||||
public void cleanUpTracks() {
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import static com.android.server.wm.flicker.TestFileUtils.readTestFile;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.android.server.wm.flicker.Assertions.Result;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Contains {@link WindowManagerTrace} tests.
|
||||
* To run this test: {@code atest FlickerLibTest:WindowManagerTraceTest}
|
||||
*/
|
||||
public class WindowManagerTraceTest {
|
||||
private WindowManagerTrace mTrace;
|
||||
|
||||
private static WindowManagerTrace readWindowManagerTraceFromFile(String relativePath) {
|
||||
try {
|
||||
return WindowManagerTrace.parseFrom(readTestFile(relativePath));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mTrace = readWindowManagerTraceFromFile("wm_trace_openchrome.pb");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canParseAllEntries() {
|
||||
assertThat(mTrace.getEntries().get(0).getTimestamp()).isEqualTo(241777211939236L);
|
||||
assertThat(mTrace.getEntries().get(mTrace.getEntries().size() - 1).getTimestamp()).isEqualTo
|
||||
(241779809471942L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canDetectAboveAppWindowVisibility() {
|
||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
|
||||
Result result = entry.isAboveAppWindowVisible("NavigationBar");
|
||||
assertThat(result.passed()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canDetectBelowAppWindowVisibility() {
|
||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
|
||||
Result result = entry.isBelowAppWindowVisible("wallpaper");
|
||||
assertThat(result.passed()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canDetectAppWindowVisibility() {
|
||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
|
||||
Result result = entry.isAppWindowVisible("com.google.android.apps.nexuslauncher");
|
||||
assertThat(result.passed()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canFailWithReasonForVisibilityChecks_windowNotFound() {
|
||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
|
||||
Result result = entry.isAboveAppWindowVisible("ImaginaryWindow");
|
||||
assertThat(result.failed()).isTrue();
|
||||
assertThat(result.reason).contains("ImaginaryWindow cannot be found");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canFailWithReasonForVisibilityChecks_windowNotVisible() {
|
||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241777211939236L);
|
||||
Result result = entry.isAboveAppWindowVisible("AssistPreviewPanel");
|
||||
assertThat(result.failed()).isTrue();
|
||||
assertThat(result.reason).contains("AssistPreviewPanel is invisible");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canDetectAppZOrder() {
|
||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241778130296410L);
|
||||
Result result = entry.isVisibleAppWindowOnTop("com.google.android.apps.chrome");
|
||||
assertThat(result.passed()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canFailWithReasonForZOrderChecks_windowNotOnTop() {
|
||||
WindowManagerTrace.Entry entry = mTrace.getEntry(241778130296410L);
|
||||
Result result = entry.isVisibleAppWindowOnTop("com.google.android.apps.nexuslauncher");
|
||||
assertThat(result.failed()).isTrue();
|
||||
assertThat(result.reason).contains("wanted=com.google.android.apps.nexuslauncher");
|
||||
assertThat(result.reason).contains("found=com.android.chrome/"
|
||||
+ "com.google.android.apps.chrome.Main");
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import static com.android.server.wm.flicker.TestFileUtils.readTestFile;
|
||||
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Contains {@link WmTraceSubject} tests.
|
||||
* To run this test: {@code atest FlickerLibTest:WmTraceSubjectTest}
|
||||
*/
|
||||
public class WmTraceSubjectTest {
|
||||
private static WindowManagerTrace readWmTraceFromFile(String relativePath) {
|
||||
try {
|
||||
return WindowManagerTrace.parseFrom(readTestFile(relativePath));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCanTransitionInAppWindow() {
|
||||
WindowManagerTrace trace = readWmTraceFromFile("wm_trace_openchrome2.pb");
|
||||
|
||||
assertThat(trace).showsAppWindowOnTop("com.google.android.apps.nexuslauncher/"
|
||||
+ ".NexusLauncherActivity").forRange(174684850717208L, 174685957511016L);
|
||||
assertThat(trace).showsAppWindowOnTop(
|
||||
"com.google.android.apps.nexuslauncher/.NexusLauncherActivity")
|
||||
.then()
|
||||
.showsAppWindowOnTop("com.android.chrome")
|
||||
.forAllEntries();
|
||||
}
|
||||
}
|
||||
@@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker.monitor;
|
||||
|
||||
import static android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto.MAGIC_NUMBER_H;
|
||||
import static android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto.MAGIC_NUMBER_L;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import android.surfaceflinger.nano.Layerstrace.LayersTraceFileProto;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Contains {@link LayersTraceMonitor} tests.
|
||||
* To run this test: {@code atest FlickerLibTest:LayersTraceMonitorTest}
|
||||
*/
|
||||
public class LayersTraceMonitorTest {
|
||||
private LayersTraceMonitor mLayersTraceMonitor;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mLayersTraceMonitor = new LayersTraceMonitor();
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
mLayersTraceMonitor.stop();
|
||||
mLayersTraceMonitor.getOutputTraceFilePath("captureLayersTrace").toFile().delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canStartLayersTrace() throws Exception {
|
||||
mLayersTraceMonitor.start();
|
||||
assertThat(mLayersTraceMonitor.isEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canStopLayersTrace() throws Exception {
|
||||
mLayersTraceMonitor.start();
|
||||
assertThat(mLayersTraceMonitor.isEnabled()).isTrue();
|
||||
mLayersTraceMonitor.stop();
|
||||
assertThat(mLayersTraceMonitor.isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void captureLayersTrace() throws Exception {
|
||||
mLayersTraceMonitor.start();
|
||||
mLayersTraceMonitor.stop();
|
||||
File testFile = mLayersTraceMonitor.save("captureLayersTrace").toFile();
|
||||
assertThat(testFile.exists()).isTrue();
|
||||
byte[] trace = Files.toByteArray(testFile);
|
||||
assertThat(trace.length).isGreaterThan(0);
|
||||
LayersTraceFileProto mLayerTraceFileProto = LayersTraceFileProto.parseFrom(trace);
|
||||
assertThat(mLayerTraceFileProto.magicNumber).isEqualTo(
|
||||
(long) MAGIC_NUMBER_H << 32 | MAGIC_NUMBER_L);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker.monitor;
|
||||
|
||||
import static android.os.SystemClock.sleep;
|
||||
|
||||
import static com.android.server.wm.flicker.monitor.ScreenRecorder.DEFAULT_OUTPUT_PATH;
|
||||
import static com.android.server.wm.flicker.monitor.ScreenRecorder.getPath;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Contains {@link ScreenRecorder} tests.
|
||||
* To run this test: {@code atest FlickerLibTest:ScreenRecorderTest}
|
||||
*/
|
||||
public class ScreenRecorderTest {
|
||||
private static final String TEST_VIDEO_FILENAME = "test.mp4";
|
||||
private ScreenRecorder mScreenRecorder;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mScreenRecorder = new ScreenRecorder();
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
DEFAULT_OUTPUT_PATH.toFile().delete();
|
||||
getPath(TEST_VIDEO_FILENAME).toFile().delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void videoIsRecorded() {
|
||||
mScreenRecorder.start();
|
||||
sleep(100);
|
||||
mScreenRecorder.stop();
|
||||
File file = DEFAULT_OUTPUT_PATH.toFile();
|
||||
assertThat(file.exists()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void videoCanBeSaved() {
|
||||
mScreenRecorder.start();
|
||||
sleep(100);
|
||||
mScreenRecorder.stop();
|
||||
mScreenRecorder.save(TEST_VIDEO_FILENAME);
|
||||
File file = getPath(TEST_VIDEO_FILENAME).toFile();
|
||||
assertThat(file.exists()).isTrue();
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker.monitor;
|
||||
|
||||
import static com.android.server.wm.flicker.helpers.AutomationUtils.wakeUpAndGoToHomeScreen;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Contains {@link WindowAnimationFrameStatsMonitor} tests.
|
||||
* To run this test: {@code atest FlickerLibTest:WindowAnimationFrameStatsMonitorTest}
|
||||
*/
|
||||
public class WindowAnimationFrameStatsMonitorTest {
|
||||
private WindowAnimationFrameStatsMonitor mWindowAnimationFrameStatsMonitor;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mWindowAnimationFrameStatsMonitor = new WindowAnimationFrameStatsMonitor(
|
||||
InstrumentationRegistry.getInstrumentation());
|
||||
wakeUpAndGoToHomeScreen();
|
||||
}
|
||||
|
||||
// TODO(vishnun)
|
||||
@Ignore("Disabled until app-helper libraries are available.")
|
||||
@Test
|
||||
public void captureWindowAnimationFrameStats() throws Exception {
|
||||
mWindowAnimationFrameStatsMonitor.start();
|
||||
//AppHelperWrapper.getInstance().getHelper(CHROME).open();
|
||||
//AppHelperWrapper.getInstance().getHelper(CHROME).exit();
|
||||
mWindowAnimationFrameStatsMonitor.stop();
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker.monitor;
|
||||
|
||||
import static com.android.server.wm.nano.WindowManagerTraceFileProto.MAGIC_NUMBER_H;
|
||||
import static com.android.server.wm.nano.WindowManagerTraceFileProto.MAGIC_NUMBER_L;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.android.server.wm.nano.WindowManagerTraceFileProto;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
/**
|
||||
* Contains {@link WindowManagerTraceMonitor} tests.
|
||||
* To run this test: {@code atest FlickerLibTest:WindowManagerTraceMonitorTest}
|
||||
*/
|
||||
public class WindowManagerTraceMonitorTest {
|
||||
private WindowManagerTraceMonitor mWindowManagerTraceMonitor;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
mWindowManagerTraceMonitor = new WindowManagerTraceMonitor();
|
||||
}
|
||||
|
||||
@After
|
||||
public void teardown() {
|
||||
mWindowManagerTraceMonitor.stop();
|
||||
mWindowManagerTraceMonitor.getOutputTraceFilePath("captureWindowTrace").toFile().delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canStartWindowTrace() throws Exception {
|
||||
mWindowManagerTraceMonitor.start();
|
||||
assertThat(mWindowManagerTraceMonitor.isEnabled()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canStopWindowTrace() throws Exception {
|
||||
mWindowManagerTraceMonitor.start();
|
||||
assertThat(mWindowManagerTraceMonitor.isEnabled()).isTrue();
|
||||
mWindowManagerTraceMonitor.stop();
|
||||
assertThat(mWindowManagerTraceMonitor.isEnabled()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void captureWindowTrace() throws Exception {
|
||||
mWindowManagerTraceMonitor.start();
|
||||
mWindowManagerTraceMonitor.stop();
|
||||
File testFile = mWindowManagerTraceMonitor.save("captureWindowTrace").toFile();
|
||||
assertThat(testFile.exists()).isTrue();
|
||||
byte[] trace = Files.toByteArray(testFile);
|
||||
assertThat(trace.length).isGreaterThan(0);
|
||||
WindowManagerTraceFileProto mWindowTraceFileProto = WindowManagerTraceFileProto.parseFrom(
|
||||
trace);
|
||||
assertThat(mWindowTraceFileProto.magicNumber).isEqualTo(
|
||||
(long) MAGIC_NUMBER_H << 32 | MAGIC_NUMBER_L);
|
||||
}
|
||||
}
|
||||
@@ -29,11 +29,15 @@ import android.util.Log;
|
||||
import android.view.Surface;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.FlakyTest;
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
@@ -44,18 +48,19 @@ import java.util.Collection;
|
||||
* Cycle through supported app rotations.
|
||||
* To run this test: {@code atest FlickerTest:ChangeAppRotationTest}
|
||||
*/
|
||||
@RunWith(Parameterized.class)
|
||||
@LargeTest
|
||||
@RunWith(Parameterized.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class ChangeAppRotationTest extends FlickerTestBase {
|
||||
private int beginRotation;
|
||||
private int endRotation;
|
||||
private int mBeginRotation;
|
||||
private int mEndRotation;
|
||||
|
||||
public ChangeAppRotationTest(String beginRotationName, String endRotationName,
|
||||
int beginRotation, int endRotation) {
|
||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||
this.beginRotation = beginRotation;
|
||||
this.endRotation = endRotation;
|
||||
this.mBeginRotation = beginRotation;
|
||||
this.mEndRotation = endRotation;
|
||||
}
|
||||
|
||||
@Parameters(name = "{0}-{1}")
|
||||
@@ -77,15 +82,19 @@ public class ChangeAppRotationTest extends FlickerTestBase {
|
||||
@Before
|
||||
public void runTransition() {
|
||||
super.runTransition(
|
||||
changeAppRotation(testApp, uiDevice, beginRotation, endRotation).build());
|
||||
changeAppRotation(mTestApp, mUiDevice, mBeginRotation, mEndRotation).build());
|
||||
}
|
||||
|
||||
@FlakyTest(bugId = 140855415)
|
||||
@Ignore("Waiting bug feedback")
|
||||
@Test
|
||||
public void checkVisibility_navBarWindowIsAlwaysVisible() {
|
||||
checkResults(result -> assertThat(result)
|
||||
.showsAboveAppWindow(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
|
||||
}
|
||||
|
||||
@FlakyTest(bugId = 140855415)
|
||||
@Ignore("Waiting bug feedback")
|
||||
@Test
|
||||
public void checkVisibility_statusBarWindowIsAlwaysVisible() {
|
||||
checkResults(result -> assertThat(result)
|
||||
@@ -94,8 +103,8 @@ public class ChangeAppRotationTest extends FlickerTestBase {
|
||||
|
||||
@Test
|
||||
public void checkPosition_navBarLayerRotatesAndScales() {
|
||||
Rect startingPos = getNavigationBarPosition(beginRotation);
|
||||
Rect endingPos = getNavigationBarPosition(endRotation);
|
||||
Rect startingPos = getNavigationBarPosition(mBeginRotation);
|
||||
Rect endingPos = getNavigationBarPosition(mEndRotation);
|
||||
checkResults(result -> {
|
||||
LayersTraceSubject.assertThat(result)
|
||||
.hasVisibleRegion(NAVIGATION_BAR_WINDOW_TITLE, startingPos)
|
||||
@@ -108,22 +117,22 @@ public class ChangeAppRotationTest extends FlickerTestBase {
|
||||
|
||||
@Test
|
||||
public void checkPosition_appLayerRotates() {
|
||||
Rect startingPos = getAppPosition(beginRotation);
|
||||
Rect endingPos = getAppPosition(endRotation);
|
||||
Rect startingPos = getAppPosition(mBeginRotation);
|
||||
Rect endingPos = getAppPosition(mEndRotation);
|
||||
Log.e(TAG, "startingPos=" + startingPos + " endingPos=" + endingPos);
|
||||
checkResults(result -> {
|
||||
LayersTraceSubject.assertThat(result)
|
||||
.hasVisibleRegion(testApp.getPackage(), startingPos).inTheBeginning();
|
||||
.hasVisibleRegion(mTestApp.getPackage(), startingPos).inTheBeginning();
|
||||
LayersTraceSubject.assertThat(result)
|
||||
.hasVisibleRegion(testApp.getPackage(), endingPos).atTheEnd();
|
||||
.hasVisibleRegion(mTestApp.getPackage(), endingPos).atTheEnd();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkPosition_statusBarLayerScales() {
|
||||
Rect startingPos = getStatusBarPosition(beginRotation);
|
||||
Rect endingPos = getStatusBarPosition(endRotation);
|
||||
Rect startingPos = getStatusBarPosition(mBeginRotation);
|
||||
Rect endingPos = getStatusBarPosition(mEndRotation);
|
||||
checkResults(result -> {
|
||||
LayersTraceSubject.assertThat(result)
|
||||
.hasVisibleRegion(STATUS_BAR_WINDOW_TITLE, startingPos)
|
||||
@@ -134,12 +143,16 @@ public class ChangeAppRotationTest extends FlickerTestBase {
|
||||
);
|
||||
}
|
||||
|
||||
@FlakyTest(bugId = 140855415)
|
||||
@Ignore("Waiting bug feedback")
|
||||
@Test
|
||||
public void checkVisibility_navBarLayerIsAlwaysVisible() {
|
||||
checkResults(result -> LayersTraceSubject.assertThat(result)
|
||||
.showsLayer(NAVIGATION_BAR_WINDOW_TITLE).forAllEntries());
|
||||
}
|
||||
|
||||
@FlakyTest(bugId = 140855415)
|
||||
@Ignore("Waiting bug feedback")
|
||||
@Test
|
||||
public void checkVisibility_statusBarLayerIsAlwaysVisible() {
|
||||
checkResults(result -> LayersTraceSubject.assertThat(result)
|
||||
|
||||
@@ -26,8 +26,10 @@ import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
/**
|
||||
* Test IME window closing back to app window transitions.
|
||||
@@ -35,6 +37,7 @@ import org.junit.runner.RunWith;
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class CloseImeWindowToAppTest extends FlickerTestBase {
|
||||
|
||||
private static final String IME_WINDOW_TITLE = "InputMethod";
|
||||
@@ -44,7 +47,7 @@ public class CloseImeWindowToAppTest extends FlickerTestBase {
|
||||
|
||||
@Before
|
||||
public void runTransition() {
|
||||
super.runTransition(editTextLoseFocusToApp(uiDevice)
|
||||
super.runTransition(editTextLoseFocusToApp(mUiDevice)
|
||||
.includeJankyRuns().build());
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,10 @@ import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
/**
|
||||
* Test IME window closing to home transitions.
|
||||
@@ -35,6 +37,7 @@ import org.junit.runner.RunWith;
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class CloseImeWindowToHomeTest extends FlickerTestBase {
|
||||
|
||||
private static final String IME_WINDOW_TITLE = "InputMethod";
|
||||
@@ -44,7 +47,7 @@ public class CloseImeWindowToHomeTest extends FlickerTestBase {
|
||||
|
||||
@Before
|
||||
public void runTransition() {
|
||||
super.runTransition(editTextLoseFocusToHome(uiDevice)
|
||||
super.runTransition(editTextLoseFocusToHome(mUiDevice)
|
||||
.includeJankyRuns().build());
|
||||
}
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ class CommonTransitions {
|
||||
device.setOrientationNatural();
|
||||
}
|
||||
// Wait for animation to complete
|
||||
sleep(3000);
|
||||
sleep(1000);
|
||||
} catch (RemoteException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
@@ -216,10 +216,10 @@ class CommonTransitions {
|
||||
|
||||
static TransitionBuilder resizeSplitScreen(IAppHelper testAppTop, IAppHelper testAppBottom,
|
||||
UiDevice device, Rational startRatio, Rational stopRatio) {
|
||||
String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_" +
|
||||
testAppBottom.getLauncherName() + "_" +
|
||||
startRatio.toString().replace("/", ":") + "_to_" +
|
||||
stopRatio.toString().replace("/", ":");
|
||||
String testTag = "resizeSplitScreen_" + testAppTop.getLauncherName() + "_"
|
||||
+ testAppBottom.getLauncherName() + "_"
|
||||
+ startRatio.toString().replace("/", ":") + "_to_"
|
||||
+ stopRatio.toString().replace("/", ":");
|
||||
return TransitionRunner.newBuilder()
|
||||
.withTag(testTag)
|
||||
.runBeforeAll(AutomationUtils::wakeUpAndGoToHomeScreen)
|
||||
@@ -231,7 +231,7 @@ class CommonTransitions {
|
||||
.runBefore(() -> launchSplitScreen(device))
|
||||
.runBefore(() -> {
|
||||
UiObject2 snapshot = device.findObject(
|
||||
By.res("com.google.android.apps.nexuslauncher", "snapshot"));
|
||||
By.res(device.getLauncherPackageName(), "snapshot"));
|
||||
snapshot.click();
|
||||
})
|
||||
.runBefore(() -> AutomationUtils.resizeSplitScreen(device, startRatio))
|
||||
@@ -316,4 +316,4 @@ class CommonTransitions {
|
||||
.runAfterAll(testApp::exit)
|
||||
.repeat(ITERATIONS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,17 +22,22 @@ import android.util.Rational;
|
||||
import android.view.Surface;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
/**
|
||||
* Tests to help debug individual transitions, capture video recordings and create test cases.
|
||||
*/
|
||||
@LargeTest
|
||||
@Ignore("Used for debugging transitions used in FlickerTests.")
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class DebugTest {
|
||||
private IAppHelper testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||
|
||||
@@ -16,20 +16,23 @@
|
||||
|
||||
package com.android.server.wm.flicker;
|
||||
|
||||
import static androidx.test.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.server.wm.flicker.helpers.AutomationUtils.setDefaultWait;
|
||||
|
||||
import static com.google.common.truth.Truth.assertWithMessage;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.platform.helpers.IAppHelper;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.support.test.uiautomator.UiDevice;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
|
||||
import com.android.server.wm.flicker.TransitionRunner.TransitionResult;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
@@ -51,10 +54,16 @@ public class FlickerTestBase {
|
||||
static final String DOCKED_STACK_DIVIDER = "DockedStackDivider";
|
||||
private static HashMap<String, List<TransitionResult>> transitionResults =
|
||||
new HashMap<>();
|
||||
IAppHelper testApp;
|
||||
UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
|
||||
private List<TransitionResult> results;
|
||||
private TransitionResult lastResult = null;
|
||||
IAppHelper mTestApp;
|
||||
UiDevice mUiDevice;
|
||||
private List<TransitionResult> mResults;
|
||||
private TransitionResult mLastResult = null;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
InstrumentationRegistry.registerInstance(getInstrumentation(), new Bundle());
|
||||
mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Teardown any system settings and clean up test artifacts from the file system.
|
||||
@@ -91,14 +100,14 @@ public class FlickerTestBase {
|
||||
*/
|
||||
void runTransition(TransitionRunner transition) {
|
||||
if (transitionResults.containsKey(transition.getTestTag())) {
|
||||
results = transitionResults.get(transition.getTestTag());
|
||||
mResults = transitionResults.get(transition.getTestTag());
|
||||
return;
|
||||
}
|
||||
results = transition.run().getResults();
|
||||
mResults = transition.run().getResults();
|
||||
/* Fail if we don't have any results due to jank */
|
||||
assertWithMessage("No results to test because all transition runs were invalid because "
|
||||
+ "of Jank").that(results).isNotEmpty();
|
||||
transitionResults.put(transition.getTestTag(), results);
|
||||
+ "of Jank").that(mResults).isNotEmpty();
|
||||
transitionResults.put(transition.getTestTag(), mResults);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,11 +115,11 @@ public class FlickerTestBase {
|
||||
*/
|
||||
void checkResults(Consumer<TransitionResult> assertion) {
|
||||
|
||||
for (TransitionResult result : results) {
|
||||
lastResult = result;
|
||||
for (TransitionResult result : mResults) {
|
||||
mLastResult = result;
|
||||
assertion.accept(result);
|
||||
}
|
||||
lastResult = null;
|
||||
mLastResult = null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -119,8 +128,8 @@ public class FlickerTestBase {
|
||||
*/
|
||||
@After
|
||||
public void markArtifactsForSaving() {
|
||||
if (lastResult != null) {
|
||||
lastResult.flagForSaving();
|
||||
if (mLastResult != null) {
|
||||
mLastResult.flagForSaving();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,12 +21,16 @@ import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
|
||||
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.FlakyTest;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
/**
|
||||
* Test cold launch app from launcher.
|
||||
@@ -34,16 +38,17 @@ import org.junit.runner.RunWith;
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class OpenAppColdTest extends FlickerTestBase {
|
||||
|
||||
public OpenAppColdTest() {
|
||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void runTransition() {
|
||||
super.runTransition(getOpenAppCold(testApp, uiDevice).build());
|
||||
super.runTransition(getOpenAppCold(mTestApp, mUiDevice).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -61,9 +66,9 @@ public class OpenAppColdTest extends FlickerTestBase {
|
||||
@Test
|
||||
public void checkVisibility_wallpaperWindowBecomesInvisible() {
|
||||
checkResults(result -> assertThat(result)
|
||||
.showsBelowAppWindow("wallpaper")
|
||||
.showsBelowAppWindow("Wallpaper")
|
||||
.then()
|
||||
.hidesBelowAppWindow("wallpaper")
|
||||
.hidesBelowAppWindow("Wallpaper")
|
||||
.forAllEntries());
|
||||
}
|
||||
|
||||
@@ -71,13 +76,15 @@ public class OpenAppColdTest extends FlickerTestBase {
|
||||
public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
|
||||
checkResults(result -> assertThat(result)
|
||||
.showsAppWindowOnTop(
|
||||
"com.google.android.apps.nexuslauncher/.NexusLauncherActivity")
|
||||
"com.android.launcher3/.Launcher")
|
||||
.then()
|
||||
.showsAppWindowOnTop(testApp.getPackage())
|
||||
.showsAppWindowOnTop(mTestApp.getPackage())
|
||||
.forAllEntries());
|
||||
}
|
||||
|
||||
@Test
|
||||
@FlakyTest(bugId = 141235985)
|
||||
@Ignore("Waiting bug feedback")
|
||||
public void checkCoveredRegion_noUncoveredRegions() {
|
||||
checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
|
||||
getDisplayBounds()).forAllEntries());
|
||||
@@ -98,9 +105,9 @@ public class OpenAppColdTest extends FlickerTestBase {
|
||||
@Test
|
||||
public void checkVisibility_wallpaperLayerBecomesInvisible() {
|
||||
checkResults(result -> LayersTraceSubject.assertThat(result)
|
||||
.showsLayer("wallpaper")
|
||||
.showsLayer("Wallpaper")
|
||||
.then()
|
||||
.hidesLayer("wallpaper")
|
||||
.hidesLayer("Wallpaper")
|
||||
.forAllEntries());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,8 +24,10 @@ import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
/**
|
||||
* Test open app to split screen.
|
||||
@@ -33,16 +35,17 @@ import org.junit.runner.RunWith;
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class OpenAppToSplitScreenTest extends FlickerTestBase {
|
||||
|
||||
public OpenAppToSplitScreenTest() {
|
||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void runTransition() {
|
||||
super.runTransition(appToSplitScreen(testApp, uiDevice).includeJankyRuns().build());
|
||||
super.runTransition(appToSplitScreen(mTestApp, mUiDevice).includeJankyRuns().build());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -21,12 +21,16 @@ import static com.android.server.wm.flicker.WindowUtils.getDisplayBounds;
|
||||
import static com.android.server.wm.flicker.WmTraceSubject.assertThat;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.FlakyTest;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
/**
|
||||
* Test warm launch app.
|
||||
@@ -34,16 +38,17 @@ import org.junit.runner.RunWith;
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class OpenAppWarmTest extends FlickerTestBase {
|
||||
|
||||
public OpenAppWarmTest() {
|
||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void runTransition() {
|
||||
super.runTransition(openAppWarm(testApp, uiDevice).build());
|
||||
super.runTransition(openAppWarm(mTestApp, mUiDevice).includeJankyRuns().build());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -61,9 +66,9 @@ public class OpenAppWarmTest extends FlickerTestBase {
|
||||
@Test
|
||||
public void checkVisibility_wallpaperBecomesInvisible() {
|
||||
checkResults(result -> assertThat(result)
|
||||
.showsBelowAppWindow("wallpaper")
|
||||
.showsBelowAppWindow("Wallpaper")
|
||||
.then()
|
||||
.hidesBelowAppWindow("wallpaper")
|
||||
.hidesBelowAppWindow("Wallpaper")
|
||||
.forAllEntries());
|
||||
}
|
||||
|
||||
@@ -71,12 +76,14 @@ public class OpenAppWarmTest extends FlickerTestBase {
|
||||
public void checkZOrder_appWindowReplacesLauncherAsTopWindow() {
|
||||
checkResults(result -> assertThat(result)
|
||||
.showsAppWindowOnTop(
|
||||
"com.google.android.apps.nexuslauncher/.NexusLauncherActivity")
|
||||
"com.android.launcher3/.Launcher")
|
||||
.then()
|
||||
.showsAppWindowOnTop(testApp.getPackage())
|
||||
.showsAppWindowOnTop(mTestApp.getPackage())
|
||||
.forAllEntries());
|
||||
}
|
||||
|
||||
@FlakyTest(bugId = 141235985)
|
||||
@Ignore("Waiting bug feedback")
|
||||
@Test
|
||||
public void checkCoveredRegion_noUncoveredRegions() {
|
||||
checkResults(result -> LayersTraceSubject.assertThat(result).coversRegion(
|
||||
@@ -98,9 +105,9 @@ public class OpenAppWarmTest extends FlickerTestBase {
|
||||
@Test
|
||||
public void checkVisibility_wallpaperLayerBecomesInvisible() {
|
||||
checkResults(result -> LayersTraceSubject.assertThat(result)
|
||||
.showsLayer("wallpaper")
|
||||
.showsLayer("Wallpaper")
|
||||
.then()
|
||||
.hidesLayer("wallpaper")
|
||||
.hidesLayer("Wallpaper")
|
||||
.forAllEntries());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,10 @@ import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
/**
|
||||
* Test IME window opening transitions.
|
||||
@@ -32,13 +34,14 @@ import org.junit.runner.RunWith;
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class OpenImeWindowTest extends FlickerTestBase {
|
||||
|
||||
private static final String IME_WINDOW_TITLE = "InputMethod";
|
||||
|
||||
@Before
|
||||
public void runTransition() {
|
||||
super.runTransition(editTextSetFocus(uiDevice)
|
||||
super.runTransition(editTextSetFocus(mUiDevice)
|
||||
.includeJankyRuns().build());
|
||||
}
|
||||
|
||||
|
||||
@@ -28,12 +28,16 @@ import android.platform.helpers.IAppHelper;
|
||||
import android.util.Rational;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.FlakyTest;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
/**
|
||||
* Test split screen resizing window transitions.
|
||||
@@ -41,10 +45,13 @@ import org.junit.runner.RunWith;
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
@FlakyTest(bugId = 140856143)
|
||||
@Ignore("Waiting bug feedback")
|
||||
public class ResizeSplitScreenTest extends FlickerTestBase {
|
||||
|
||||
public ResizeSplitScreenTest() {
|
||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||
}
|
||||
|
||||
@@ -53,7 +60,7 @@ public class ResizeSplitScreenTest extends FlickerTestBase {
|
||||
IAppHelper bottomApp = new StandardAppHelper(InstrumentationRegistry
|
||||
.getInstrumentation(),
|
||||
"com.android.server.wm.flicker.testapp", "ImeApp");
|
||||
super.runTransition(resizeSplitScreen(testApp, bottomApp, uiDevice, new Rational(1, 3),
|
||||
super.runTransition(resizeSplitScreen(mTestApp, bottomApp, mUiDevice, new Rational(1, 3),
|
||||
new Rational(2, 3)).includeJankyRuns().build());
|
||||
}
|
||||
|
||||
|
||||
@@ -33,8 +33,10 @@ import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.filters.LargeTest;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
import org.junit.runners.Parameterized;
|
||||
import org.junit.runners.Parameterized.Parameters;
|
||||
|
||||
@@ -47,6 +49,7 @@ import java.util.Collection;
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(Parameterized.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
public class SeamlessAppRotationTest extends FlickerTestBase {
|
||||
private int mBeginRotation;
|
||||
private int mEndRotation;
|
||||
@@ -105,7 +108,7 @@ public class SeamlessAppRotationTest extends FlickerTestBase {
|
||||
|
||||
super.runTransition(
|
||||
changeAppRotation(mIntent, intentId, InstrumentationRegistry.getContext(),
|
||||
uiDevice, mBeginRotation, mEndRotation).repeat(5).build());
|
||||
mUiDevice, mBeginRotation, mEndRotation).repeat(5).build());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -25,8 +25,11 @@ import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.FixMethodOrder;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.MethodSorters;
|
||||
|
||||
/**
|
||||
* Test open app to split screen.
|
||||
@@ -34,16 +37,19 @@ import org.junit.runner.RunWith;
|
||||
*/
|
||||
@LargeTest
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||
@FlakyTest(bugId = 140856143)
|
||||
@Ignore("Waiting bug feedback")
|
||||
public class SplitScreenToLauncherTest extends FlickerTestBase {
|
||||
|
||||
public SplitScreenToLauncherTest() {
|
||||
this.testApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
this.mTestApp = new StandardAppHelper(InstrumentationRegistry.getInstrumentation(),
|
||||
"com.android.server.wm.flicker.testapp", "SimpleApp");
|
||||
}
|
||||
|
||||
@Before
|
||||
public void runTransition() {
|
||||
super.runTransition(splitScreenToLauncher(testApp, uiDevice).includeJankyRuns().build());
|
||||
super.runTransition(splitScreenToLauncher(mTestApp, mUiDevice).includeJankyRuns().build());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -62,13 +68,12 @@ public class SplitScreenToLauncherTest extends FlickerTestBase {
|
||||
.forAllEntries());
|
||||
}
|
||||
|
||||
@FlakyTest(bugId = 79686616)
|
||||
@Test
|
||||
public void checkVisibility_appLayerBecomesInVisible() {
|
||||
checkResults(result -> LayersTraceSubject.assertThat(result)
|
||||
.showsLayer(testApp.getPackage())
|
||||
.showsLayer(mTestApp.getPackage())
|
||||
.then()
|
||||
.hidesLayer(testApp.getPackage())
|
||||
.hidesLayer(mTestApp.getPackage())
|
||||
.forAllEntries());
|
||||
}
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2018 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 com.android.server.wm.flicker;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.platform.helpers.AbstractStandardAppHelper;
|
||||
|
||||
/**
|
||||
* Class to take advantage of {@code IAppHelper} interface so the same test can be run against
|
||||
* first party and third party apps.
|
||||
*/
|
||||
public class StandardAppHelper extends AbstractStandardAppHelper {
|
||||
private final String mPackageName;
|
||||
private final String mLauncherName;
|
||||
|
||||
public StandardAppHelper(Instrumentation instr, String packageName, String launcherName) {
|
||||
super(instr);
|
||||
mPackageName = packageName;
|
||||
mLauncherName = launcherName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getPackage() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public String getLauncherName() {
|
||||
return mLauncherName;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void dismissInitialDialogs() {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.server.wm.flicker.testapp;
|
||||
|
||||
import static android.os.SystemClock.sleep;
|
||||
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
|
||||
|
||||
import static com.android.server.wm.flicker.testapp.ActivityOptions.EXTRA_STARVE_UI_THREAD;
|
||||
|
||||
@@ -39,8 +38,8 @@ public class SeamlessRotationActivity extends Activity {
|
||||
super.onCreate(savedInstanceState);
|
||||
enableSeamlessRotation();
|
||||
setContentView(R.layout.activity_simple);
|
||||
boolean starveUiThread = getIntent().getExtras() != null &&
|
||||
getIntent().getExtras().getBoolean(EXTRA_STARVE_UI_THREAD);
|
||||
boolean starveUiThread = getIntent().getExtras() != null
|
||||
&& getIntent().getExtras().getBoolean(EXTRA_STARVE_UI_THREAD);
|
||||
if (starveUiThread) {
|
||||
starveUiThread();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user