blob: 277628b8d2e5abf66b85a0cef9be82e6f3600952 [file] [log] [blame]
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tools.common.flicker.assertions
/** Utility class to store assertions composed of multiple individual assertions */
class CompoundAssertion<T>(assertion: (T) -> Unit, name: String, optional: Boolean) :
IAssertion<T> {
private val assertions = mutableListOf<NamedAssertion<T>>()
init {
add(assertion, name, optional)
}
override val isOptional
get() = assertions.all { it.isOptional }
override val name
get() = assertions.joinToString(" and ") { it.name }
/**
* Executes all [assertions] on [target]
*
* In case of failure, returns the first non-optional failure (if available) or the first failed
* assertion
*/
override fun invoke(target: T) {
val failures =
assertions.mapNotNull { assertion ->
val error = kotlin.runCatching { assertion.invoke(target) }.exceptionOrNull()
if (error != null) {
Pair(assertion, error)
} else {
null
}
}
val nonOptionalFailure = failures.firstOrNull { !it.first.isOptional }
if (nonOptionalFailure != null) {
throw nonOptionalFailure.second
}
val firstFailure = failures.firstOrNull()
// Only throw first failure if all siblings are also optional otherwise don't throw anything
// If the CompoundAssertion is fully optional (i.e. all assertions in the compound assertion
// are optional), then we want to make sure the AssertionsChecker knows about the failure to
// not advance to the next state. Otherwise, the AssertionChecker doesn't need to know about
// the failure and can just consider the assertion as passed and advance to the next state
// since there were non-optional assertions which passed.
if (firstFailure != null && isOptional) {
throw firstFailure.second
}
}
/** Adds a new assertion to the list */
fun add(assertion: (T) -> Unit, name: String, optional: Boolean) {
assertions.add(NamedAssertion(assertion, name, optional))
}
override fun toString(): String = name
override fun equals(other: Any?): Boolean {
if (other !is CompoundAssertion<*>) {
return false
}
if (!super.equals(other)) {
return false
}
assertions.forEachIndexed { index, assertion ->
if (assertion != other.assertions[index]) {
return false
}
}
return true
}
override fun hashCode(): Int {
return assertions.hashCode()
}
}