1 /*
<lambda>null2  * Copyright 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 androidx.camera.testing.impl
18 
19 import android.util.Log
20 import androidx.camera.testing.impl.CameraPipeConfigTestRule.Companion.CAMERA_PIPE_TEST_FLAG
21 import org.junit.AssumptionViolatedException
22 import org.junit.rules.TestRule
23 import org.junit.runner.Description
24 import org.junit.runners.model.Statement
25 
26 /**
27  * A [TestRule] to convert test failures into AssumptionViolatedException
28  *
29  * All the test methods will be executed as usual when [active] is set to false.
30  *
31  * When the [active] is true and DUT doesn't set the debug [CAMERA_PIPE_TEST_FLAG], all the test
32  * failures will be converted to AssumptionViolatedException. Otherwise, test failures will be shown
33  * as usual.
34  *
35  * The [CAMERA_PIPE_TEST_FLAG] can be enabled on the DUT by the command:
36  * ```
37  * adb shell setprop log.tag.CAMERA_PIPE_TESTING DEBUG
38  * ```
39  *
40  * To apply the [TestRule] , please create the [CameraPipeConfigTestRule] directly. For Camera-Pipe
41  * related tests, please set [active] to true.
42  *
43  * ```
44  *  @get:Rule
45  *  val testRule: CameraPipeConfigTestRule = CameraPipeConfigTestRule(
46  *      active = true
47  *  )
48  * ```
49  *
50  * @property active true to activate this rule.
51  */
52 public class CameraPipeConfigTestRule(
53     public val active: Boolean,
54 ) : TestRule {
55 
56     override fun apply(base: Statement, description: Description): Statement =
57         object : Statement() {
58             private var standardHandler: Thread.UncaughtExceptionHandler? = null
59 
60             override fun evaluate() {
61                 if (active) {
62                     if (Log.isLoggable(CAMERA_PIPE_MH_FLAG, Log.DEBUG)) {
63                         testInPipeLab()
64                     } else {
65                         testNotInPipeLab()
66                     }
67                 } else {
68                     if (Log.isLoggable(CAMERA2_TEST_DISABLE, Log.DEBUG)) {
69                         throw AssumptionViolatedException(
70                             "Ignore Camera2 tests since CAMERA2_TEST_DISABLE flag is turned on."
71                         )
72                     }
73                     base.evaluate()
74                 }
75             }
76 
77             private fun testInPipeLab() {
78                 try {
79                     log("started: ${description.displayName}")
80                     logUncaughtExceptions()
81                     base.evaluate()
82                 } catch (e: AssumptionViolatedException) {
83                     log("AssumptionViolatedException: ${description.displayName}", e)
84                     handleException(e)
85                 } catch (e: Throwable) {
86                     log("failed: ${description.displayName}", e)
87                     handleException(e)
88                 } finally {
89                     restoreUncaughtExceptionHandler()
90                     log("finished: ${description.displayName}")
91                 }
92             }
93 
94             private fun testNotInPipeLab() {
95                 if (testInAllowList() && !Log.isLoggable(CAMERA_MH_FLAG, Log.DEBUG)) {
96                     // Run the test when (1) In the allow list && (2) It is not MH daily test.
97                     base.evaluate()
98                 } else {
99                     throw AssumptionViolatedException(
100                         "Ignore Camera-pipe tests since there's no debug flag"
101                     )
102                 }
103             }
104 
105             private fun testInAllowList() =
106                 allowPresubmitTests.any { description.displayName.contains(it, ignoreCase = true) }
107 
108             private fun handleException(e: Throwable) {
109                 if (Log.isLoggable(CAMERA_PIPE_TEST_FLAG, Log.DEBUG)) {
110                     throw e
111                 } else {
112                     throw AssumptionViolatedException("CameraPipeTestFailure", e)
113                 }
114             }
115 
116             private fun logUncaughtExceptions() {
117                 standardHandler = Thread.getDefaultUncaughtExceptionHandler()
118                 Thread.setDefaultUncaughtExceptionHandler { _, e ->
119                     log("Invoking uncaught exception handler: ${description.displayName}", e)
120                     handleException(e)
121                 }
122             }
123 
124             private fun restoreUncaughtExceptionHandler() {
125                 Thread.setDefaultUncaughtExceptionHandler(standardHandler)
126             }
127         }
128 
129     private companion object {
130         private const val CAMERA2_TEST_DISABLE = "CAMERA2_TEST_DISABLE"
131         private const val CAMERA_PIPE_TEST_FLAG = "CAMERA_PIPE_TESTING"
132         private const val CAMERA_PIPE_MH_FLAG = "CameraPipeMH"
133         private const val CAMERA_MH_FLAG = "MH"
134         private const val LOG_TAG = "CameraPipeTest"
135 
136         private val allowPresubmitTests =
137             listOf(
138                 // CoreTestApps
139                 "androidx.camera.integration.core.",
140                 // Camera-View
141                 "androidx.camera.view.",
142                 // Camera-Video
143                 "androidx.camera.video.",
144                 // UIWidgets
145                 "androidx.camera.integration.uiwidgets.",
146             )
147 
148         fun log(message: String, throwable: Throwable? = null) {
149             if (throwable != null) {
150                 Log.e(LOG_TAG, message, throwable)
151             } else {
152                 Log.i(LOG_TAG, message)
153             }
154         }
155     }
156 }
157