• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2021 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.wm.flicker.traces
18 
19 import com.android.server.wm.flicker.assertions.Assertion
20 import com.android.server.wm.flicker.assertions.AssertionsChecker
21 import com.android.server.wm.flicker.assertions.FlickerSubject
22 import com.android.server.wm.traces.common.prettyTimestamp
23 import com.google.common.truth.Fact
24 import com.google.common.truth.FailureMetadata
25 
26 /**
27  * Base subject for flicker trace assertions
28  */
29 abstract class FlickerTraceSubject<EntrySubject : FlickerSubject>(
30     fm: FailureMetadata,
31     data: Any?
32 ) : FlickerSubject(fm, data) {
33     override val timestamp: Long get() = subjects.firstOrNull()?.timestamp ?: 0L
34     override val selfFacts by lazy {
35         val firstTimestamp = subjects.firstOrNull()?.timestamp ?: 0L
36         val lastTimestamp = subjects.lastOrNull()?.timestamp ?: 0L
37         val first = "${prettyTimestamp(firstTimestamp)} (timestamp=$firstTimestamp)"
38         val last = "${prettyTimestamp(lastTimestamp)} (timestamp=$lastTimestamp)"
39         listOf(Fact.fact("Trace start", first),
40                 Fact.fact("Trace end", last))
41     }
42 
43     protected val assertionsChecker = AssertionsChecker<EntrySubject>()
44     private var newAssertionBlock = true
45 
46     abstract val subjects: List<EntrySubject>
47 
48     /**
49      * Empty the subject's list of assertions.
50      */
51     internal fun clear() {
52         assertionsChecker.clear()
53         newAssertionBlock = true
54     }
55 
56     /**
57      * Adds a new assertion block (if preceded by [then]) or appends an assertion to the
58      * latest existing assertion block
59      *
60      * @param name Assertion name
61      * @param isOptional If this assertion is optional or must pass
62      */
63     protected fun addAssertion(
64         name: String,
65         isOptional: Boolean = false,
66         assertion: Assertion<EntrySubject>
67     ) {
68         if (newAssertionBlock) {
69             assertionsChecker.add(name, isOptional, assertion)
70         } else {
71             assertionsChecker.append(name, isOptional, assertion)
72         }
73         newAssertionBlock = false
74     }
75 
76     /**
77      * Run the assertions for all trace entries
78      */
79     fun forAllEntries() {
80         assertionsChecker.test(subjects)
81     }
82 
83     /**
84      * User-defined entry point for the first trace entry
85      */
86     fun first(): EntrySubject = subjects.firstOrNull() ?: error("Trace is empty")
87 
88     /**
89      * User-defined entry point for the last trace entry
90      */
91     fun last(): EntrySubject = subjects.lastOrNull() ?: error("Trace is empty")
92 
93     /**
94      * Signal that the last assertion set is complete. The next assertion added will start a new
95      * set of assertions.
96      *
97      * E.g.: checkA().then().checkB()
98      *
99      * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
100      * after checkA passes.
101      */
102     open fun then(): FlickerTraceSubject<EntrySubject> = apply {
103         startAssertionBlock()
104     }
105 
106     /**
107      * Ignores the first entries in the trace, until the first assertion passes. If it reaches the
108      * end of the trace without passing any assertion, return a failure with the name/reason from
109      * the first assertion
110      *
111      * @return
112      */
113     open fun skipUntilFirstAssertion(): FlickerTraceSubject<EntrySubject> =
114         apply { assertionsChecker.skipUntilFirstAssertion() }
115 
116     /**
117      * Signal that the last assertion set is complete. The next assertion added will start a new
118      * set of assertions.
119      *
120      * E.g.: checkA().then().checkB()
121      *
122      * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
123      * after checkA passes.
124      */
125     private fun startAssertionBlock() {
126         newAssertionBlock = true
127     }
128 
129     /**
130      * Checks whether all the trace entries on the list are visible for more than one consecutive
131      * entry
132      *
133      * Ignore the first and last trace subjects. This is necessary because WM and SF traces
134      * log entries only when a change occurs.
135      *
136      * If the trace starts immediately before an animation or if it stops immediately after one,
137      * the first and last entry may contain elements that are visible only for that entry.
138      * Those elements, however, are not flickers, since they existed on the screen before or after
139      * the test.
140      *
141      * @param [visibleEntriesProvider] a list of all the entries with their name and index
142      */
143     protected fun visibleEntriesShownMoreThanOneConsecutiveTime(
144         visibleEntriesProvider: (EntrySubject) -> Set<String>
145     ) {
146         if (subjects.isEmpty()) {
147             return
148         }
149         // Duplicate the first and last trace subjects to prevent them from triggering failures
150         // since WM and SF traces log entries only when a change occurs
151         val firstState = subjects.first()
152         val lastState = subjects.last()
153         val subjects = subjects.toMutableList().also {
154             it.add(lastState)
155             it.add(0, firstState)
156         }
157         var lastVisible = visibleEntriesProvider(subjects.first())
158         val lastNew = lastVisible.toMutableSet()
159 
160         // first subject was already taken
161         subjects.drop(1).forEachIndexed { index, entrySubject ->
162             val currentVisible = visibleEntriesProvider(entrySubject)
163             val newVisible = currentVisible.filter { it !in lastVisible }
164             lastNew.removeAll(currentVisible)
165 
166             if (lastNew.isNotEmpty()) {
167                 val prevEntry = subjects[index]
168                 prevEntry.fail("$lastNew is not visible for 2 entries")
169             }
170             lastNew.addAll(newVisible)
171             lastVisible = currentVisible
172         }
173 
174         if (lastNew.isNotEmpty()) {
175             val lastEntry = subjects.last()
176             lastEntry.fail("$lastNew is not visible for 2 entries")
177         }
178     }
179 
180     override fun toString(): String = "${this::class.simpleName}" +
181             "(${subjects.firstOrNull()?.timestamp ?: 0},${subjects.lastOrNull()?.timestamp ?: 0})"
182 }
183