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 * @Rule 199 * public TestMetrics metrics = new TestMetrics(); 200 * 201 * @Test 202 * public void testFoo() { 203 * metrics.addTestMetric("key", "value"); 204 * metrics.addTestMetric("key2", "value2"); 205 * } 206 * 207 * @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 * @Rule 304 * public TestLogData logs = new TestLogData(); 305 * 306 * @Test 307 * public void testFoo() { 308 * logs.addTestLog("logcat", LogDataType.LOGCAT, new FileInputStreamSource(logcatFile)); 309 * } 310 * 311 * @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