1 /* 2 * Copyright (C) 2010 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.invoker; 17 18 import com.android.ddmlib.Log.LogLevel; 19 import com.android.tradefed.build.BuildInfo; 20 import com.android.tradefed.build.BuildRetrievalError; 21 import com.android.tradefed.build.IBuildInfo; 22 import com.android.tradefed.command.CommandRunner.ExitCode; 23 import com.android.tradefed.command.CommandScheduler; 24 import com.android.tradefed.command.ICommandScheduler.IScheduledInvocationListener; 25 import com.android.tradefed.config.ConfigurationException; 26 import com.android.tradefed.config.DynamicRemoteFileResolver; 27 import com.android.tradefed.config.GlobalConfiguration; 28 import com.android.tradefed.config.IConfiguration; 29 import com.android.tradefed.device.DeviceManager; 30 import com.android.tradefed.device.DeviceNotAvailableException; 31 import com.android.tradefed.device.DeviceUnresponsiveException; 32 import com.android.tradefed.device.FreeDeviceState; 33 import com.android.tradefed.device.ITestDevice; 34 import com.android.tradefed.device.ITestDevice.RecoveryMode; 35 import com.android.tradefed.device.NativeDevice; 36 import com.android.tradefed.device.StubDevice; 37 import com.android.tradefed.device.TcpDevice; 38 import com.android.tradefed.device.TestDeviceState; 39 import com.android.tradefed.device.cloud.ManagedRemoteDevice; 40 import com.android.tradefed.device.cloud.NestedRemoteDevice; 41 import com.android.tradefed.device.cloud.RemoteAndroidVirtualDevice; 42 import com.android.tradefed.error.HarnessException; 43 import com.android.tradefed.error.IHarnessException; 44 import com.android.tradefed.guice.InvocationScope; 45 import com.android.tradefed.invoker.logger.CurrentInvocation; 46 import com.android.tradefed.invoker.logger.CurrentInvocation.InvocationInfo; 47 import com.android.tradefed.invoker.logger.InvocationMetricLogger; 48 import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; 49 import com.android.tradefed.invoker.logger.TfObjectTracker; 50 import com.android.tradefed.invoker.sandbox.ParentSandboxInvocationExecution; 51 import com.android.tradefed.invoker.sandbox.SandboxedInvocationExecution; 52 import com.android.tradefed.invoker.shard.LastShardDetector; 53 import com.android.tradefed.invoker.shard.ShardHelper; 54 import com.android.tradefed.log.BaseLeveledLogOutput; 55 import com.android.tradefed.log.ILeveledLogOutput; 56 import com.android.tradefed.log.ILogRegistry; 57 import com.android.tradefed.log.ITestLogger; 58 import com.android.tradefed.log.LogRegistry; 59 import com.android.tradefed.log.LogUtil.CLog; 60 import com.android.tradefed.log.StdoutLogger; 61 import com.android.tradefed.postprocessor.IPostProcessor; 62 import com.android.tradefed.result.ActionInProgress; 63 import com.android.tradefed.result.FailureDescription; 64 import com.android.tradefed.result.FileInputStreamSource; 65 import com.android.tradefed.result.ITestInvocationListener; 66 import com.android.tradefed.result.InputStreamSource; 67 import com.android.tradefed.result.LogDataType; 68 import com.android.tradefed.result.LogSaverResultForwarder; 69 import com.android.tradefed.result.ResultAndLogForwarder; 70 import com.android.tradefed.result.error.ErrorIdentifier; 71 import com.android.tradefed.result.error.InfraErrorIdentifier; 72 import com.android.tradefed.result.proto.TestRecordProto.FailureStatus; 73 import com.android.tradefed.retry.IRetryDecision; 74 import com.android.tradefed.retry.ResultAggregator; 75 import com.android.tradefed.retry.RetryStrategy; 76 import com.android.tradefed.targetprep.BuildError; 77 import com.android.tradefed.targetprep.DeviceFailedToBootError; 78 import com.android.tradefed.targetprep.TargetSetupError; 79 import com.android.tradefed.testtype.SubprocessTfLauncher; 80 import com.android.tradefed.util.FileUtil; 81 import com.android.tradefed.util.IRunUtil; 82 import com.android.tradefed.util.PrettyPrintDelimiter; 83 import com.android.tradefed.util.RunInterruptedException; 84 import com.android.tradefed.util.RunUtil; 85 import com.android.tradefed.util.TimeUtil; 86 import com.android.tradefed.util.executor.ParallelDeviceExecutor; 87 88 import com.google.common.annotations.VisibleForTesting; 89 90 import java.io.File; 91 import java.io.IOException; 92 import java.util.ArrayList; 93 import java.util.Arrays; 94 import java.util.List; 95 import java.util.Map; 96 import java.util.Map.Entry; 97 import java.util.concurrent.Callable; 98 import java.util.concurrent.TimeUnit; 99 100 /** 101 * Default implementation of {@link ITestInvocation}. 102 * <p/> 103 * Loads major objects based on {@link IConfiguration} 104 * - retrieves build 105 * - prepares target 106 * - runs tests 107 * - reports results 108 */ 109 public class TestInvocation implements ITestInvocation { 110 111 /** Key of the command line args attributes */ 112 public static final String COMMAND_ARGS_KEY = "command_line_args"; 113 114 /** 115 * Format of the key in {@link IBuildInfo} to log the battery level for each step of the 116 * invocation. (Setup, test, tear down). 117 */ 118 private static final String BATTERY_ATTRIBUTE_FORMAT_KEY = "%s-battery-%s"; 119 120 public static final String TRADEFED_LOG_NAME = "host_log"; 121 public static final String TRADEFED_END_HOST_LOG = "end_host_log"; 122 /** Suffix used on host_log for the part before sharding occurs. */ 123 static final String BEFORE_SHARDING_SUFFIX = "_before_sharding"; 124 static final String DEVICE_LOG_NAME_PREFIX = "device_logcat_"; 125 static final String EMULATOR_LOG_NAME_PREFIX = "emulator_log_"; 126 static final String BUILD_ERROR_BUGREPORT_NAME = "build_error_bugreport"; 127 static final String DEVICE_UNRESPONSIVE_BUGREPORT_NAME = "device_unresponsive_bugreport"; 128 static final String INVOCATION_ENDED_BUGREPORT_NAME = "invocation_ended_bugreport"; 129 static final String TARGET_SETUP_ERROR_BUGREPORT_NAME = "target_setup_error_bugreport"; 130 static final String BATT_TAG = "[battery level]"; 131 132 public enum Stage { 133 ERROR("error"), 134 SETUP("setup"), 135 TEST("test"), 136 TEARDOWN("teardown"); 137 138 private final String mName; 139 Stage(String name)140 Stage(String name) { 141 mName = name; 142 } 143 getName()144 public String getName() { 145 return mName; 146 } 147 } 148 149 /** The different mode an invocation can run into. */ 150 public enum RunMode { 151 REGULAR, 152 PARENT_SANDBOX, 153 SANDBOX, 154 REMOTE_INVOCATION, 155 } 156 157 private String mStatus = "(not invoked)"; 158 private String mStopCause = null; 159 private Long mStopRequestTime = null; 160 private boolean mTestStarted = false; 161 private boolean mInvocationFailed = false; 162 private List<IScheduledInvocationListener> mSchedulerListeners = new ArrayList<>(); 163 164 /** 165 * Display a log message informing the user of a invocation being started. 166 * 167 * @param context the {@link IInvocationContext} 168 * @param config the {@link IConfiguration} 169 */ logStartInvocation(IInvocationContext context, IConfiguration config)170 private void logStartInvocation(IInvocationContext context, IConfiguration config) { 171 String shardSuffix = ""; 172 if (config.getCommandOptions().getShardIndex() != null) { 173 shardSuffix = 174 String.format( 175 " (shard %d of %d)", 176 config.getCommandOptions().getShardIndex() + 1, 177 config.getCommandOptions().getShardCount()); 178 } 179 StringBuilder buildInfos = new StringBuilder(); 180 StringBuilder msg = new StringBuilder("Starting invocation for '"); 181 msg.append(context.getTestTag()); 182 msg.append("' with "); 183 for (Entry<ITestDevice, IBuildInfo> entry : context.getDeviceBuildMap().entrySet()) { 184 msg.append("'[ "); 185 msg.append(entry.getValue().toString()); 186 buildInfos.append(entry.getValue().toString()); 187 msg.append(" on device '"); 188 msg.append(entry.getKey().getSerialNumber()); 189 msg.append("'] "); 190 } 191 msg.append(shardSuffix); 192 CLog.logAndDisplay(LogLevel.INFO, msg.toString()); 193 mStatus = String.format("running %s on build(s) '%s'", context.getTestTag(), 194 buildInfos.toString()) + shardSuffix; 195 } 196 197 /** 198 * Performs the invocation 199 * 200 * @param config the {@link IConfiguration} 201 * @param testInfo the {@link TestInformation} to use for the invocation. 202 */ performInvocation( IConfiguration config, TestInformation testInfo, IInvocationExecution invocationPath, ITestInvocationListener listener, boolean devicePreSetupDone)203 private void performInvocation( 204 IConfiguration config, 205 TestInformation testInfo, 206 IInvocationExecution invocationPath, 207 ITestInvocationListener listener, 208 boolean devicePreSetupDone) 209 throws Throwable { 210 ReportHostLog reportThread = new ReportHostLog(listener, config); 211 Runtime.getRuntime().addShutdownHook(reportThread); 212 boolean resumed = false; 213 String bugreportName = null; 214 long startTime = System.currentTimeMillis(); 215 long elapsedTime = -1; 216 Throwable exception = null; 217 Throwable tearDownException = null; 218 ITestDevice badDevice = null; 219 IInvocationContext context = testInfo.getContext(); 220 221 // Ensure that no unexpected attributes are added afterward 222 ((InvocationContext) context).lockAttributes(); 223 try { 224 logDeviceBatteryLevel(context, "initial"); 225 // Run the preInvocationSetup on devices. 226 if (!devicePreSetupDone) { 227 if (!config.getCommandOptions().shouldUseSandboxing()) { 228 invocationPath.runDevicePreInvocationSetup(context, config, listener); 229 } 230 } 231 // Then run the regular setup and run 232 prepareAndRun(config, testInfo, invocationPath, listener); 233 } catch (BuildError e) { 234 exception = e; 235 CLog.w("BuildError on device '%s'. Reason: %s", e.getDeviceSerial(), e.toString()); 236 bugreportName = BUILD_ERROR_BUGREPORT_NAME; 237 if (e.getDeviceSerial() != null) { 238 badDevice = context.getDeviceBySerial(e.getDeviceSerial()); 239 } 240 if (e instanceof DeviceFailedToBootError) { 241 if (badDevice == null) { 242 context.setRecoveryModeForAllDevices(RecoveryMode.NONE); 243 } else { 244 badDevice.setRecoveryMode(RecoveryMode.NONE); 245 } 246 } 247 reportFailure(createFailureFromException(e, FailureStatus.INFRA_FAILURE), listener); 248 } catch (TargetSetupError e) { 249 exception = e; 250 CLog.e("Caught exception while running invocation"); 251 CLog.e(e); 252 bugreportName = TARGET_SETUP_ERROR_BUGREPORT_NAME; 253 if (e.getDeviceSerial() != null) { 254 badDevice = context.getDeviceBySerial(e.getDeviceSerial()); 255 } 256 reportFailure(createFailureFromException(e, FailureStatus.INFRA_FAILURE), listener); 257 } catch (DeviceNotAvailableException e) { 258 exception = e; 259 // log a warning here so its captured before reportLogs is called 260 CLog.w("Invocation did not complete due to device %s becoming not available. " + 261 "Reason: %s", e.getSerial(), e.getMessage()); 262 badDevice = context.getDeviceBySerial(e.getSerial()); 263 if ((e instanceof DeviceUnresponsiveException) && badDevice != null 264 && TestDeviceState.ONLINE.equals(badDevice.getDeviceState())) { 265 // under certain cases it might still be possible to grab a bugreport 266 bugreportName = DEVICE_UNRESPONSIVE_BUGREPORT_NAME; 267 } 268 reportFailure(createFailureFromException(e, FailureStatus.INFRA_FAILURE), listener); 269 // Upon reaching here after an exception, it is safe to assume that recovery 270 // has already been attempted so we disable it to avoid re-entry during clean up. 271 if (badDevice != null) { 272 badDevice.setRecoveryMode(RecoveryMode.NONE); 273 } 274 throw e; 275 } catch (RunInterruptedException e) { 276 exception = e; 277 CLog.w("Invocation interrupted"); 278 reportFailure(createFailureFromException(e, FailureStatus.UNSET), listener); 279 } catch (AssertionError e) { 280 exception = e; 281 CLog.e("Caught AssertionError while running invocation: %s", e.toString()); 282 CLog.e(e); 283 reportFailure(createFailureFromException(e, FailureStatus.UNSET), listener); 284 } catch (Throwable t) { 285 exception = t; 286 // log a warning here so its captured before reportLogs is called 287 CLog.e("Unexpected exception when running invocation: %s", t.toString()); 288 CLog.e(t); 289 reportFailure(createFailureFromException(t, FailureStatus.UNSET), listener); 290 throw t; 291 } finally { 292 // Only capture logcat for TEST if we started the test phase. 293 if (mTestStarted) { 294 for (ITestDevice device : context.getDevices()) { 295 invocationPath.reportLogs(device, listener, Stage.TEST); 296 } 297 } 298 CurrentInvocation.setActionInProgress(ActionInProgress.TEAR_DOWN); 299 getRunUtil().allowInterrupt(false); 300 if (config.getCommandOptions().takeBugreportOnInvocationEnded() || 301 config.getCommandOptions().takeBugreportzOnInvocationEnded()) { 302 if (bugreportName != null) { 303 CLog.i("Bugreport to be taken for failure instead of invocation ended."); 304 } else { 305 bugreportName = INVOCATION_ENDED_BUGREPORT_NAME; 306 } 307 } 308 if (bugreportName != null) { 309 if (context.getDevices().size() == 1 || badDevice != null) { 310 ITestDevice collectBugreport = badDevice; 311 if (collectBugreport == null) { 312 collectBugreport = context.getDevices().get(0); 313 } 314 // If we have identified a faulty device only take the bugreport on it. 315 takeBugreport(collectBugreport, listener, bugreportName); 316 } else if (context.getDevices().size() > 1) { 317 ParallelDeviceExecutor<Boolean> executor = 318 new ParallelDeviceExecutor<>(context.getDevices()); 319 List<Callable<Boolean>> callableTasks = new ArrayList<>(); 320 final String reportName = bugreportName; 321 for (ITestDevice device : context.getDevices()) { 322 Callable<Boolean> callableTask = 323 () -> { 324 takeBugreport(device, listener, reportName); 325 return true; 326 }; 327 callableTasks.add(callableTask); 328 } 329 // Capture the bugreports best effort, ignore the results. 330 executor.invokeAll(callableTasks, 5, TimeUnit.MINUTES); 331 } 332 } 333 // Save the device executeShellCommand logs 334 logExecuteShellCommand(context.getDevices(), listener); 335 336 mStatus = "tearing down"; 337 try { 338 invocationPath.doTeardown(testInfo, config, listener, exception); 339 } catch (Throwable e) { 340 tearDownException = e; 341 CLog.e("Exception when tearing down invocation: %s", tearDownException.toString()); 342 CLog.e(tearDownException); 343 if (exception == null) { 344 // only report when the exception is new during tear down 345 reportFailure( 346 createFailureFromException( 347 tearDownException, FailureStatus.INFRA_FAILURE), 348 listener); 349 } 350 } 351 mStatus = "done running tests"; 352 CurrentInvocation.setActionInProgress(ActionInProgress.FREE_RESOURCES); 353 // Track the timestamp when we are done with devices 354 addInvocationMetric( 355 InvocationMetricKey.DEVICE_DONE_TIMESTAMP, System.currentTimeMillis()); 356 Map<ITestDevice, FreeDeviceState> devicesStates = 357 handleAndLogReleaseState(context, exception); 358 if (config.getCommandOptions().earlyDeviceRelease()) { 359 context.markReleasedEarly(); 360 for (IScheduledInvocationListener scheduleListener : mSchedulerListeners) { 361 scheduleListener.releaseDevices(context, devicesStates); 362 } 363 } 364 try { 365 // Clean up host. 366 invocationPath.doCleanUp(context, config, exception); 367 for (ITestDevice device : context.getDevices()) { 368 invocationPath.reportLogs(device, listener, Stage.TEARDOWN); 369 } 370 if (mStopCause != null) { 371 String message = 372 String.format( 373 "Invocation was interrupted due to: %s, results will be " 374 + "affected.", 375 mStopCause); 376 FailureDescription failure = 377 FailureDescription.create(message, FailureStatus.CANCELLED); 378 reportFailure(failure, listener); 379 PrettyPrintDelimiter.printStageDelimiter(message); 380 if (mStopRequestTime != null) { 381 // This is not 100% perfect since result reporting can still run a bit 382 // longer, but this is our last opportunity to report it. 383 long latency = System.currentTimeMillis() - mStopRequestTime; 384 InvocationMetricLogger.addInvocationMetrics( 385 InvocationMetricKey.SHUTDOWN_HARD_LATENCY, latency); 386 } 387 } 388 reportHostLog(listener, config); 389 // If host_log is reported, remove the hook 390 Runtime.getRuntime().removeShutdownHook(reportThread); 391 392 elapsedTime = System.currentTimeMillis() - startTime; 393 if (!resumed) { 394 // Init a log for the end of the host_log. 395 ILeveledLogOutput endHostLog = config.getLogOutput(); 396 endHostLog.init(); 397 getLogRegistry().registerLogger(endHostLog); 398 PrettyPrintDelimiter.printStageDelimiter("===== Result Reporters ====="); 399 try { 400 // Copy the invocation metrics to the context 401 ((InvocationContext) context).logInvocationMetrics(); 402 listener.invocationEnded(elapsedTime); 403 } finally { 404 InvocationMetricLogger.clearInvocationMetrics(); 405 endHostLog.closeLog(); 406 getLogRegistry().unregisterLogger(); 407 } 408 } 409 } finally { 410 TfObjectTracker.clearTracking(); 411 CurrentInvocation.clearInvocationInfos(); 412 } 413 } 414 if (tearDownException != null) { 415 // this means a DNAE or RTE has happened during teardown, need to throw 416 // if there was a preceding RTE or DNAE stored in 'exception', it would have already 417 // been thrown before exiting the previous try...catch...finally block 418 throw tearDownException; 419 } 420 } 421 422 /** Do setup and run the tests */ prepareAndRun( IConfiguration config, TestInformation testInfo, IInvocationExecution invocationPath, ITestInvocationListener listener)423 private void prepareAndRun( 424 IConfiguration config, 425 TestInformation testInfo, 426 IInvocationExecution invocationPath, 427 ITestInvocationListener listener) 428 throws Throwable { 429 getRunUtil().allowInterrupt(true); 430 logDeviceBatteryLevel(testInfo.getContext(), "initial -> setup"); 431 CurrentInvocation.setActionInProgress(ActionInProgress.SETUP); 432 invocationPath.doSetup(testInfo, config, listener); 433 logDeviceBatteryLevel(testInfo.getContext(), "setup -> test"); 434 mTestStarted = true; 435 CurrentInvocation.setActionInProgress(ActionInProgress.TEST); 436 invocationPath.runTests(testInfo, config, listener); 437 logDeviceBatteryLevel(testInfo.getContext(), "after test"); 438 CurrentInvocation.setActionInProgress(ActionInProgress.UNSET); 439 } 440 441 /** 442 * Starts the invocation. 443 * <p/> 444 * Starts logging, and informs listeners that invocation has been started. 445 * 446 * @param config 447 * @param context 448 */ startInvocation(IConfiguration config, IInvocationContext context, ITestInvocationListener listener)449 private void startInvocation(IConfiguration config, IInvocationContext context, 450 ITestInvocationListener listener) { 451 logStartInvocation(context, config); 452 listener.invocationStarted(context); 453 } 454 455 /** Report the exception failure as an invocation failure. */ reportFailure(FailureDescription failure, ITestInvocationListener listener)456 private void reportFailure(FailureDescription failure, ITestInvocationListener listener) { 457 if (mInvocationFailed) { 458 CLog.e("An invocation failure was already reported, ignoring %s", failure); 459 return; 460 } 461 // Always report the failure 462 listener.invocationFailed(failure); 463 mInvocationFailed = true; 464 } 465 466 /** 467 * Create a {@link FailureDescription} from an invocation exception. 468 * 469 * @param exception The exception to convert 470 * @param defaultStatus The status to use by default if the exception is not a {@link 471 * IHarnessException}. 472 */ createFailureFromException( Throwable exception, FailureStatus defaultStatus)473 private FailureDescription createFailureFromException( 474 Throwable exception, FailureStatus defaultStatus) { 475 ErrorIdentifier id = null; 476 if (exception instanceof IHarnessException) { 477 id = ((HarnessException) exception).getErrorId(); 478 } 479 FailureDescription failure = 480 CurrentInvocation.createFailure(exception.getMessage(), id) 481 .setCause(exception) 482 .setFailureStatus(defaultStatus); 483 if (id != null) { 484 failure.setErrorIdentifier(id); 485 failure.setFailureStatus(id.status()); 486 } 487 return failure; 488 } 489 reportHostLog(ITestInvocationListener listener, IConfiguration config)490 private void reportHostLog(ITestInvocationListener listener, IConfiguration config) { 491 reportHostLog(listener, config, TRADEFED_LOG_NAME); 492 } 493 reportHostLog( ITestInvocationListener listener, IConfiguration config, String name)494 private void reportHostLog( 495 ITestInvocationListener listener, IConfiguration config, String name) { 496 ILeveledLogOutput logger = config.getLogOutput(); 497 try (InputStreamSource globalLogSource = logger.getLog()) { 498 if (globalLogSource != null) { 499 if (config.getCommandOptions().getHostLogSuffix() != null) { 500 name += config.getCommandOptions().getHostLogSuffix(); 501 } 502 listener.testLog(name, LogDataType.HOST_LOG, globalLogSource); 503 } else { 504 // Only print the non-logging if we are not a stdout logger 505 if (!(logger instanceof StdoutLogger)) { 506 CLog.i("Skip logging %s to a file with logger '%s'", name, logger); 507 } 508 } 509 } 510 // once tradefed log is reported, all further log calls for this invocation can get lost 511 // unregister logger so future log calls get directed to the tradefed global log 512 getLogRegistry().unregisterLogger(); 513 logger.closeLog(); 514 } 515 takeBugreport( ITestDevice device, ITestInvocationListener listener, String bugreportName)516 private void takeBugreport( 517 ITestDevice device, ITestInvocationListener listener, String bugreportName) { 518 if (device == null) { 519 return; 520 } 521 if (device.getIDevice() instanceof StubDevice) { 522 return; 523 } 524 // logBugreport will report a regular bugreport if bugreportz is not supported. 525 boolean res = 526 device.logBugreport( 527 String.format("%s_%s", bugreportName, device.getSerialNumber()), listener); 528 if (!res) { 529 CLog.w("Error when collecting bugreport for device '%s'", device.getSerialNumber()); 530 } 531 } 532 533 /** 534 * Gets the {@link ILogRegistry} to use. 535 * <p/> 536 * Exposed for unit testing. 537 */ getLogRegistry()538 ILogRegistry getLogRegistry() { 539 return LogRegistry.getLogRegistry(); 540 } 541 542 /** 543 * Utility method to fetch the default {@link IRunUtil} singleton 544 * <p /> 545 * Exposed for unit testing. 546 */ getRunUtil()547 IRunUtil getRunUtil() { 548 return RunUtil.getDefault(); 549 } 550 551 @Override toString()552 public String toString() { 553 return mStatus; 554 } 555 556 /** 557 * Log the battery level of each device in the invocation. 558 * 559 * @param context the {@link IInvocationContext} of the invocation. 560 * @param event a {@link String} describing the context of the logging (initial, setup, etc.). 561 */ 562 @VisibleForTesting logDeviceBatteryLevel(IInvocationContext context, String event)563 void logDeviceBatteryLevel(IInvocationContext context, String event) { 564 for (ITestDevice testDevice : context.getDevices()) { 565 if (testDevice == null) { 566 continue; 567 } 568 if (testDevice.getIDevice() instanceof StubDevice) { 569 continue; 570 } 571 if (testDevice instanceof RemoteAndroidVirtualDevice 572 || testDevice instanceof NestedRemoteDevice) { 573 // Vritual devices have a fake battery there is no point in logging it. 574 continue; 575 } 576 Integer batteryLevel = testDevice.getBattery(); 577 if (batteryLevel == null) { 578 CLog.v("Failed to get battery level for %s", testDevice.getSerialNumber()); 579 continue; 580 } 581 CLog.v("%s - %s - %d%%", BATT_TAG, event, batteryLevel); 582 context.getBuildInfo(testDevice) 583 .addBuildAttribute( 584 String.format( 585 BATTERY_ATTRIBUTE_FORMAT_KEY, 586 testDevice.getSerialNumber(), 587 event), 588 batteryLevel.toString()); 589 } 590 } 591 592 /** 593 * Invoke {@link IInvocationExecution#fetchBuild(TestInformation, IConfiguration, IRescheduler, 594 * ITestInvocationListener)} and handles the output as well as failures. 595 * 596 * @param testInfo the {@link TestInformation} of the invocation. 597 * @param config the {@link IConfiguration} of this test run. 598 * @param rescheduler the {@link IRescheduler}, for rescheduling portions of the invocation for 599 * execution on another resource(s) 600 * @param listener the {@link ITestInvocation} to report build download failures. 601 * @param invocationPath the {@link IInvocationExecution} driving the invocation. 602 * @return True if we successfully downloaded the build, false otherwise. 603 * @throws DeviceNotAvailableException 604 */ invokeFetchBuild( TestInformation testInfo, IConfiguration config, IRescheduler rescheduler, ITestInvocationListener listener, IInvocationExecution invocationPath)605 private boolean invokeFetchBuild( 606 TestInformation testInfo, 607 IConfiguration config, 608 IRescheduler rescheduler, 609 ITestInvocationListener listener, 610 IInvocationExecution invocationPath) 611 throws DeviceNotAvailableException { 612 CurrentInvocation.setActionInProgress(ActionInProgress.FETCHING_ARTIFACTS); 613 Exception buildException = null; 614 boolean res = false; 615 try { 616 res = invocationPath.fetchBuild(testInfo, config, rescheduler, listener); 617 if (res) { 618 // Successful fetch of build. 619 CurrentInvocation.setActionInProgress(ActionInProgress.UNSET); 620 return true; 621 } 622 // In case of build not found issues. 623 mStatus = "(no build to test)"; 624 // Set the exit code to error 625 buildException = 626 new BuildRetrievalError( 627 "No build found to test.", InfraErrorIdentifier.ARTIFACT_NOT_FOUND); 628 } catch (BuildRetrievalError | RuntimeException e) { 629 buildException = e; 630 } 631 setExitCode(ExitCode.NO_BUILD, buildException); 632 // Report an empty invocation, so this error is sent to listeners 633 startInvocation(config, testInfo.getContext(), listener); 634 reportFailure( 635 createFailureFromException(buildException, FailureStatus.INFRA_FAILURE), listener); 636 for (ITestDevice device : testInfo.getContext().getDevices()) { 637 invocationPath.reportLogs(device, listener, Stage.ERROR); 638 } 639 reportHostLog(listener, config); 640 listener.invocationEnded(0L); 641 return false; 642 } 643 644 /** 645 * Invoke {@link IConfiguration#resolveDynamicOptions(DynamicRemoteFileResolver)} to resolve the 646 * dynamic files. 647 * 648 * @param context the {@link IInvocationContext} of the invocation. 649 * @param config the {@link IConfiguration} of this test run. 650 * @param rescheduler the {@link IRescheduler}, for rescheduling portions of the invocation for 651 * execution on another resource(s) 652 * @param listener the {@link ITestInvocation} to report build download failures. 653 * @param invocationPath the {@link IInvocationExecution} driving the invocation. 654 * @param mode The current {@link RunMode} of the invocation. 655 * @return True if we successfully downloaded the build, false otherwise. 656 */ invokeRemoteDynamic( IInvocationContext context, IConfiguration config, IRescheduler rescheduler, ITestInvocationListener listener, IInvocationExecution invocationPath, RunMode mode)657 private boolean invokeRemoteDynamic( 658 IInvocationContext context, 659 IConfiguration config, 660 IRescheduler rescheduler, 661 ITestInvocationListener listener, 662 IInvocationExecution invocationPath, 663 RunMode mode) { 664 try { 665 // Don't resolve for remote invocation, wait until we are inside the remote. 666 if (RunMode.REMOTE_INVOCATION.equals(mode)) { 667 return true; 668 } 669 CurrentInvocation.setActionInProgress(ActionInProgress.FETCHING_ARTIFACTS); 670 DynamicRemoteFileResolver resolver = new DynamicRemoteFileResolver(); 671 resolver.setDevice(context.getDevices().get(0)); 672 resolver.addExtraArgs(config.getCommandOptions().getDynamicDownloadArgs()); 673 config.resolveDynamicOptions(resolver); 674 CurrentInvocation.setActionInProgress(ActionInProgress.UNSET); 675 return true; 676 } catch (RuntimeException | BuildRetrievalError | ConfigurationException e) { 677 // In case of build not found issues. 678 mStatus = "(failed dynamic download)"; 679 // Set the exit code to error 680 setExitCode(ExitCode.NO_BUILD, e); 681 682 // We don't have a reporting buildInfo at this point 683 IBuildInfo info = new BuildInfo(); 684 context.addDeviceBuildInfo(context.getDeviceConfigNames().get(0), info); 685 686 // Report an empty invocation, so this error is sent to listeners 687 startInvocation(config, context, listener); 688 reportFailure(createFailureFromException(e, FailureStatus.INFRA_FAILURE), listener); 689 for (ITestDevice device : context.getDevices()) { 690 invocationPath.reportLogs(device, listener, Stage.ERROR); 691 } 692 reportHostLog(listener, config); 693 listener.invocationEnded(0L); 694 return false; 695 } 696 } 697 698 /** {@inheritDoc} */ 699 @Override invoke( IInvocationContext context, IConfiguration config, IRescheduler rescheduler, ITestInvocationListener... extraListeners)700 public void invoke( 701 IInvocationContext context, 702 IConfiguration config, 703 IRescheduler rescheduler, 704 ITestInvocationListener... extraListeners) 705 throws DeviceNotAvailableException, Throwable { 706 for (ITestInvocationListener listener : extraListeners) { 707 if (listener instanceof IScheduledInvocationListener) { 708 mSchedulerListeners.add((IScheduledInvocationListener) listener); 709 } 710 } 711 // Create the TestInformation for the invocation 712 // TODO: Use invocation-id in the workfolder name 713 Object sharedInfoObject = 714 config.getConfigurationObject(ShardHelper.SHARED_TEST_INFORMATION); 715 TestInformation sharedTestInfo = null; 716 TestInformation info = null; 717 if (sharedInfoObject != null) { 718 sharedTestInfo = (TestInformation) sharedInfoObject; 719 // During sharding we share everything except the invocation context 720 info = TestInformation.createModuleTestInfo(sharedTestInfo, context); 721 } 722 if (info == null) { 723 File mWorkFolder = FileUtil.createTempDir("tradefed-invocation-workfolder"); 724 info = 725 TestInformation.newBuilder() 726 .setInvocationContext(context) 727 .setDependenciesFolder(mWorkFolder) 728 .build(); 729 } 730 CurrentInvocation.addInvocationInfo(InvocationInfo.WORK_FOLDER, info.dependenciesFolder()); 731 732 CleanUpInvocationFiles cleanUpThread = new CleanUpInvocationFiles(info, config); 733 Runtime.getRuntime().addShutdownHook(cleanUpThread); 734 registerExecutionFiles(info.executionFiles()); 735 736 List<ITestInvocationListener> allListeners = 737 new ArrayList<>(config.getTestInvocationListeners().size() + extraListeners.length); 738 allListeners.addAll(config.getTestInvocationListeners()); 739 allListeners.addAll(Arrays.asList(extraListeners)); 740 ITestInvocationListener listener = null; 741 742 // Auto retry feature 743 IRetryDecision decision = config.getRetryDecision(); 744 ResultAggregator aggregator = null; 745 decision.setInvocationContext(context); 746 // We don't need the aggregator in the subprocess because the parent will take care of it. 747 if (!config.getCommandOptions() 748 .getInvocationData() 749 .containsKey(SubprocessTfLauncher.SUBPROCESS_TAG_NAME) 750 && decision.isAutoRetryEnabled() 751 && decision.getMaxRetryCount() > 1 752 && !RetryStrategy.NO_RETRY.equals(decision.getRetryStrategy())) { 753 CLog.d("Auto-retry enabled, using the ResultAggregator to handle multiple retries."); 754 aggregator = new ResultAggregator(allListeners, decision.getRetryStrategy()); 755 allListeners = Arrays.asList(aggregator); 756 } 757 758 if (!config.getPostProcessors().isEmpty()) { 759 ITestInvocationListener forwarder = new ResultAndLogForwarder(allListeners); 760 // Post-processors are the first layer around the final reporters. 761 for (IPostProcessor postProcessor : config.getPostProcessors()) { 762 if (postProcessor.isDisabled()) { 763 CLog.d("%s has been disabled. skipping.", postProcessor); 764 } else { 765 forwarder = postProcessor.init(forwarder); 766 } 767 } 768 listener = new LogSaverResultForwarder(config.getLogSaver(), Arrays.asList(forwarder)); 769 } else { 770 listener = new LogSaverResultForwarder(config.getLogSaver(), allListeners); 771 } 772 773 RunMode mode = RunMode.REGULAR; 774 if (config.getConfigurationDescription().shouldUseSandbox()) { 775 mode = RunMode.SANDBOX; 776 } 777 if (config.getCommandOptions().shouldUseSandboxing()) { 778 mode = RunMode.PARENT_SANDBOX; 779 } 780 if (context.getDevices().get(0) instanceof ManagedRemoteDevice) { 781 mode = RunMode.REMOTE_INVOCATION; 782 } 783 IInvocationExecution invocationPath = createInvocationExec(mode); 784 updateInvocationContext(context, config); 785 786 // Create the Guice scope 787 InvocationScope scope = getInvocationScope(); 788 scope.enter(); 789 // Seed our TF objects to the Guice scope 790 scope.seed(IRescheduler.class, rescheduler); 791 scope.seedConfiguration(config); 792 boolean sharding = false; 793 try { 794 ILeveledLogOutput leveledLogOutput = config.getLogOutput(); 795 leveledLogOutput.init(); 796 if (leveledLogOutput instanceof BaseLeveledLogOutput) { 797 ((BaseLeveledLogOutput) leveledLogOutput).initFilters(config); 798 } 799 getLogRegistry().registerLogger(leveledLogOutput); 800 mStatus = "resolving dynamic options"; 801 boolean resolverSuccess = 802 invokeRemoteDynamic( 803 context, config, rescheduler, listener, invocationPath, mode); 804 if (!resolverSuccess) { 805 return; 806 } 807 808 mStatus = "fetching build"; 809 for (String deviceName : context.getDeviceConfigNames()) { 810 context.getDevice(deviceName).clearLastConnectedWifiNetwork(); 811 context.getDevice(deviceName) 812 .setOptions(config.getDeviceConfigByName(deviceName).getDeviceOptions()); 813 if (config.getDeviceConfigByName(deviceName) 814 .getDeviceOptions() 815 .isLogcatCaptureEnabled()) { 816 if (!(context.getDevice(deviceName).getIDevice() instanceof StubDevice)) { 817 context.getDevice(deviceName).startLogcat(); 818 } 819 } 820 } 821 822 String cmdLineArgs = config.getCommandLine(); 823 if (cmdLineArgs != null) { 824 CLog.i("Invocation was started with cmd: %s", cmdLineArgs); 825 } 826 827 long start = System.currentTimeMillis(); 828 boolean providerSuccess = 829 invokeFetchBuild(info, config, rescheduler, listener, invocationPath); 830 long fetchBuildDuration = System.currentTimeMillis() - start; 831 InvocationMetricLogger.addInvocationMetrics( 832 InvocationMetricKey.FETCH_BUILD, fetchBuildDuration); 833 CLog.d("Fetch build duration: %s", TimeUtil.formatElapsedTime(fetchBuildDuration)); 834 if (!providerSuccess) { 835 return; 836 } 837 838 boolean deviceInit = false; 839 // If the top level invocation has --use-sandbox do not shard there. It will shard in 840 // the child invocation. 841 if (RunMode.REGULAR.equals(mode) || RunMode.SANDBOX.equals(mode)) { 842 mStatus = "sharding"; 843 844 // TODO: Handle local sharding and special devices 845 Integer shardCount = config.getCommandOptions().getShardCount(); 846 Integer shardIndex = config.getCommandOptions().getShardIndex(); 847 // Special Handling in case of sharding within the same invocation (in-place): Some 848 // devices (Remote devices for example) require extra preparation step to be 849 // available, but sharding requires the device to be available in some cases. So 850 // we call the device setup early to meet all the requirements. 851 if (shardCount != null && shardIndex != null) { 852 deviceInit = true; 853 startInvocation(config, context, listener); 854 try { 855 invocationPath.runDevicePreInvocationSetup(context, config, listener); 856 } catch (DeviceNotAvailableException | TargetSetupError e) { 857 CLog.e(e); 858 FailureDescription failure = FailureDescription.create(e.getMessage()); 859 failure.setCause(e).setFailureStatus(FailureStatus.INFRA_FAILURE); 860 if (e instanceof DeviceNotAvailableException) { 861 setExitCode(ExitCode.DEVICE_UNAVAILABLE, e); 862 } else { 863 setExitCode(ExitCode.THROWABLE_EXCEPTION, e); 864 } 865 try { 866 invocationPath.runDevicePostInvocationTearDown(context, config, e); 867 } finally { 868 reportFailure( 869 createFailureFromException(e, FailureStatus.INFRA_FAILURE), 870 listener); 871 // Reports the logs 872 for (ITestDevice device : context.getDevices()) { 873 invocationPath.reportLogs(device, listener, Stage.ERROR); 874 } 875 reportHostLog(listener, config); 876 listener.invocationEnded(0L); 877 } 878 return; 879 } 880 } 881 882 try { 883 sharding = invocationPath.shardConfig(config, info, rescheduler, listener); 884 } catch (RuntimeException unexpected) { 885 if (deviceInit) { 886 // If we did an early setup, do the tear down. 887 invocationPath.runDevicePostInvocationTearDown(context, config, null); 888 } 889 throw unexpected; 890 } 891 if (sharding) { 892 CLog.i( 893 "Invocation for %s has been sharded, rescheduling", 894 context.getSerials()); 895 // Log the chunk of parent host_log before sharding 896 reportHostLog(listener, config, TRADEFED_LOG_NAME + BEFORE_SHARDING_SUFFIX); 897 config.getLogSaver().invocationEnded(0L); 898 if (aggregator != null) { 899 // The host_log is not available yet to reporters that don't support 900 // granular results, so forward it. 901 aggregator.forwardAggregatedInvocationLogs(); 902 } 903 return; 904 } 905 } 906 // Once we have all the information we can start the invocation. 907 if (!deviceInit) { 908 startInvocation(config, context, listener); 909 } 910 if (config.getTests() == null || config.getTests().isEmpty()) { 911 CLog.e("No tests to run"); 912 if (deviceInit) { 913 // If we did an early setup, do the tear down. 914 invocationPath.runDevicePostInvocationTearDown(context, config, null); 915 } 916 listener.invocationEnded(0L); 917 return; 918 } 919 920 performInvocation(config, info, invocationPath, listener, deviceInit); 921 setExitCode(ExitCode.NO_ERROR, null); 922 } catch (IOException e) { 923 CLog.e(e); 924 } finally { 925 scope.exit(); 926 // Ensure build infos are always cleaned up at the end of invocation. 927 invocationPath.cleanUpBuilds(context, config); 928 // ensure we always deregister the logger 929 for (String deviceName : context.getDeviceConfigNames()) { 930 if (!(context.getDevice(deviceName).getIDevice() instanceof StubDevice)) { 931 context.getDevice(deviceName).stopLogcat(); 932 } 933 } 934 if (!sharding) { 935 // If we are the parent shard, we do not delete the test information 936 deleteInvocationFiles(info, config); 937 } 938 // save remaining logs contents to global log 939 getLogRegistry().dumpToGlobalLog(config.getLogOutput()); 940 // Ensure log is unregistered and closed 941 getLogRegistry().unregisterLogger(); 942 config.getLogOutput().closeLog(); 943 config.cleanConfigurationData(); 944 945 Runtime.getRuntime().removeShutdownHook(cleanUpThread); 946 } 947 } 948 949 /** Returns the current {@link InvocationScope}. */ 950 @VisibleForTesting getInvocationScope()951 InvocationScope getInvocationScope() { 952 return InvocationScope.getDefault(); 953 } 954 955 @VisibleForTesting registerExecutionFiles(ExecutionFiles executionFiles)956 public void registerExecutionFiles(ExecutionFiles executionFiles) { 957 CurrentInvocation.registerExecutionFiles(executionFiles); 958 } 959 960 /** 961 * Helper to set the exit code. Exposed for testing. 962 */ setExitCode(ExitCode code, Throwable stack)963 protected void setExitCode(ExitCode code, Throwable stack) { 964 GlobalConfiguration.getInstance().getCommandScheduler() 965 .setLastInvocationExitCode(code, stack); 966 } 967 addInvocationMetric(InvocationMetricKey key, long value)968 protected void addInvocationMetric(InvocationMetricKey key, long value) { 969 InvocationMetricLogger.addInvocationMetrics(key, value); 970 } 971 addInvocationMetric(InvocationMetricKey key, String value)972 protected void addInvocationMetric(InvocationMetricKey key, String value) { 973 InvocationMetricLogger.addInvocationMetrics(key, value); 974 } 975 getDeviceLogName(Stage stage)976 public static String getDeviceLogName(Stage stage) { 977 return DEVICE_LOG_NAME_PREFIX + stage.getName(); 978 } 979 getEmulatorLogName(Stage stage)980 public static String getEmulatorLogName(Stage stage) { 981 return EMULATOR_LOG_NAME_PREFIX + stage.getName(); 982 } 983 984 @Override notifyInvocationStopped(String message)985 public void notifyInvocationStopped(String message) { 986 mStopCause = message; 987 if (mStopRequestTime == null) { 988 mStopRequestTime = System.currentTimeMillis(); 989 } 990 } 991 992 /** 993 * Create the invocation path that should be followed. 994 * 995 * @param mode The mode we are currently running as. 996 * @return The {@link IInvocationExecution} describing the invocation. 997 */ createInvocationExec(RunMode mode)998 public IInvocationExecution createInvocationExec(RunMode mode) { 999 switch (mode) { 1000 case PARENT_SANDBOX: 1001 return new ParentSandboxInvocationExecution(); 1002 case SANDBOX: 1003 return new SandboxedInvocationExecution(); 1004 case REMOTE_INVOCATION: 1005 return new RemoteInvocationExecution(); 1006 default: 1007 return new InvocationExecution(); 1008 } 1009 } 1010 1011 /** Prints a delimiter for a given Stage of the invocation. */ printStageDelimiter(Stage phase, boolean end)1012 public static void printStageDelimiter(Stage phase, boolean end) { 1013 String startEnd = end ? "ENDING" : "STARTING"; 1014 String message = String.format("===== %s PHASE %s =====", phase, startEnd); 1015 PrettyPrintDelimiter.printStageDelimiter(message); 1016 } 1017 logExecuteShellCommand(List<ITestDevice> devices, ITestLogger logger)1018 private void logExecuteShellCommand(List<ITestDevice> devices, ITestLogger logger) { 1019 for (ITestDevice device : devices) { 1020 if (!(device instanceof NativeDevice)) { 1021 return; 1022 } 1023 File log = ((NativeDevice) device).getExecuteShellCommandLog(); 1024 if (log == null || !log.exists()) { 1025 return; 1026 } 1027 if (log.length() == 0) { 1028 CLog.d("executeShellCommandLog file was empty, skip logging."); 1029 return; 1030 } 1031 try (InputStreamSource source = new FileInputStreamSource(log)) { 1032 logger.testLog( 1033 String.format("executeShellCommandLog_%s", device.getSerialNumber()), 1034 LogDataType.TEXT, 1035 source); 1036 } 1037 } 1038 } 1039 1040 /** 1041 * Update the {@link IInvocationContext} with additional info from the {@link IConfiguration}. 1042 * 1043 * @param context the {@link IInvocationContext} 1044 * @param config the {@link IConfiguration} 1045 */ updateInvocationContext(IInvocationContext context, IConfiguration config)1046 private void updateInvocationContext(IInvocationContext context, IConfiguration config) { 1047 if (config.getCommandLine() != null) { 1048 context.addInvocationAttribute( 1049 TestInvocation.COMMAND_ARGS_KEY, config.getCommandLine()); 1050 } 1051 if (config.getCommandOptions().getShardCount() != null) { 1052 context.addInvocationAttribute( 1053 "shard_count", config.getCommandOptions().getShardCount().toString()); 1054 } 1055 if (config.getCommandOptions().getShardIndex() != null) { 1056 context.addInvocationAttribute( 1057 "shard_index", config.getCommandOptions().getShardIndex().toString()); 1058 } 1059 context.setTestTag(getTestTag(config)); 1060 } 1061 1062 /** Helper to create the test tag from the configuration. */ getTestTag(IConfiguration config)1063 private String getTestTag(IConfiguration config) { 1064 String testTag = config.getCommandOptions().getTestTag(); 1065 if (config.getCommandOptions().getTestTagSuffix() != null) { 1066 testTag = 1067 String.format("%s-%s", testTag, config.getCommandOptions().getTestTagSuffix()); 1068 } 1069 return testTag; 1070 } 1071 1072 /** 1073 * Delete the invocation files if this is the last shard for local sharding or if we are not in 1074 * a local sharding situation. 1075 */ deleteInvocationFiles(TestInformation testInfo, IConfiguration config)1076 private void deleteInvocationFiles(TestInformation testInfo, IConfiguration config) { 1077 Object obj = config.getConfigurationObject(ShardHelper.LAST_SHARD_DETECTOR); 1078 if (obj != null) { 1079 LastShardDetector lastShardDetector = (LastShardDetector) obj; 1080 if (!lastShardDetector.isLastShardDone()) { 1081 return; 1082 } 1083 } 1084 // Delete the invocation work directory at the end 1085 FileUtil.recursiveDelete(testInfo.dependenciesFolder()); 1086 // Delete all the execution files 1087 testInfo.executionFiles().clearFiles(); 1088 } 1089 handleAndLogReleaseState( IInvocationContext context, Throwable exception)1090 private Map<ITestDevice, FreeDeviceState> handleAndLogReleaseState( 1091 IInvocationContext context, Throwable exception) { 1092 // Capture the FreeDeviceState of the primary device 1093 Map<ITestDevice, FreeDeviceState> devicesStates = 1094 CommandScheduler.createReleaseMap(context, exception); 1095 if (devicesStates.size() >= 1) { 1096 addInvocationMetric( 1097 InvocationMetricKey.DEVICE_RELEASE_STATE, 1098 devicesStates.values().iterator().next().toString()); 1099 } 1100 int countPhysicalLost = 0; 1101 int countVirtualLost = 0; 1102 for (Entry<ITestDevice, FreeDeviceState> fds : devicesStates.entrySet()) { 1103 // TODO: Rely on the FailureStatus for lost devices instead 1104 if (fds.getKey().getIDevice() instanceof TcpDevice 1105 && exception instanceof DeviceNotAvailableException) { 1106 countVirtualLost++; 1107 continue; 1108 } 1109 if (fds.getKey().getIDevice() instanceof StubDevice) { 1110 continue; 1111 } 1112 if (FreeDeviceState.UNAVAILABLE.equals(fds.getValue())) { 1113 countPhysicalLost++; 1114 } 1115 } 1116 if (countPhysicalLost > 0) { 1117 addInvocationMetric(InvocationMetricKey.DEVICE_LOST_DETECTED, countPhysicalLost); 1118 if (GlobalConfiguration.getDeviceManagerInstance() instanceof DeviceManager) { 1119 String adbOutput = 1120 ((DeviceManager) GlobalConfiguration.getDeviceManagerInstance()) 1121 .executeGlobalAdbCommand("devices"); 1122 CLog.e("'adb devices' output:\n%s", adbOutput); 1123 } 1124 } else if (countVirtualLost > 0) { 1125 addInvocationMetric(InvocationMetricKey.VIRTUAL_DEVICE_LOST_DETECTED, countVirtualLost); 1126 } 1127 return devicesStates; 1128 } 1129 1130 /** Helper Thread that ensures host_log is reported in case of killed JVM */ 1131 private class ReportHostLog extends Thread { 1132 1133 private ITestInvocationListener mListener; 1134 private IConfiguration mConfiguration; 1135 ReportHostLog(ITestInvocationListener listener, IConfiguration config)1136 public ReportHostLog(ITestInvocationListener listener, IConfiguration config) { 1137 mListener = listener; 1138 mConfiguration = config; 1139 } 1140 1141 @Override run()1142 public void run() { 1143 // Report all the logs that always be reported anyway. 1144 reportHostLog(mListener, mConfiguration); 1145 } 1146 } 1147 1148 /** Helper Thread to ensure invocation files are deleted in case of killed JVM */ 1149 private class CleanUpInvocationFiles extends Thread { 1150 1151 private TestInformation mTestInfo; 1152 private IConfiguration mConfig; 1153 CleanUpInvocationFiles(TestInformation currentInfo, IConfiguration config)1154 public CleanUpInvocationFiles(TestInformation currentInfo, IConfiguration config) { 1155 mTestInfo = currentInfo; 1156 mConfig = config; 1157 } 1158 1159 @Override run()1160 public void run() { 1161 deleteInvocationFiles(mTestInfo, mConfig); 1162 } 1163 } 1164 } 1165