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 
24 /** Base subject for flicker trace assertions */
25 abstract class FlickerTraceSubject<EntrySubject : FlickerSubject> : FlickerSubject() {
26     override val timestamp
27         get() = subjects.firstOrNull()?.timestamp ?: Timestamps.empty()
28 
29     protected val assertionsChecker = AssertionsChecker<EntrySubject>()
30     private var newAssertionBlock = true
31 
32     abstract val subjects: List<EntrySubject>
33 
34     fun hasAssertions() = !assertionsChecker.isEmpty()
35 
36     /**
37      * Adds a new assertion block (if preceded by [then]) or appends an assertion to the latest
38      * existing assertion block
39      *
40      * @param name Assertion name
41      * @param isOptional If this assertion is optional or must pass
42      */
43     protected fun addAssertion(
44         name: String,
45         isOptional: Boolean = false,
46         assertion: (EntrySubject) -> Unit
47     ) {
48         if (newAssertionBlock) {
49             assertionsChecker.add(name, isOptional, assertion)
50         } else {
51             assertionsChecker.append(name, isOptional, assertion)
52         }
53         newAssertionBlock = false
54     }
55 
56     /** Run the assertions for all trace entries */
57     fun forAllEntries() {
58         require(subjects.isNotEmpty()) { "Trace is empty" }
59         assertionsChecker.test(subjects)
60     }
61 
62     /** User-defined entry point for the first trace entry */
63     fun first(): EntrySubject = subjects.firstOrNull() ?: error("Trace is empty")
64 
65     /** User-defined entry point for the last trace entry */
66     fun last(): EntrySubject = subjects.lastOrNull() ?: error("Trace is empty")
67 
68     /**
69      * Signal that the last assertion set is complete. The next assertion added will start a new set
70      * of assertions.
71      *
72      * E.g.: checkA().then().checkB()
73      *
74      * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
75      * after checkA passes.
76      */
77     open fun then(): FlickerTraceSubject<EntrySubject> = apply { startAssertionBlock() }
78 
79     /**
80      * Ignores the first entries in the trace, until the first assertion passes. If it reaches the
81      * end of the trace without passing any assertion, return a failure with the name/reason from
82      * the first assertion
83      *
84      * @return
85      */
86     open fun skipUntilFirstAssertion(): FlickerTraceSubject<EntrySubject> = apply {
87         assertionsChecker.skipUntilFirstAssertion()
88     }
89 
90     /**
91      * Signal that the last assertion set is complete. The next assertion added will start a new set
92      * of assertions.
93      *
94      * E.g.: checkA().then().checkB()
95      *
96      * Will produce two sets of assertions (checkA) and (checkB) and checkB will only be checked
97      * after checkA passes.
98      */
99     private fun startAssertionBlock() {
100         newAssertionBlock = true
101     }
102 
103     /**
104      * Checks whether all the trace entries on the list are visible for more than one consecutive
105      * entry
106      *
107      * Ignore the first and last trace subjects. This is necessary because WM and SF traces log
108      * entries only when a change occurs.
109      *
110      * If the trace starts immediately before an animation or if it stops immediately after one, the
111      * first and last entry may contain elements that are visible only for that entry. Those
112      * elements, however, are not flickers, since they existed on the screen before or after the
113      * test.
114      *
115      * @param [visibleEntriesProvider] a list of all the entries with their name and index
116      */
117     protected fun visibleEntriesShownMoreThanOneConsecutiveTime(
118         visibleEntriesProvider: (EntrySubject) -> Set<String>
119     ) {
120         if (subjects.isEmpty()) {
121             return
122         }
123         // Duplicate the first and last trace subjects to prevent them from triggering failures
124         // since WM and SF traces log entries only when a change occurs
125         val firstState = subjects.first()
126         val lastState = subjects.last()
127         val subjects =
128             subjects.toMutableList().also {
129                 it.add(lastState)
130                 it.add(0, firstState)
131             }
132         var lastVisible = visibleEntriesProvider(subjects.first())
133         val lastNew = lastVisible.toMutableSet()
134 
135         // first subject was already taken
136         subjects.drop(1).forEachIndexed { index, entrySubject ->
137             val currentVisible = visibleEntriesProvider(entrySubject)
138             val newVisible = currentVisible.filter { it !in lastVisible }
139             lastNew.removeAll(currentVisible)
140 
141             if (lastNew.isNotEmpty()) {
142                 val errorMsgBuilder =
143                     ExceptionMessageBuilder()
144                         .forSubject(subjects[index])
145                         .setMessage("$lastNew is not visible for 2 entries")
146                 throw SubjectAssertionError(errorMsgBuilder)
147             }
148             lastNew.addAll(newVisible)
149             lastVisible = currentVisible
150         }
151 
152         if (lastNew.isNotEmpty()) {
153             val errorMsgBuilder =
154                 ExceptionMessageBuilder()
155                     .forSubject(subjects.last())
156                     .setMessage("$lastNew is not visible for 2 entries")
157             throw SubjectAssertionError(errorMsgBuilder)
158         }
159     }
160 
161     override fun toString(): String =
162         "${this::class.simpleName}" +
163             "(${subjects.firstOrNull()?.timestamp ?: 0},${subjects.lastOrNull()?.timestamp ?: 0})"
164 }
165