Loading packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt 0 → 100644 +155 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 com.android.systemui.log import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.model.Statement import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) @SmallTest class LogWtfHandlerRuleTest : SysuiTestCase() { val underTest = LogWtfHandlerRule() @Test fun passingTestWithoutWtf_shouldPass() { val result = runTestCodeWithRule { Log.e(TAG, "just an error", IndexOutOfBoundsException()) } assertThat(result.isSuccess).isTrue() } @Test fun passingTestWithWtf_shouldFail() { val result = runTestCodeWithRule { Log.wtf(TAG, "some terrible failure", IllegalStateException()) } assertThat(result.isFailure).isTrue() val exception = result.exceptionOrNull() assertThat(exception).isInstanceOf(AssertionError::class.java) assertThat(exception?.cause).isInstanceOf(Log.TerribleFailure::class.java) assertThat(exception?.cause?.cause).isInstanceOf(IllegalStateException::class.java) } @Test fun failingTestWithoutWtf_shouldFail() { val result = runTestCodeWithRule { Log.e(TAG, "just an error", IndexOutOfBoundsException()) throw NullPointerException("some npe") } assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) } @Test fun failingTestWithWtf_shouldFail() { val result = runTestCodeWithRule { Log.wtf(TAG, "some terrible failure", IllegalStateException()) throw NullPointerException("some npe") } assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions assertThat(suppressedExceptions).hasSize(1) val suppressed = suppressedExceptions.first() assertThat(suppressed).isInstanceOf(AssertionError::class.java) assertThat(suppressed.cause).isInstanceOf(Log.TerribleFailure::class.java) assertThat(suppressed.cause?.cause).isInstanceOf(IllegalStateException::class.java) } @Test fun passingTestWithExemptWtf_shouldPass() { underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } val result = runTestCodeWithRule { Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) } assertThat(result.isSuccess).isTrue() } @Test fun failingTestWithExemptWtf_shouldFail() { underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } val result = runTestCodeWithRule { Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) throw NullPointerException("some npe") } assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions assertThat(suppressedExceptions).isEmpty() } @Test fun passingTestWithOneExemptWtfOfTwo_shouldFail() { underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } val result = runTestCodeWithRule { Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) Log.wtf(TAG, "some terrible failure", IllegalStateException()) } assertThat(result.isFailure).isTrue() val exception = result.exceptionOrNull() assertThat(exception).isInstanceOf(AssertionError::class.java) assertThat(exception?.cause).isInstanceOf(Log.TerribleFailure::class.java) assertThat(exception?.cause?.cause).isInstanceOf(IllegalStateException::class.java) } @Test fun failingTestWithOneExemptWtfOfTwo_shouldFail() { underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } val result = runTestCodeWithRule { Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) Log.wtf(TAG, "some terrible failure", IllegalStateException()) throw NullPointerException("some npe") } assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions assertThat(suppressedExceptions).hasSize(1) val suppressed = suppressedExceptions.first() assertThat(suppressed).isInstanceOf(AssertionError::class.java) assertThat(suppressed.cause).isInstanceOf(Log.TerribleFailure::class.java) assertThat(suppressed.cause?.cause).isInstanceOf(IllegalStateException::class.java) } private fun runTestCodeWithRule(testCode: () -> Unit): Result<Unit> { val testCodeStatement = object : Statement() { override fun evaluate() { testCode() } } val wrappedTest = underTest.apply(testCodeStatement, mock()) return try { wrappedTest.evaluate() Result.success(Unit) } catch (e: Throwable) { Result.failure(e) } } companion object { const val TAG = "LogWtfHandlerRuleTest" const val TAG_EXPECTED = "EXPECTED" } } packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt +41 −61 Original line number Diff line number Diff line Loading @@ -24,21 +24,23 @@ import org.junit.runners.model.Statement class LogWtfHandlerRule : TestRule { private var started = false private var handler = ThrowAndFailAtEnd private var failureLogExemptions = mutableListOf<FailureLogExemption>() override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { started = true val handler = TerribleFailureTestHandler() val originalWtfHandler = Log.setWtfHandler(handler) var failure: Throwable? = null try { base.evaluate() } catch (ex: Throwable) { failure = ex.runAndAddSuppressed { handler.onTestFailure(ex) } failure = ex } finally { failure = failure.runAndAddSuppressed { handler.onTestFinished() } failure = runAndAddSuppressed(failure) { handler.onTestFinished(failureLogExemptions) } Log.setWtfHandler(originalWtfHandler) } if (failure != null) { Loading @@ -48,74 +50,52 @@ class LogWtfHandlerRule : TestRule { } } fun Throwable?.runAndAddSuppressed(block: () -> Unit): Throwable? { /** Adds a log failure exemption. Exemptions are evaluated at the end of the test. */ fun addFailureLogExemption(exemption: FailureLogExemption) { failureLogExemptions.add(exemption) } /** Clears and sets exemptions. Exemptions are evaluated at the end of the test. */ fun resetFailureLogExemptions(vararg exemptions: FailureLogExemption) { failureLogExemptions = exemptions.toMutableList() } private fun runAndAddSuppressed(currentError: Throwable?, block: () -> Unit): Throwable? { try { block() } catch (t: Throwable) { if (this == null) { if (currentError == null) { return t } addSuppressed(t) currentError.addSuppressed(t) } return this } fun setWtfHandler(handler: TerribleFailureTestHandler) { check(!started) { "Should only be called before the test starts" } this.handler = handler } fun interface TerribleFailureTestHandler : TerribleFailureHandler { fun onTestFailure(failure: Throwable) {} fun onTestFinished() {} return currentError } companion object Handlers { val ThrowAndFailAtEnd get() = object : TerribleFailureTestHandler { val failures = mutableListOf<Log.TerribleFailure>() private class TerribleFailureTestHandler : TerribleFailureHandler { private val failureLogs = mutableListOf<FailureLog>() override fun onTerribleFailure( tag: String, what: Log.TerribleFailure, system: Boolean ) { failures.add(what) throw what override fun onTerribleFailure(tag: String, what: Log.TerribleFailure, system: Boolean) { failureLogs.add(FailureLog(tag = tag, failure = what, system = system)) } override fun onTestFailure(failure: Throwable) { super.onTestFailure(failure) fun onTestFinished(exemptions: List<FailureLogExemption>) { val failures = failureLogs.filter { failureLog -> !exemptions.any { it.isFailureLogExempt(failureLog) } } override fun onTestFinished() { if (failures.isNotEmpty()) { throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0]) throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0].failure) } } } val JustThrow = TerribleFailureTestHandler { _, what, _ -> throw what } val JustFailAtEnd get() = object : TerribleFailureTestHandler { val failures = mutableListOf<Log.TerribleFailure>() override fun onTerribleFailure( tag: String, what: Log.TerribleFailure, system: Boolean ) { failures.add(what) } /** All the information from a call to [Log.wtf] that was handed to [TerribleFailureHandler] */ data class FailureLog(val tag: String, val failure: Log.TerribleFailure, val system: Boolean) override fun onTestFinished() { if (failures.isNotEmpty()) { throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0]) } } } /** An interface for exempting a [FailureLog] from causing a test failure. */ fun interface FailureLogExemption { /** Determines whether a log should be except from failing the test. */ fun isFailureLogExempt(log: FailureLog): Boolean } } Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/log/LogWtfHandlerRuleTest.kt 0 → 100644 +155 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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 com.android.systemui.log import android.util.Log import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.google.common.truth.Truth.assertThat import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.model.Statement import org.mockito.kotlin.mock @RunWith(AndroidJUnit4::class) @SmallTest class LogWtfHandlerRuleTest : SysuiTestCase() { val underTest = LogWtfHandlerRule() @Test fun passingTestWithoutWtf_shouldPass() { val result = runTestCodeWithRule { Log.e(TAG, "just an error", IndexOutOfBoundsException()) } assertThat(result.isSuccess).isTrue() } @Test fun passingTestWithWtf_shouldFail() { val result = runTestCodeWithRule { Log.wtf(TAG, "some terrible failure", IllegalStateException()) } assertThat(result.isFailure).isTrue() val exception = result.exceptionOrNull() assertThat(exception).isInstanceOf(AssertionError::class.java) assertThat(exception?.cause).isInstanceOf(Log.TerribleFailure::class.java) assertThat(exception?.cause?.cause).isInstanceOf(IllegalStateException::class.java) } @Test fun failingTestWithoutWtf_shouldFail() { val result = runTestCodeWithRule { Log.e(TAG, "just an error", IndexOutOfBoundsException()) throw NullPointerException("some npe") } assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) } @Test fun failingTestWithWtf_shouldFail() { val result = runTestCodeWithRule { Log.wtf(TAG, "some terrible failure", IllegalStateException()) throw NullPointerException("some npe") } assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions assertThat(suppressedExceptions).hasSize(1) val suppressed = suppressedExceptions.first() assertThat(suppressed).isInstanceOf(AssertionError::class.java) assertThat(suppressed.cause).isInstanceOf(Log.TerribleFailure::class.java) assertThat(suppressed.cause?.cause).isInstanceOf(IllegalStateException::class.java) } @Test fun passingTestWithExemptWtf_shouldPass() { underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } val result = runTestCodeWithRule { Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) } assertThat(result.isSuccess).isTrue() } @Test fun failingTestWithExemptWtf_shouldFail() { underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } val result = runTestCodeWithRule { Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) throw NullPointerException("some npe") } assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions assertThat(suppressedExceptions).isEmpty() } @Test fun passingTestWithOneExemptWtfOfTwo_shouldFail() { underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } val result = runTestCodeWithRule { Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) Log.wtf(TAG, "some terrible failure", IllegalStateException()) } assertThat(result.isFailure).isTrue() val exception = result.exceptionOrNull() assertThat(exception).isInstanceOf(AssertionError::class.java) assertThat(exception?.cause).isInstanceOf(Log.TerribleFailure::class.java) assertThat(exception?.cause?.cause).isInstanceOf(IllegalStateException::class.java) } @Test fun failingTestWithOneExemptWtfOfTwo_shouldFail() { underTest.addFailureLogExemption { it.tag == TAG_EXPECTED } val result = runTestCodeWithRule { Log.wtf(TAG_EXPECTED, "some expected failure", IllegalStateException()) Log.wtf(TAG, "some terrible failure", IllegalStateException()) throw NullPointerException("some npe") } assertThat(result.isFailure).isTrue() assertThat(result.exceptionOrNull()).isInstanceOf(NullPointerException::class.java) val suppressedExceptions = result.exceptionOrNull()!!.suppressedExceptions assertThat(suppressedExceptions).hasSize(1) val suppressed = suppressedExceptions.first() assertThat(suppressed).isInstanceOf(AssertionError::class.java) assertThat(suppressed.cause).isInstanceOf(Log.TerribleFailure::class.java) assertThat(suppressed.cause?.cause).isInstanceOf(IllegalStateException::class.java) } private fun runTestCodeWithRule(testCode: () -> Unit): Result<Unit> { val testCodeStatement = object : Statement() { override fun evaluate() { testCode() } } val wrappedTest = underTest.apply(testCodeStatement, mock()) return try { wrappedTest.evaluate() Result.success(Unit) } catch (e: Throwable) { Result.failure(e) } } companion object { const val TAG = "LogWtfHandlerRuleTest" const val TAG_EXPECTED = "EXPECTED" } }
packages/SystemUI/tests/utils/src/com/android/systemui/log/LogWtfHandlerRule.kt +41 −61 Original line number Diff line number Diff line Loading @@ -24,21 +24,23 @@ import org.junit.runners.model.Statement class LogWtfHandlerRule : TestRule { private var started = false private var handler = ThrowAndFailAtEnd private var failureLogExemptions = mutableListOf<FailureLogExemption>() override fun apply(base: Statement, description: Description): Statement { return object : Statement() { override fun evaluate() { started = true val handler = TerribleFailureTestHandler() val originalWtfHandler = Log.setWtfHandler(handler) var failure: Throwable? = null try { base.evaluate() } catch (ex: Throwable) { failure = ex.runAndAddSuppressed { handler.onTestFailure(ex) } failure = ex } finally { failure = failure.runAndAddSuppressed { handler.onTestFinished() } failure = runAndAddSuppressed(failure) { handler.onTestFinished(failureLogExemptions) } Log.setWtfHandler(originalWtfHandler) } if (failure != null) { Loading @@ -48,74 +50,52 @@ class LogWtfHandlerRule : TestRule { } } fun Throwable?.runAndAddSuppressed(block: () -> Unit): Throwable? { /** Adds a log failure exemption. Exemptions are evaluated at the end of the test. */ fun addFailureLogExemption(exemption: FailureLogExemption) { failureLogExemptions.add(exemption) } /** Clears and sets exemptions. Exemptions are evaluated at the end of the test. */ fun resetFailureLogExemptions(vararg exemptions: FailureLogExemption) { failureLogExemptions = exemptions.toMutableList() } private fun runAndAddSuppressed(currentError: Throwable?, block: () -> Unit): Throwable? { try { block() } catch (t: Throwable) { if (this == null) { if (currentError == null) { return t } addSuppressed(t) currentError.addSuppressed(t) } return this } fun setWtfHandler(handler: TerribleFailureTestHandler) { check(!started) { "Should only be called before the test starts" } this.handler = handler } fun interface TerribleFailureTestHandler : TerribleFailureHandler { fun onTestFailure(failure: Throwable) {} fun onTestFinished() {} return currentError } companion object Handlers { val ThrowAndFailAtEnd get() = object : TerribleFailureTestHandler { val failures = mutableListOf<Log.TerribleFailure>() private class TerribleFailureTestHandler : TerribleFailureHandler { private val failureLogs = mutableListOf<FailureLog>() override fun onTerribleFailure( tag: String, what: Log.TerribleFailure, system: Boolean ) { failures.add(what) throw what override fun onTerribleFailure(tag: String, what: Log.TerribleFailure, system: Boolean) { failureLogs.add(FailureLog(tag = tag, failure = what, system = system)) } override fun onTestFailure(failure: Throwable) { super.onTestFailure(failure) fun onTestFinished(exemptions: List<FailureLogExemption>) { val failures = failureLogs.filter { failureLog -> !exemptions.any { it.isFailureLogExempt(failureLog) } } override fun onTestFinished() { if (failures.isNotEmpty()) { throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0]) throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0].failure) } } } val JustThrow = TerribleFailureTestHandler { _, what, _ -> throw what } val JustFailAtEnd get() = object : TerribleFailureTestHandler { val failures = mutableListOf<Log.TerribleFailure>() override fun onTerribleFailure( tag: String, what: Log.TerribleFailure, system: Boolean ) { failures.add(what) } /** All the information from a call to [Log.wtf] that was handed to [TerribleFailureHandler] */ data class FailureLog(val tag: String, val failure: Log.TerribleFailure, val system: Boolean) override fun onTestFinished() { if (failures.isNotEmpty()) { throw AssertionError("Unexpected Log.wtf calls: $failures", failures[0]) } } } /** An interface for exempting a [FailureLog] from causing a test failure. */ fun interface FailureLogExemption { /** Determines whether a log should be except from failing the test. */ fun isFailureLogExempt(log: FailureLog): Boolean } }