1 /*
2 * Copyright 2021 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 androidx.benchmark
18
19 import androidx.annotation.RestrictTo
20
21 /** Represents an error in configuration of a benchmark. */
22 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
23 data class ConfigurationError(
24 /**
25 * All-caps, publicly visible ID for the error.
26 *
27 * Used for suppression via instrumentation arguments.
28 */
29 val id: String,
30
31 /** One-line summary of the problem. */
32 val summary: String,
33
34 /** Multi-line, preformatted detailed description of the problem. */
35 val message: String
36 ) {
37 init {
38 validateParams(id, summary)
39 }
40
41 companion object {
validateParamsnull42 internal fun validateParams(id: String, summary: String) {
43 require(!id.contains("[a-z]".toRegex())) {
44 "IDs must be ALL-CAPs by convention (id=$id)"
45 }
46 require(!id.contains("_")) {
47 "Use hyphen instead of underscore for consistency (id=$id)"
48 }
49 require(!summary.contains("\n")) { "Summary must be one line" }
50 }
51 }
52
53 /** Representation of suppressed errors during a running benchmark. */
54 class SuppressionState(
55 /** Prefix for output data to mark as potentially invalid. */
56 val prefix: String,
57
58 /** Warning message to present to the user. */
59 val warningMessage: String
60 )
61 }
62
63 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
conditionalErrornull64 fun conditionalError(
65 hasError: Boolean,
66 id: String,
67 summary: String,
68 message: String
69 ): ConfigurationError? {
70 // validation done here *and* in constructor to ensure it happens even when error doesn't fire
71 ConfigurationError.validateParams(id, summary)
72 return if (hasError) {
73 ConfigurationError(id, summary, message)
74 } else null
75 }
76
prettyPrintnull77 internal fun List<ConfigurationError>.prettyPrint(prefix: String): String {
78 return joinToString("\n") { prefix + it.summary + "\n" + it.message.prependIndent() + "\n" }
79 }
80
81 /**
82 * Throw an AssertionError if the list contains an unsuppressed error, and return either a
83 * SuppressionState if errors are suppressed, or null otherwise.
84 */
85 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
Listnull86 fun List<ConfigurationError>.checkAndGetSuppressionState(
87 suppressedErrorIds: Set<String>,
88 ): ConfigurationError.SuppressionState? {
89 if (isEmpty()) {
90 return null
91 }
92
93 val (suppressed, unsuppressed) = partition { suppressedErrorIds.contains(it.id) }
94
95 val prefix = this.joinToString("_") { it.id } + "_"
96
97 // either fail and report all unsuppressed errors ...
98 if (unsuppressed.isNotEmpty() && !Arguments.dryRunMode) {
99 val unsuppressedString = unsuppressed.joinToString(" ") { it.id }
100 val suppressedString = suppressed.joinToString(" ") { it.id }
101 val howToSuppressString = this.joinToString(",") { it.id }
102 throw AssertionError(
103 """
104 |ERRORS (not suppressed): $unsuppressedString
105 |WARNINGS (suppressed): $suppressedString
106 |
107 |${unsuppressed.prettyPrint("ERROR: ")}
108 |While you can suppress these errors (turning them into warnings)
109 |PLEASE NOTE THAT EACH SUPPRESSED ERROR COMPROMISES ACCURACY
110 |
111 |// Sample suppression, in a benchmark module's build.gradle:
112 |android {
113 | defaultConfig {
114 | testInstrumentationRunnerArguments["androidx.benchmark.suppressErrors"] = "$howToSuppressString"
115 | }
116 |}
117 """
118 .trimMargin()
119 )
120 }
121
122 // ... or report all errors as suppressed
123 return ConfigurationError.SuppressionState(prefix, prettyPrint("WARNING: "))
124 }
125