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