ConfigurationError.kt
/*
* Copyright 2021 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 androidx.benchmark
import androidx.annotation.RestrictTo
/**
* Represents an error in configuration of a benchmark.
*
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class ConfigurationError(
/**
* All-caps, publicly visible ID for the error.
*
* Used for suppression via instrumentation arguments.
*/
val id: String,
/**
* One-line summary of the problem.
*/
val summary: String,
/**
* Multi-line, preformatted detailed description of the problem.
*/
val message: String
) {
init {
validateParams(id, summary)
}
companion object {
internal fun validateParams(
id: String,
summary: String
) {
require(!id.contains("[a-z]".toRegex())) {
"IDs must be ALL-CAPs by convention (id=$id)"
}
require(!id.contains("_")) {
"Use hyphen instead of underscore for consistency (id=$id)"
}
require(!summary.contains("\n")) {
"Summary must be one line"
}
}
}
/**
* Representation of suppressed errors during a running benchmark.
*/
class SuppressionState(
/**
* Prefix for output data to mark as potentially invalid.
*/
val prefix: String,
/**
* Warning message to present to the user.
*/
val warningMessage: String
)
}
/** @suppress */
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun conditionalError(
hasError: Boolean,
id: String,
summary: String,
message: String
): ConfigurationError? {
// validation done here *and* in constructor to ensure it happens even when error doesn't fire
ConfigurationError.validateParams(id, summary)
return if (hasError) {
ConfigurationError(id, summary, message)
} else null
}
internal fun List<ConfigurationError>.prettyPrint(prefix: String): String {
return joinToString("\n") {
prefix + it.summary + "\n" + it.message.prependIndent() + "\n"
}
}
/**
* Throw an AssertionError if the list contains an unsuppressed error, and return either a
* SuppressionState if errors are suppressed, or null otherwise.
*
* @suppress
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun List<ConfigurationError>.checkAndGetSuppressionState(
suppressedErrorIds: Set<String>,
): ConfigurationError.SuppressionState? {
if (isEmpty()) {
return null
}
val (suppressed, unsuppressed) = partition {
suppressedErrorIds.contains(it.id)
}
val prefix = this.joinToString("_") { it.id } + "_"
// either fail and report all unsuppressed errors ...
if (unsuppressed.isNotEmpty() && !Arguments.dryRunMode) {
val unsuppressedString = unsuppressed.joinToString(" ") { it.id }
val suppressedString = suppressed.joinToString(" ") { it.id }
val howToSuppressString = this.joinToString(",") { it.id }
throw AssertionError(
"""
|ERRORS (not suppressed): $unsuppressedString
|WARNINGS (suppressed): $suppressedString
|
|${unsuppressed.prettyPrint("ERROR: ")}
|While you can suppress these errors (turning them into warnings)
|PLEASE NOTE THAT EACH SUPPRESSED ERROR COMPROMISES ACCURACY
|
|// Sample suppression, in a benchmark module's build.gradle:
|android {
| defaultConfig {
| testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "$howToSuppressString"
| }
|}
""".trimMargin()
)
}
// ... or report all errors as suppressed
return ConfigurationError.SuppressionState(prefix, prettyPrint("WARNING: "))
}