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