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