1 /* 2 * Copyright (C) 2024 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 package android.platform.test.ravenwood; 17 18 import android.util.Log; 19 20 import org.junit.AssumptionViolatedException; 21 import org.junit.runner.Description; 22 import org.junit.runner.Result; 23 import org.junit.runner.notification.Failure; 24 import org.junit.runner.notification.RunListener; 25 import org.junit.runner.notification.RunNotifier; 26 import org.junit.runner.notification.StoppedByUserException; 27 import org.junit.runners.model.MultipleFailureException; 28 29 import java.util.ArrayList; 30 import java.util.Stack; 31 32 /** 33 * A run notifier that wraps another notifier and provides the following features: 34 * - Handle a failure that happened before testStarted and testEnded (typically that means 35 * it's from @BeforeClass or @AfterClass, or a @ClassRule) and deliver it as if 36 * individual tests in the class reported it. This is for b/364395552. 37 * 38 * - Logging. 39 */ 40 class RavenwoodRunNotifier extends RunNotifier { 41 private final RunNotifier mRealNotifier; 42 43 private final Stack<Description> mSuiteStack = new Stack<>(); 44 private Description mCurrentSuite = null; 45 private final ArrayList<Throwable> mOutOfTestFailures = new ArrayList<>(); 46 47 private boolean mBeforeTest = true; 48 private boolean mAfterTest = false; 49 RavenwoodRunNotifier(RunNotifier realNotifier)50 RavenwoodRunNotifier(RunNotifier realNotifier) { 51 mRealNotifier = realNotifier; 52 } 53 isInTest()54 private boolean isInTest() { 55 return !mBeforeTest && !mAfterTest; 56 } 57 58 @Override addListener(RunListener listener)59 public void addListener(RunListener listener) { 60 mRealNotifier.addListener(listener); 61 } 62 63 @Override removeListener(RunListener listener)64 public void removeListener(RunListener listener) { 65 mRealNotifier.removeListener(listener); 66 } 67 68 @Override addFirstListener(RunListener listener)69 public void addFirstListener(RunListener listener) { 70 mRealNotifier.addFirstListener(listener); 71 } 72 73 @Override fireTestRunStarted(Description description)74 public void fireTestRunStarted(Description description) { 75 Log.i(RavenwoodAwareTestRunner.TAG, "testRunStarted: " + description); 76 mRealNotifier.fireTestRunStarted(description); 77 } 78 79 @Override fireTestRunFinished(Result result)80 public void fireTestRunFinished(Result result) { 81 Log.i(RavenwoodAwareTestRunner.TAG, "testRunFinished: " 82 + result.getRunCount() + "," 83 + result.getFailureCount() + "," 84 + result.getAssumptionFailureCount() + "," 85 + result.getIgnoreCount()); 86 mRealNotifier.fireTestRunFinished(result); 87 } 88 89 @Override fireTestSuiteStarted(Description description)90 public void fireTestSuiteStarted(Description description) { 91 Log.i(RavenwoodAwareTestRunner.TAG, "testSuiteStarted: " + description); 92 mRealNotifier.fireTestSuiteStarted(description); 93 94 mBeforeTest = true; 95 mAfterTest = false; 96 97 // Keep track of the current suite, needed if the outer test is a Suite, 98 // in which case its children are test classes. (not test methods) 99 mCurrentSuite = description; 100 mSuiteStack.push(description); 101 102 mOutOfTestFailures.clear(); 103 } 104 105 @Override fireTestSuiteFinished(Description description)106 public void fireTestSuiteFinished(Description description) { 107 Log.i(RavenwoodAwareTestRunner.TAG, "testSuiteFinished: " + description); 108 mRealNotifier.fireTestSuiteFinished(description); 109 110 maybeHandleOutOfTestFailures(); 111 112 mBeforeTest = true; 113 mAfterTest = false; 114 115 // Restore the upper suite. 116 mSuiteStack.pop(); 117 mCurrentSuite = mSuiteStack.size() == 0 ? null : mSuiteStack.peek(); 118 } 119 120 @Override fireTestStarted(Description description)121 public void fireTestStarted(Description description) throws StoppedByUserException { 122 Log.i(RavenwoodAwareTestRunner.TAG, "testStarted: " + description); 123 mRealNotifier.fireTestStarted(description); 124 125 mAfterTest = false; 126 mBeforeTest = false; 127 } 128 129 @Override fireTestFailure(Failure failure)130 public void fireTestFailure(Failure failure) { 131 Log.i(RavenwoodAwareTestRunner.TAG, "testFailure: " + failure); 132 133 if (isInTest()) { 134 mRealNotifier.fireTestFailure(failure); 135 } else { 136 mOutOfTestFailures.add(failure.getException()); 137 } 138 } 139 140 @Override fireTestAssumptionFailed(Failure failure)141 public void fireTestAssumptionFailed(Failure failure) { 142 Log.i(RavenwoodAwareTestRunner.TAG, "testAssumptionFailed: " + failure); 143 144 if (isInTest()) { 145 mRealNotifier.fireTestAssumptionFailed(failure); 146 } else { 147 mOutOfTestFailures.add(failure.getException()); 148 } 149 } 150 151 @Override fireTestIgnored(Description description)152 public void fireTestIgnored(Description description) { 153 Log.i(RavenwoodAwareTestRunner.TAG, "testIgnored: " + description); 154 mRealNotifier.fireTestIgnored(description); 155 } 156 157 @Override fireTestFinished(Description description)158 public void fireTestFinished(Description description) { 159 Log.i(RavenwoodAwareTestRunner.TAG, "testFinished: " + description); 160 mRealNotifier.fireTestFinished(description); 161 162 mAfterTest = true; 163 } 164 165 @Override pleaseStop()166 public void pleaseStop() { 167 Log.w(RavenwoodAwareTestRunner.TAG, "pleaseStop:"); 168 mRealNotifier.pleaseStop(); 169 } 170 171 /** 172 * At the end of each Suite, we handle failures happened out of test methods. 173 * (typically in @BeforeClass or @AfterClasses) 174 * 175 * This is to work around b/364395552. 176 */ maybeHandleOutOfTestFailures()177 private boolean maybeHandleOutOfTestFailures() { 178 if (mOutOfTestFailures.size() == 0) { 179 return false; 180 } 181 Throwable th; 182 if (mOutOfTestFailures.size() == 1) { 183 th = mOutOfTestFailures.get(0); 184 } else { 185 th = new MultipleFailureException(mOutOfTestFailures); 186 } 187 if (mBeforeTest) { 188 reportBeforeTestFailure(mCurrentSuite, th); 189 return true; 190 } 191 if (mAfterTest) { 192 reportAfterTestFailure(th); 193 return true; 194 } 195 return false; 196 } 197 reportBeforeTestFailure(Description suiteDesc, Throwable th)198 public void reportBeforeTestFailure(Description suiteDesc, Throwable th) { 199 // If a failure happens befere running any tests, we'll need to pretend 200 // as if each test in the suite reported the failure, to work around b/364395552. 201 for (var child : suiteDesc.getChildren()) { 202 if (child.isSuite()) { 203 // If the chiil is still a "parent" -- a test class or a test suite 204 // -- propagate to its children. 205 mRealNotifier.fireTestSuiteStarted(child); 206 reportBeforeTestFailure(child, th); 207 mRealNotifier.fireTestSuiteFinished(child); 208 } else { 209 mRealNotifier.fireTestStarted(child); 210 Failure f = new Failure(child, th); 211 if (th instanceof AssumptionViolatedException) { 212 mRealNotifier.fireTestAssumptionFailed(f); 213 } else { 214 mRealNotifier.fireTestFailure(f); 215 } 216 mRealNotifier.fireTestFinished(child); 217 } 218 } 219 } 220 reportAfterTestFailure(Throwable th)221 public void reportAfterTestFailure(Throwable th) { 222 // Unfortunately, there's no good way to report it, so kill the own process. 223 RavenwoodAwareTestRunner.onCriticalError( 224 "Failures detected in @AfterClass, which would be swallowed by tradefed", 225 th); 226 } 227 } 228