• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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