1 /*
2  * Copyright (C) 2016 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.tradefed.testtype;
17 
18 import com.android.tradefed.build.BuildRetrievalError;
19 import com.android.tradefed.config.ConfigurationException;
20 import com.android.tradefed.config.DynamicRemoteFileResolver;
21 import com.android.tradefed.config.Option;
22 import com.android.tradefed.config.OptionSetter;
23 import com.android.tradefed.invoker.TestInformation;
24 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
25 import com.android.tradefed.result.InputStreamSource;
26 import com.android.tradefed.result.LogDataType;
27 import com.android.tradefed.testtype.MetricTestCase.LogHolder;
28 import com.android.tradefed.testtype.junit4.AfterClassWithInfo;
29 import com.android.tradefed.testtype.junit4.BeforeClassWithInfo;
30 import com.android.tradefed.testtype.junit4.CarryDnaeError;
31 import com.android.tradefed.testtype.junit4.RunAftersWithInfo;
32 import com.android.tradefed.testtype.junit4.RunBeforesWithInfo;
33 import com.android.tradefed.testtype.junit4.RunNotifierWrapper;
34 import com.android.tradefed.util.FileUtil;
35 import com.android.tradefed.util.proto.TfMetricProtoUtil;
36 
37 import com.google.common.annotations.VisibleForTesting;
38 
39 import org.junit.rules.ExternalResource;
40 import org.junit.rules.TestRule;
41 import org.junit.runner.Description;
42 import org.junit.runner.notification.RunNotifier;
43 import org.junit.runners.BlockJUnit4ClassRunner;
44 import org.junit.runners.model.FrameworkMethod;
45 import org.junit.runners.model.InitializationError;
46 import org.junit.runners.model.Statement;
47 
48 import java.io.File;
49 import java.lang.annotation.Annotation;
50 import java.util.ArrayList;
51 import java.util.HashMap;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.Set;
55 
56 /**
57  * JUnit4 test runner that also accommodates {@link IDeviceTest}. Should be specified above JUnit4
58  * Test with a RunWith annotation.
59  */
60 public class DeviceJUnit4ClassRunner extends BlockJUnit4ClassRunner
61         implements IAbiReceiver, ISetOptionReceiver, ITestInformationReceiver {
62     private IAbi mAbi;
63     private TestInformation mTestInformation;
64 
65     /** Keep track of the list of downloaded files. */
66     private List<File> mDownloadedFiles = new ArrayList<>();
67 
68     @Option(name = HostTest.SET_OPTION_NAME, description = HostTest.SET_OPTION_DESC)
69     private List<String> mKeyValueOptions = new ArrayList<>();
70 
DeviceJUnit4ClassRunner(Class<?> klass)71     public DeviceJUnit4ClassRunner(Class<?> klass) throws InitializationError {
72         super(klass);
73     }
74 
75     /**
76      * We override createTest in order to set the device.
77      */
78     @Override
createTest()79     protected Object createTest() throws Exception {
80         Object testObj = super.createTest();
81         if (testObj instanceof IDeviceTest) {
82             ((IDeviceTest) testObj).setDevice(mTestInformation.getDevice());
83         }
84         if (testObj instanceof IBuildReceiver) {
85             ((IBuildReceiver) testObj).setBuild(mTestInformation.getBuildInfo());
86         }
87         // We are more flexible about abi information since not always available.
88         if (testObj instanceof IAbiReceiver) {
89             ((IAbiReceiver) testObj).setAbi(mAbi);
90         }
91         if (testObj instanceof IInvocationContextReceiver) {
92             ((IInvocationContextReceiver) testObj)
93                     .setInvocationContext(mTestInformation.getContext());
94         }
95         if (testObj instanceof ITestInformationReceiver) {
96             ((ITestInformationReceiver) testObj).setTestInformation(mTestInformation);
97         }
98         // Set options of test object
99         HostTest.setOptionToLoadedObject(testObj, mKeyValueOptions);
100         mDownloadedFiles.addAll(resolveRemoteFileForObject(testObj));
101         return testObj;
102     }
103 
104     @Override
runChild(FrameworkMethod method, RunNotifier notifier)105     protected void runChild(FrameworkMethod method, RunNotifier notifier) {
106         RunNotifierWrapper wrapper = new RunNotifierWrapper(notifier);
107         try {
108             super.runChild(method, wrapper);
109         } finally {
110             for (File f : mDownloadedFiles) {
111                 FileUtil.recursiveDelete(f);
112             }
113         }
114         if (wrapper.getDeviceNotAvailableException() != null) {
115             throw new CarryDnaeError(wrapper.getDeviceNotAvailableException());
116         }
117     }
118 
119     @Override
withBeforeClasses(Statement statement)120     protected Statement withBeforeClasses(Statement statement) {
121         Statement s = super.withBeforeClasses(statement);
122 
123         List<FrameworkMethod> beforesWithDevice =
124                 getTestClass().getAnnotatedMethods(BeforeClassWithInfo.class);
125         return beforesWithDevice.isEmpty()
126                 ? s
127                 : new RunBeforesWithInfo(statement, beforesWithDevice, mTestInformation);
128     }
129 
130     @Override
withAfterClasses(Statement statement)131     protected Statement withAfterClasses(Statement statement) {
132         Statement s = super.withAfterClasses(statement);
133 
134         List<FrameworkMethod> aftersWithDevice =
135                 getTestClass().getAnnotatedMethods(AfterClassWithInfo.class);
136         return aftersWithDevice.isEmpty()
137                 ? s
138                 : new RunAftersWithInfo(statement, aftersWithDevice, mTestInformation);
139     }
140 
141     @Override
run(RunNotifier notifier)142     public void run(RunNotifier notifier) {
143         RunNotifierWrapper wrapper = new RunNotifierWrapper(notifier);
144         super.run(wrapper);
145 
146         if (wrapper.getDeviceNotAvailableException() != null) {
147             throw new CarryDnaeError(wrapper.getDeviceNotAvailableException());
148         }
149     }
150 
151     @Override
setAbi(IAbi abi)152     public void setAbi(IAbi abi) {
153         mAbi = abi;
154     }
155 
156     @Override
getAbi()157     public IAbi getAbi() {
158         return mAbi;
159     }
160 
161     @Override
setTestInformation(TestInformation testInformation)162     public void setTestInformation(TestInformation testInformation) {
163         mTestInformation = testInformation;
164     }
165 
166     @Override
getTestInformation()167     public TestInformation getTestInformation() {
168         return mTestInformation;
169     }
170 
171     @VisibleForTesting
createResolver()172     DynamicRemoteFileResolver createResolver() {
173         DynamicRemoteFileResolver resolver = new DynamicRemoteFileResolver();
174         if (mTestInformation != null) {
175             resolver.setDevice(mTestInformation.getDevice());
176         }
177         return resolver;
178     }
179 
resolveRemoteFileForObject(Object obj)180     private Set<File> resolveRemoteFileForObject(Object obj) {
181         try {
182             OptionSetter setter = new OptionSetter(obj);
183             return setter.validateRemoteFilePath(createResolver());
184         } catch (BuildRetrievalError | ConfigurationException e) {
185             throw new RuntimeException(e);
186         }
187     }
188 
189     /**
190      * Implementation of {@link ExternalResource} and {@link TestRule}. This rule allows to log
191      * metrics during a test case (inside @Test). It guarantees that the metrics map is cleaned
192      * between tests, so the same rule object can be re-used.
193      *
194      * <pre>Example:
195      * &#064;Rule
196      * public TestMetrics metrics = new TestMetrics();
197      *
198      * &#064;Test
199      * public void testFoo() {
200      *     metrics.addTestMetric("key", "value");
201      *     metrics.addTestMetric("key2", "value2");
202      * }
203      *
204      * &#064;Test
205      * public void testFoo2() {
206      *     metrics.addTestMetric("key3", "value3");
207      * }
208      * </pre>
209      */
210     public static class TestMetrics extends ExternalResource {
211 
212         Description mDescription;
213         private Map<String, String> mMetrics = new HashMap<>();
214         private HashMap<String, Metric> mProtoMetrics = new HashMap<>();
215 
216         @Override
apply(Statement base, Description description)217         public Statement apply(Statement base, Description description) {
218             mDescription = description;
219             return super.apply(base, description);
220         }
221 
222         /**
223          * Log a metric entry for the test case. Each key within a test case must be unique
224          * otherwise it will override the previous value.
225          *
226          * @param key The key of the metric.
227          * @param value The value associated to the key.
228          */
addTestMetric(String key, String value)229         public void addTestMetric(String key, String value) {
230             mMetrics.put(key, value);
231         }
232 
233         /**
234          * Log a metric entry in proto format for the test case. Each key within a test case must be
235          * unique otherwise it will override the previous value.
236          *
237          * @param key The key of the metric.
238          * @param metric The value associated to the key.
239          */
addTestMetric(String key, Metric metric)240         public void addTestMetric(String key, Metric metric) {
241             mProtoMetrics.put(key, metric);
242         }
243 
244         @Override
before()245         protected void before() throws Throwable {
246             mMetrics = new HashMap<>();
247             mProtoMetrics = new HashMap<>();
248         }
249 
250         @Override
after()251         protected void after() {
252             // we inject a Description with an annotation carrying metrics.
253             // We have to go around, since Description cannot be extended and RunNotifier
254             // does not give us a lot of flexibility to find our metrics back.
255             mProtoMetrics.putAll(TfMetricProtoUtil.upgradeConvert(mMetrics));
256             mDescription.addChild(
257                     Description.createTestDescription(
258                             "METRICS", "METRICS", new MetricAnnotation(mProtoMetrics)));
259         }
260     }
261 
262     /** Fake annotation meant to carry metrics to the reporters. */
263     public static class MetricAnnotation implements Annotation {
264 
265         public HashMap<String, Metric> mMetrics = new HashMap<>();
266 
MetricAnnotation(HashMap<String, Metric> metrics)267         public MetricAnnotation(HashMap<String, Metric> metrics) {
268             mMetrics.putAll(metrics);
269         }
270 
271         @Override
annotationType()272         public Class<? extends Annotation> annotationType() {
273             return null;
274         }
275     }
276 
277     /**
278      * Implementation of {@link ExternalResource} and {@link TestRule}. This rule allows to log logs
279      * during a test case (inside @Test). It guarantees that the log list is cleaned between tests,
280      * so the same rule object can be re-used.
281      *
282      * <pre>Example:
283      * &#064;Rule
284      * public TestLogData logs = new TestLogData();
285      *
286      * &#064;Test
287      * public void testFoo() {
288      *     logs.addTestLog("logcat", LogDataType.LOGCAT, new FileInputStreamSource(logcatFile));
289      * }
290      *
291      * &#064;Test
292      * public void testFoo2() {
293      *     logs.addTestLog("logcat2", LogDataType.LOGCAT, new FileInputStreamSource(logcatFile2));
294      * }
295      * </pre>
296      */
297     public static class TestLogData extends ExternalResource {
298         private Description mDescription;
299         private List<LogHolder> mLogs = new ArrayList<>();
300 
301         @Override
apply(Statement base, Description description)302         public Statement apply(Statement base, Description description) {
303             mDescription = description;
304             return super.apply(base, description);
305         }
306 
addTestLog( String dataName, LogDataType dataType, InputStreamSource dataStream)307         public final void addTestLog(
308                 String dataName, LogDataType dataType, InputStreamSource dataStream) {
309             mLogs.add(new LogHolder(dataName, dataType, dataStream));
310         }
311 
312         @Override
after()313         protected void after() {
314             // we inject a Description with an annotation carrying metrics.
315             // We have to go around, since Description cannot be extended and RunNotifier
316             // does not give us a lot of flexibility to find our metrics back.
317             mDescription.addChild(
318                     Description.createTestDescription("LOGS", "LOGS", new LogAnnotation(mLogs)));
319         }
320     }
321 
322     /** Fake annotation meant to carry logs to the reporters. */
323     public static class LogAnnotation implements Annotation {
324 
325         public List<LogHolder> mLogs = new ArrayList<>();
326 
LogAnnotation(List<LogHolder> logs)327         public LogAnnotation(List<LogHolder> logs) {
328             mLogs.addAll(logs);
329         }
330 
331         @Override
annotationType()332         public Class<? extends Annotation> annotationType() {
333             return null;
334         }
335     }
336 }
337