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