1 /* 2 * Copyright (C) 2017 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.device.metric; 17 18 import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey; 19 import com.android.tradefed.build.IBuildInfo; 20 import com.android.tradefed.build.IDeviceBuildInfo; 21 import com.android.tradefed.config.Option; 22 import com.android.tradefed.device.ITestDevice; 23 import com.android.tradefed.device.StubDevice; 24 import com.android.tradefed.invoker.IInvocationContext; 25 import com.android.tradefed.log.LogUtil.CLog; 26 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 27 import com.android.tradefed.result.FailureDescription; 28 import com.android.tradefed.result.ITestInvocationListener; 29 import com.android.tradefed.result.InputStreamSource; 30 import com.android.tradefed.result.LogDataType; 31 import com.android.tradefed.result.TestDescription; 32 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; 33 import com.android.tradefed.testtype.suite.ModuleDefinition; 34 import com.android.tradefed.util.FileUtil; 35 36 import java.io.File; 37 import java.io.IOException; 38 import java.util.ArrayList; 39 import java.util.Arrays; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Set; 45 import java.util.stream.Collectors; 46 47 /** 48 * Base implementation of {@link IMetricCollector} that allows to start and stop collection on 49 * {@link #onTestRunStart(DeviceMetricData)} and {@link #onTestRunEnd(DeviceMetricData, Map)}. 50 */ 51 public class BaseDeviceMetricCollector implements IMetricCollector { 52 53 public static final String TEST_CASE_INCLUDE_GROUP_OPTION = "test-case-include-group"; 54 public static final String TEST_CASE_EXCLUDE_GROUP_OPTION = "test-case-exclude-group"; 55 56 @Option(name = "disable", description = "disables the metrics collector") 57 private boolean mDisable = false; 58 59 @Option( 60 name = TEST_CASE_INCLUDE_GROUP_OPTION, 61 description = 62 "Specify a group to include as part of the collection," 63 + "group can be specified via @MetricOption. Can be repeated." 64 + "Usage: @MetricOption(group = \"groupname\") to your test methods, then" 65 + "use --test-case-include-anotation groupename to only run your group." 66 ) 67 private List<String> mTestCaseIncludeAnnotationGroup = new ArrayList<>(); 68 69 @Option( 70 name = TEST_CASE_EXCLUDE_GROUP_OPTION, 71 description = 72 "Specify a group to exclude from the metric collection," 73 + "group can be specified via @MetricOption. Can be repeated." 74 ) 75 private List<String> mTestCaseExcludeAnnotationGroup = new ArrayList<>(); 76 77 private IInvocationContext mContext; 78 private List<ITestDevice> mRealDeviceList; 79 private ITestInvocationListener mForwarder; 80 private Map<String, File> mTestArtifactFilePathMap = new HashMap<>(); 81 private DeviceMetricData mRunData; 82 private DeviceMetricData mTestData; 83 private String mTag; 84 private String mRunName; 85 86 /** 87 * Variable for whether or not to skip the collection of one test case because it was filtered. 88 */ 89 private boolean mSkipTestCase = false; 90 /** Whether or not the collector was initialized already or not. */ 91 private boolean mWasInitDone = false; 92 93 @Override init( IInvocationContext context, ITestInvocationListener listener)94 public ITestInvocationListener init( 95 IInvocationContext context, ITestInvocationListener listener) { 96 mContext = context; 97 mForwarder = listener; 98 if (mWasInitDone) { 99 throw new IllegalStateException( 100 String.format("init was called a second time on %s", this)); 101 } 102 mWasInitDone = true; 103 return this; 104 } 105 106 @Override getDevices()107 public final List<ITestDevice> getDevices() { 108 return mContext.getDevices(); 109 } 110 111 /** Returns all the non-stub devices from the {@link #getDevices()} list. */ getRealDevices()112 public final List<ITestDevice> getRealDevices() { 113 if (mRealDeviceList == null) { 114 mRealDeviceList = 115 mContext.getDevices() 116 .stream() 117 .filter(d -> !(d.getIDevice() instanceof StubDevice)) 118 .collect(Collectors.toList()); 119 } 120 return mRealDeviceList; 121 } 122 123 /** 124 * Retrieve the file from the test artifacts or module artifacts and cache 125 * it in a map for the subsequent calls. 126 * 127 * @param fileName name of the file to look up in the artifacts. 128 * @return File from the test artifact or module artifact. Returns null 129 * if file is not found. 130 */ getFileFromTestArtifacts(String fileName)131 public File getFileFromTestArtifacts(String fileName) { 132 if (mTestArtifactFilePathMap.containsKey(fileName)) { 133 return mTestArtifactFilePathMap.get(fileName); 134 } 135 136 File resolvedFile = resolveRelativeFilePath(fileName); 137 if (resolvedFile != null) { 138 CLog.i("Using file %s from %s", fileName, resolvedFile.getAbsolutePath()); 139 mTestArtifactFilePathMap.put(fileName, resolvedFile); 140 } 141 return resolvedFile; 142 } 143 144 @Override getBuildInfos()145 public final List<IBuildInfo> getBuildInfos() { 146 return mContext.getBuildInfos(); 147 } 148 149 @Override getInvocationListener()150 public final ITestInvocationListener getInvocationListener() { 151 return mForwarder; 152 } 153 154 @Override onTestRunStart(DeviceMetricData runData)155 public void onTestRunStart(DeviceMetricData runData) { 156 // Does nothing 157 } 158 159 @Override onTestRunEnd( DeviceMetricData runData, final Map<String, Metric> currentRunMetrics)160 public void onTestRunEnd( 161 DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) { 162 // Does nothing 163 } 164 165 @Override onTestStart(DeviceMetricData testData)166 public void onTestStart(DeviceMetricData testData) { 167 // Does nothing 168 } 169 170 @Override onTestFail(DeviceMetricData testData, TestDescription test)171 public void onTestFail(DeviceMetricData testData, TestDescription test) { 172 // Does nothing 173 } 174 175 @Override onTestAssumptionFailure(DeviceMetricData testData, TestDescription test)176 public void onTestAssumptionFailure(DeviceMetricData testData, TestDescription test) { 177 // Does nothing 178 } 179 180 @Override onTestEnd( DeviceMetricData testData, final Map<String, Metric> currentTestCaseMetrics)181 public void onTestEnd( 182 DeviceMetricData testData, final Map<String, Metric> currentTestCaseMetrics) { 183 // Does nothing 184 } 185 186 @Override onTestEnd( DeviceMetricData testData, final Map<String, Metric> currentTestCaseMetrics, TestDescription test)187 public void onTestEnd( 188 DeviceMetricData testData, 189 final Map<String, Metric> currentTestCaseMetrics, 190 TestDescription test) { 191 // Call the default implementation of onTestEnd if not overridden 192 onTestEnd(testData, currentTestCaseMetrics); 193 } 194 195 /** =================================== */ 196 /** Invocation Listeners for forwarding */ 197 @Override invocationStarted(IInvocationContext context)198 public final void invocationStarted(IInvocationContext context) { 199 mForwarder.invocationStarted(context); 200 } 201 202 @Override invocationFailed(Throwable cause)203 public final void invocationFailed(Throwable cause) { 204 mForwarder.invocationFailed(cause); 205 } 206 207 @Override invocationFailed(FailureDescription failure)208 public final void invocationFailed(FailureDescription failure) { 209 mForwarder.invocationFailed(failure); 210 } 211 212 @Override invocationEnded(long elapsedTime)213 public final void invocationEnded(long elapsedTime) { 214 mForwarder.invocationEnded(elapsedTime); 215 } 216 217 @Override testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)218 public final void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { 219 mForwarder.testLog(dataName, dataType, dataStream); 220 } 221 222 @Override testModuleStarted(IInvocationContext moduleContext)223 public final void testModuleStarted(IInvocationContext moduleContext) { 224 mForwarder.testModuleStarted(moduleContext); 225 } 226 227 @Override testModuleEnded()228 public final void testModuleEnded() { 229 mForwarder.testModuleEnded(); 230 } 231 232 /** Test run callbacks */ 233 @Override testRunStarted(String runName, int testCount)234 public final void testRunStarted(String runName, int testCount) { 235 testRunStarted(runName, testCount, 0); 236 } 237 238 @Override testRunStarted(String runName, int testCount, int attemptNumber)239 public final void testRunStarted(String runName, int testCount, int attemptNumber) { 240 testRunStarted(runName, testCount, 0, System.currentTimeMillis()); 241 } 242 243 @Override testRunStarted( String runName, int testCount, int attemptNumber, long startTime)244 public final void testRunStarted( 245 String runName, int testCount, int attemptNumber, long startTime) { 246 mRunData = new DeviceMetricData(mContext); 247 mRunName = runName; 248 try { 249 onTestRunStart(mRunData); 250 } catch (Throwable t) { 251 // Prevent exception from messing up the status reporting. 252 CLog.e(t); 253 } 254 mForwarder.testRunStarted(runName, testCount, attemptNumber, startTime); 255 } 256 257 @Override testRunFailed(String errorMessage)258 public final void testRunFailed(String errorMessage) { 259 mForwarder.testRunFailed(errorMessage); 260 } 261 262 @Override testRunFailed(FailureDescription failure)263 public final void testRunFailed(FailureDescription failure) { 264 mForwarder.testRunFailed(failure); 265 } 266 267 @Override testRunStopped(long elapsedTime)268 public final void testRunStopped(long elapsedTime) { 269 mForwarder.testRunStopped(elapsedTime); 270 } 271 272 @Override testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics)273 public final void testRunEnded(long elapsedTime, HashMap<String, Metric> runMetrics) { 274 try { 275 onTestRunEnd(mRunData, runMetrics); 276 mRunData.addToMetrics(runMetrics); 277 } catch (Throwable t) { 278 // Prevent exception from messing up the status reporting. 279 CLog.e(t); 280 } 281 mForwarder.testRunEnded(elapsedTime, runMetrics); 282 } 283 284 /** Test cases callbacks */ 285 @Override testStarted(TestDescription test)286 public final void testStarted(TestDescription test) { 287 testStarted(test, System.currentTimeMillis()); 288 } 289 290 @Override testStarted(TestDescription test, long startTime)291 public final void testStarted(TestDescription test, long startTime) { 292 mTestData = new DeviceMetricData(mContext); 293 mSkipTestCase = shouldSkip(test); 294 if (!mSkipTestCase) { 295 try { 296 onTestStart(mTestData); 297 } catch (Throwable t) { 298 // Prevent exception from messing up the status reporting. 299 CLog.e(t); 300 } 301 } 302 mForwarder.testStarted(test, startTime); 303 } 304 305 @Override testFailed(TestDescription test, String trace)306 public final void testFailed(TestDescription test, String trace) { 307 if (!mSkipTestCase) { 308 try { 309 onTestFail(mTestData, test); 310 } catch (Throwable t) { 311 // Prevent exception from messing up the status reporting. 312 CLog.e(t); 313 } 314 } 315 mForwarder.testFailed(test, trace); 316 } 317 318 @Override testFailed(TestDescription test, FailureDescription failure)319 public final void testFailed(TestDescription test, FailureDescription failure) { 320 if (!mSkipTestCase) { 321 // Don't collect on not_executed test case 322 if (failure.getFailureStatus() == null 323 || !FailureStatus.NOT_EXECUTED.equals(failure.getFailureStatus())) { 324 try { 325 onTestFail(mTestData, test); 326 } catch (Throwable t) { 327 // Prevent exception from messing up the status reporting. 328 CLog.e(t); 329 } 330 } 331 } 332 mForwarder.testFailed(test, failure); 333 } 334 335 @Override testEnded(TestDescription test, HashMap<String, Metric> testMetrics)336 public final void testEnded(TestDescription test, HashMap<String, Metric> testMetrics) { 337 testEnded(test, System.currentTimeMillis(), testMetrics); 338 } 339 340 @Override testEnded( TestDescription test, long endTime, HashMap<String, Metric> testMetrics)341 public final void testEnded( 342 TestDescription test, long endTime, HashMap<String, Metric> testMetrics) { 343 if (!mSkipTestCase) { 344 try { 345 onTestEnd(mTestData, testMetrics, test); 346 mTestData.addToMetrics(testMetrics); 347 } catch (Throwable t) { 348 // Prevent exception from messing up the status reporting. 349 CLog.e(t); 350 } 351 } else { 352 CLog.d("Skipping %s collection for %s.", this.getClass().getName(), test.toString()); 353 } 354 mForwarder.testEnded(test, endTime, testMetrics); 355 } 356 357 @Override testAssumptionFailure(TestDescription test, String trace)358 public final void testAssumptionFailure(TestDescription test, String trace) { 359 if (!mSkipTestCase) { 360 try { 361 onTestAssumptionFailure(mTestData, test); 362 } catch (Throwable t) { 363 // Prevent exception from messing up the status reporting. 364 CLog.e(t); 365 } 366 } 367 mForwarder.testAssumptionFailure(test, trace); 368 } 369 370 @Override testAssumptionFailure(TestDescription test, FailureDescription failure)371 public final void testAssumptionFailure(TestDescription test, FailureDescription failure) { 372 if (!mSkipTestCase) { 373 try { 374 onTestAssumptionFailure(mTestData, test); 375 } catch (Throwable t) { 376 // Prevent exception from messing up the status reporting. 377 CLog.e(t); 378 } 379 } 380 mForwarder.testAssumptionFailure(test, failure); 381 } 382 383 @Override testIgnored(TestDescription test)384 public final void testIgnored(TestDescription test) { 385 mForwarder.testIgnored(test); 386 } 387 388 /** {@inheritDoc} */ 389 @Override isDisabled()390 public final boolean isDisabled() { 391 return mDisable; 392 } 393 394 /** {@inheritDoc} */ 395 @Override setDisable(boolean isDisabled)396 public final void setDisable(boolean isDisabled) { 397 mDisable = isDisabled; 398 } 399 400 /** 401 * Sets the {@code mTag} of the collector. It can be used to specify the interval of the 402 * collector. 403 * 404 * @param tag the unique identifier of the collector. 405 */ setTag(String tag)406 public void setTag(String tag) { 407 mTag = tag; 408 } 409 410 /** 411 * Returns the identifier {@code mTag} of the collector. 412 * 413 * @return mTag, the unique identifier of the collector. 414 */ getTag()415 public String getTag() { 416 return mTag; 417 } 418 419 /** 420 * Returns the name of test run {@code mRunName} that triggers the collector. 421 * 422 * @return mRunName, the current test run name. 423 */ getRunName()424 public String getRunName() { 425 return mRunName; 426 } 427 428 /** 429 * Helper to decide if a test case should or not run the collector method associated. 430 * 431 * @param desc the identifier of the test case. 432 * @return True the collector should be skipped. False otherwise. 433 */ shouldSkip(TestDescription desc)434 private boolean shouldSkip(TestDescription desc) { 435 Set<String> testCaseGroups = new HashSet<>(); 436 if (desc.getAnnotation(MetricOption.class) != null) { 437 String groupName = desc.getAnnotation(MetricOption.class).group(); 438 testCaseGroups.addAll(Arrays.asList(groupName.split(","))); 439 } else { 440 // Add empty group name for default case. 441 testCaseGroups.add(""); 442 } 443 // Exclusion has priority: if any of the groups is excluded, exclude the test case. 444 for (String groupName : testCaseGroups) { 445 if (mTestCaseExcludeAnnotationGroup.contains(groupName)) { 446 return true; 447 } 448 } 449 // Inclusion filter: if any of the group is included, include the test case. 450 for (String includeGroupName : mTestCaseIncludeAnnotationGroup) { 451 if (testCaseGroups.contains(includeGroupName)) { 452 return false; 453 } 454 } 455 456 // If we had filters and did not match any groups 457 if (!mTestCaseIncludeAnnotationGroup.isEmpty()) { 458 return true; 459 } 460 return false; 461 } 462 463 /** 464 * Resolves the relative path of the file from the test artifacts 465 * directory or module directory. 466 * 467 * @param fileName file name that needs to be resolved. 468 * @return File file resolved for the given file name. Returns null 469 * if file not found. 470 */ resolveRelativeFilePath(String fileName)471 private File resolveRelativeFilePath(String fileName) { 472 IBuildInfo buildInfo = getBuildInfos().get(0); 473 String mModuleName = null; 474 // Retrieve the module name. 475 if (mContext.getAttributes().get(ModuleDefinition.MODULE_NAME) != null) { 476 mModuleName = mContext.getAttributes().get(ModuleDefinition.MODULE_NAME) 477 .get(0); 478 } 479 480 File src = null; 481 if (buildInfo != null) { 482 src = buildInfo.getFile(fileName); 483 if (src != null && src.exists()) { 484 return src; 485 } 486 } 487 488 if (buildInfo instanceof IDeviceBuildInfo) { 489 IDeviceBuildInfo deviceBuild = (IDeviceBuildInfo) buildInfo; 490 File testDir = deviceBuild.getTestsDir(); 491 List<File> scanDirs = new ArrayList<>(); 492 // If it exists, always look first in the ANDROID_TARGET_OUT_TESTCASES 493 File targetTestCases = deviceBuild.getFile(BuildInfoFileKey.TARGET_LINKED_DIR); 494 if (targetTestCases != null) { 495 scanDirs.add(targetTestCases); 496 } 497 // If not, look into the test directory. 498 if (testDir != null) { 499 scanDirs.add(testDir); 500 } 501 502 if (mModuleName != null) { 503 // Use module name as a discriminant to find some files 504 if (testDir != null) { 505 try { 506 File moduleDir = FileUtil.findDirectory( 507 mModuleName, scanDirs.toArray(new File[] {})); 508 if (moduleDir != null) { 509 // If the spec is pushing the module itself 510 if (mModuleName.equals(fileName)) { 511 // If that's the main binary generated by the target, we push the 512 // full directory 513 return moduleDir; 514 } 515 // Search the module directory if it exists use it in priority 516 src = FileUtil.findFile(fileName, null, moduleDir); 517 if (src != null) { 518 CLog.i("Retrieving src file from" + src.getAbsolutePath()); 519 return src; 520 } 521 } else { 522 CLog.d("Did not find any module directory for '%s'", mModuleName); 523 } 524 525 } catch (IOException e) { 526 CLog.w( 527 "Something went wrong while searching for the module '%s' " 528 + "directory.", 529 mModuleName); 530 } 531 } 532 } 533 534 for (File searchDir : scanDirs) { 535 try { 536 Set<File> allMatch = FileUtil.findFilesObject(searchDir, fileName); 537 if (allMatch.size() > 1) { 538 CLog.d( 539 "Several match for filename '%s', searching for top-level match.", 540 fileName); 541 for (File f : allMatch) { 542 if (f.getParent().equals(searchDir.getAbsolutePath())) { 543 return f; 544 } 545 } 546 } else if (allMatch.size() == 1) { 547 return allMatch.iterator().next(); 548 } 549 } catch (IOException e) { 550 CLog.w("Failed to find test files from directory."); 551 } 552 } 553 } 554 return src; 555 } 556 557 } 558