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 * @Rule 196 * public TestMetrics metrics = new TestMetrics(); 197 * 198 * @Test 199 * public void testFoo() { 200 * metrics.addTestMetric("key", "value"); 201 * metrics.addTestMetric("key2", "value2"); 202 * } 203 * 204 * @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 * @Rule 284 * public TestLogData logs = new TestLogData(); 285 * 286 * @Test 287 * public void testFoo() { 288 * logs.addTestLog("logcat", LogDataType.LOGCAT, new FileInputStreamSource(logcatFile)); 289 * } 290 * 291 * @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