xref: /aosp_15_r20/platform_testing/libraries/flicker/src/android/tools/flicker/assertions/CompoundAssertion.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.assertions
18 
19 import android.tools.function.AssertionPredicate
20 
21 /** Utility class to store assertions composed of multiple individual assertions */
22 class CompoundAssertion<T>(assertion: AssertionPredicate<T>, name: String, optional: Boolean) :
23     Assertion<T> {
24     private val assertions = mutableListOf<NamedAssertion<T>>()
25 
26     init {
27         add(assertion, name, optional)
28     }
29 
30     override val isOptional
31         get() = assertions.all { it.isOptional }
32 
33     override val name
34         get() = assertions.joinToString(" and ") { it.name }
35 
36     /**
37      * Executes all [assertions] on [target]
38      *
39      * In case of failure, returns the first non-optional failure (if available) or the first failed
40      * assertion
41      */
42     override fun invoke(target: T) {
43         val failures =
44             assertions.mapNotNull { assertion ->
45                 val error = kotlin.runCatching { assertion.invoke(target) }.exceptionOrNull()
46                 if (error != null) {
47                     Pair(assertion, error)
48                 } else {
49                     null
50                 }
51             }
52         val nonOptionalFailure = failures.firstOrNull { !it.first.isOptional }
53         if (nonOptionalFailure != null) {
54             throw nonOptionalFailure.second
55         }
56         val firstFailure = failures.firstOrNull()
57         // Only throw first failure if all siblings are also optional otherwise don't throw anything
58         // If the CompoundAssertion is fully optional (i.e. all assertions in the compound assertion
59         // are optional), then we want to make sure the AssertionsChecker knows about the failure to
60         // not advance to the next state. Otherwise, the AssertionChecker doesn't need to know about
61         // the failure and can just consider the assertion as passed and advance to the next state
62         // since there were non-optional assertions which passed.
63         if (firstFailure != null && isOptional) {
64             throw firstFailure.second
65         }
66     }
67 
68     /** Adds a new assertion to the list */
69     fun add(assertion: AssertionPredicate<T>, name: String, optional: Boolean) {
70         assertions.add(NamedAssertion(assertion, name, optional))
71     }
72 
73     override fun toString(): String = name
74 
75     override fun equals(other: Any?): Boolean {
76         if (other !is CompoundAssertion<*>) {
77             return false
78         }
79         if (!super.equals(other)) {
80             return false
81         }
82         assertions.forEachIndexed { index, assertion ->
83             if (assertion != other.assertions[index]) {
84                 return false
85             }
86         }
87         return true
88     }
89 
90     override fun hashCode(): Int {
91         return assertions.hashCode()
92     }
93 }
94