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