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