• 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 com.android.ravenwoodtest.runnercallbacktests;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 import static com.google.common.truth.Truth.assertWithMessage;
20 
21 import android.platform.test.annotations.NoRavenizer;
22 import android.platform.test.ravenwood.RavenwoodAwareTestRunner;
23 import android.platform.test.ravenwood.RavenwoodConfigPrivate;
24 import android.util.Log;
25 
26 import junitparams.JUnitParamsRunner;
27 import junitparams.Parameters;
28 
29 import org.junit.Ignore;
30 import org.junit.Test;
31 import org.junit.runner.Description;
32 import org.junit.runner.JUnitCore;
33 import org.junit.runner.Result;
34 import org.junit.runner.RunWith;
35 import org.junit.runner.notification.Failure;
36 import org.junit.runner.notification.RunListener;
37 
38 import java.lang.annotation.ElementType;
39 import java.lang.annotation.Retention;
40 import java.lang.annotation.RetentionPolicy;
41 import java.lang.annotation.Target;
42 import java.util.ArrayList;
43 import java.util.Arrays;
44 import java.util.function.BiConsumer;
45 
46 
47 /**
48  * Base class for tests to make sure {@link RavenwoodAwareTestRunner} produces expected callbacks
49  * in various situations. (most of them are error situations.)
50  *
51  * Subclasses must contain test classes as static inner classes with an {@link Expected} annotation.
52  * This class finds them using reflections and run them one by one directly using {@link JUnitCore},
53  * and check the callbacks.
54  *
55  * Subclasses do no need to have any test methods.
56  *
57  * The {@link Expected} annotation must contain the expected result as a string.
58  *
59  * This test abuses the fact that atest + tradefed + junit won't run nested classes automatically.
60  * (So atest won't show any results directly from the nested classes.)
61  *
62  * The actual test method is {@link #doTest}, which is executed for each target test class, using
63  * junit-params.
64  */
65 @RunWith(JUnitParamsRunner.class)
66 @NoRavenizer // This class shouldn't be executed with RavenwoodAwareTestRunner.
67 public abstract class RavenwoodRunnerTestBase {
68     private static final String TAG = "RavenwoodRunnerTestBase";
69 
70     /**
71      * Annotation to specify the expected result for a class.
72      */
73     @Target({ElementType.TYPE})
74     @Retention(RetentionPolicy.RUNTIME)
75     public @interface Expected {
value()76         String value();
77     }
78 
79     /**
80      * Take a multiline string, strip all of them, remove empty lines, and return it.
81      */
stripMultiLines(String resultString)82     private static String stripMultiLines(String resultString) {
83         var list = new ArrayList<String>();
84         for (var line : resultString.split("\n")) {
85             var s = line.strip();
86             if (s.length() > 0) {
87                 list.add(s);
88             }
89         }
90         return String.join("\n", list);
91     }
92 
93     /**
94      * Extract the expected result from @Expected.
95      */
getExpectedResult(Class<?> testClazz)96     private String getExpectedResult(Class<?> testClazz) {
97         var expect = testClazz.getAnnotation(Expected.class);
98         return stripMultiLines(expect.value());
99     }
100 
101     /**
102      * List all the nested classrs with an {@link Expected} annotation in a given class.
103      */
getTestClasses()104     public Class<?>[] getTestClasses() {
105         var thisClass = this.getClass();
106         var ret = Arrays.stream(thisClass.getNestMembers())
107                 .filter((c) -> c.getAnnotation(Expected.class) != null)
108                 .filter((c) -> c.getAnnotation(Ignore.class) == null)
109                 .toArray(Class[]::new);
110 
111         assertThat(ret.length).isGreaterThan(0);
112 
113         return ret;
114     }
115 
116     /**
117      * This is the actual test method. We use junit-params to run this method for each target
118      * test class, which are returned by {@link #getTestClasses}.
119      *
120      * It runs each test class, and compare the result collected with
121      * {@link ResultCollectingListener} to expected results (as strings).
122      */
123     @Test
124     @Parameters(method = "getTestClasses")
doTest(Class<?> testClazz)125     public void doTest(Class<?> testClazz) {
126         doTest(testClazz, getExpectedResult(testClazz));
127     }
128 
129     /**
130      * Run a given test class, and compare the result collected with
131      * {@link ResultCollectingListener} to expected results (as a string).
132      */
doTest(Class<?> testClazz, String expectedResult)133     private void doTest(Class<?> testClazz, String expectedResult) {
134         Log.i(TAG, "Running test for " + testClazz);
135         var junitCore = new JUnitCore();
136 
137         // Create a listener.
138         var listener = new ResultCollectingListener();
139         junitCore.addListener(listener);
140 
141         // Set a listener to critical errors. This will also prevent
142         // {@link RavenwoodAwareTestRunner} from calling System.exit() when there's
143         // a critical error.
144         RavenwoodConfigPrivate.setCriticalErrorHandler(listener.sCriticalErrorListener);
145 
146         try {
147             // Run the test class.
148             junitCore.run(testClazz);
149         } finally {
150             // Clear the critical error listener.
151             RavenwoodConfigPrivate.setCriticalErrorHandler(null);
152         }
153 
154         // Check the result.
155         assertWithMessage("Failure in test class: " + testClazz.getCanonicalName() + "]")
156                 .that(listener.getResult())
157                 .isEqualTo(expectedResult);
158     }
159 
160     /**
161      * A JUnit RunListener that collects all the callbacks as a single string.
162      */
163     private static class ResultCollectingListener extends RunListener {
164         private final ArrayList<String> mResult = new ArrayList<>();
165 
166         public final BiConsumer<String, Throwable> sCriticalErrorListener = (message, th) -> {
167             mResult.add("criticalError: " + message + ": " + th.getMessage());
168         };
169 
170         @Override
testRunStarted(Description description)171         public void testRunStarted(Description description) throws Exception {
172             mResult.add("testRunStarted: " + description);
173         }
174 
175         @Override
testRunFinished(Result result)176         public void testRunFinished(Result result) throws Exception {
177             mResult.add("testRunFinished: "
178                     + result.getRunCount() + ","
179                     + result.getFailureCount() + ","
180                     + result.getAssumptionFailureCount() + ","
181                     + result.getIgnoreCount());
182         }
183 
184         @Override
testSuiteStarted(Description description)185         public void testSuiteStarted(Description description) throws Exception {
186             mResult.add("testSuiteStarted: " + description);
187         }
188 
189         @Override
testSuiteFinished(Description description)190         public void testSuiteFinished(Description description) throws Exception {
191             mResult.add("testSuiteFinished: " + description);
192         }
193 
194         @Override
testStarted(Description description)195         public void testStarted(Description description) throws Exception {
196             mResult.add("testStarted: " + description);
197         }
198 
199         @Override
testFinished(Description description)200         public void testFinished(Description description) throws Exception {
201             mResult.add("testFinished: " + description);
202         }
203 
204         @Override
testFailure(Failure failure)205         public void testFailure(Failure failure) throws Exception {
206             mResult.add("testFailure: " + failure.getException().getMessage());
207         }
208 
209         @Override
testAssumptionFailure(Failure failure)210         public void testAssumptionFailure(Failure failure) {
211             mResult.add("testAssumptionFailure: " + failure.getException().getMessage());
212         }
213 
214         @Override
testIgnored(Description description)215         public void testIgnored(Description description) throws Exception {
216             mResult.add("testIgnored: " + description);
217         }
218 
getResult()219         public String getResult() {
220             return String.join("\n", mResult);
221         }
222     }
223 }
224