| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.tradefed.invoker; |
| |
| import com.android.ddmlib.IDevice; |
| import com.android.ddmlib.Log.LogLevel; |
| import com.android.tradefed.build.BuildInfo; |
| import com.android.tradefed.build.BuildInfoKey; |
| import com.android.tradefed.build.BuildInfoKey.BuildInfoFileKey; |
| import com.android.tradefed.build.BuildRetrievalError; |
| import com.android.tradefed.build.IBuildInfo; |
| import com.android.tradefed.build.IBuildInfo.BuildInfoProperties; |
| import com.android.tradefed.build.IBuildProvider; |
| import com.android.tradefed.build.IDeviceBuildInfo; |
| import com.android.tradefed.build.IDeviceBuildProvider; |
| import com.android.tradefed.config.GlobalConfiguration; |
| import com.android.tradefed.config.IConfiguration; |
| import com.android.tradefed.config.IDeviceConfiguration; |
| import com.android.tradefed.device.DeviceNotAvailableException; |
| import com.android.tradefed.device.ITestDevice; |
| import com.android.tradefed.device.StubDevice; |
| import com.android.tradefed.device.metric.AutoLogCollector; |
| import com.android.tradefed.device.metric.CollectorHelper; |
| import com.android.tradefed.device.metric.IMetricCollector; |
| import com.android.tradefed.device.metric.IMetricCollectorReceiver; |
| import com.android.tradefed.invoker.TestInvocation.Stage; |
| import com.android.tradefed.invoker.logger.InvocationMetricLogger; |
| import com.android.tradefed.invoker.logger.InvocationMetricLogger.InvocationMetricKey; |
| import com.android.tradefed.invoker.shard.IShardHelper; |
| import com.android.tradefed.log.ITestLogger; |
| import com.android.tradefed.log.LogUtil.CLog; |
| import com.android.tradefed.result.ByteArrayInputStreamSource; |
| import com.android.tradefed.result.ITestInvocationListener; |
| import com.android.tradefed.result.ITestLoggerReceiver; |
| import com.android.tradefed.result.InputStreamSource; |
| import com.android.tradefed.result.LogDataType; |
| import com.android.tradefed.retry.IRetryDecision; |
| import com.android.tradefed.retry.RetryLogSaverResultForwarder; |
| import com.android.tradefed.retry.RetryStatistics; |
| import com.android.tradefed.retry.RetryStrategy; |
| import com.android.tradefed.suite.checker.ISystemStatusCheckerReceiver; |
| import com.android.tradefed.targetprep.BuildError; |
| import com.android.tradefed.targetprep.IHostCleaner; |
| import com.android.tradefed.targetprep.ITargetCleaner; |
| import com.android.tradefed.targetprep.ITargetPreparer; |
| import com.android.tradefed.targetprep.TargetSetupError; |
| import com.android.tradefed.targetprep.multi.IMultiTargetPreparer; |
| import com.android.tradefed.testtype.IBuildReceiver; |
| import com.android.tradefed.testtype.IDeviceTest; |
| import com.android.tradefed.testtype.IInvocationContextReceiver; |
| import com.android.tradefed.testtype.IMultiDeviceTest; |
| import com.android.tradefed.testtype.IRemoteTest; |
| import com.android.tradefed.testtype.suite.ITestSuite; |
| import com.android.tradefed.testtype.suite.ModuleListener; |
| import com.android.tradefed.util.CommandResult; |
| import com.android.tradefed.util.CommandStatus; |
| import com.android.tradefed.util.FileUtil; |
| import com.android.tradefed.util.PrettyPrintDelimiter; |
| import com.android.tradefed.util.RunUtil; |
| import com.android.tradefed.util.SystemUtil; |
| import com.android.tradefed.util.SystemUtil.EnvVariable; |
| import com.android.tradefed.util.TimeUtil; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Strings; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.ListIterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.stream.Collectors; |
| |
| /** |
| * Class that describes all the invocation steps: build download, target_prep, run tests, clean up. |
| * Can be extended to override the default behavior of some steps. Order of the steps is driven by |
| * {@link TestInvocation}. |
| */ |
| public class InvocationExecution implements IInvocationExecution { |
| |
| public static final String ADB_VERSION_KEY = "adb_version"; |
| public static final String JAVA_VERSION_KEY = "java_version"; |
| // Track which preparer ran in setup to ensure we don't trigger tearDown if setup was skipped. |
| private Set<IMultiTargetPreparer> mTrackMultiPreparers = null; |
| private Map<String, Set<ITargetPreparer>> mTrackTargetPreparers = null; |
| |
| @Override |
| public boolean fetchBuild( |
| IInvocationContext context, |
| IConfiguration config, |
| IRescheduler rescheduler, |
| ITestInvocationListener listener) |
| throws DeviceNotAvailableException, BuildRetrievalError { |
| String currentDeviceName = null; |
| try { |
| // TODO: evaluate fetching build in parallel |
| for (String deviceName : context.getDeviceConfigNames()) { |
| currentDeviceName = deviceName; |
| IBuildInfo info = null; |
| ITestDevice device = context.getDevice(deviceName); |
| IDeviceConfiguration deviceConfig = config.getDeviceConfigByName(deviceName); |
| IBuildProvider provider = deviceConfig.getBuildProvider(); |
| // Inject the context to the provider if it can receive it |
| if (provider instanceof IInvocationContextReceiver) { |
| ((IInvocationContextReceiver) provider).setInvocationContext(context); |
| } |
| // Get the build |
| if (provider instanceof IDeviceBuildProvider) { |
| // Download a device build if the provider can handle it. |
| info = ((IDeviceBuildProvider) provider).getBuild(device); |
| } else { |
| info = provider.getBuild(); |
| } |
| if (info != null) { |
| info.setDeviceSerial(device.getSerialNumber()); |
| context.addDeviceBuildInfo(deviceName, info); |
| device.setRecovery(deviceConfig.getDeviceRecovery()); |
| } else { |
| CLog.logAndDisplay( |
| LogLevel.WARN, |
| "No build found to test for device: %s", |
| device.getSerialNumber()); |
| IBuildInfo notFoundStub = new BuildInfo(); |
| updateBuild(notFoundStub, config); |
| context.addDeviceBuildInfo(currentDeviceName, notFoundStub); |
| return false; |
| } |
| // TODO: remove build update when reporting is done on context |
| updateBuild(info, config); |
| info.setTestResourceBuild(config.isDeviceConfiguredFake(currentDeviceName)); |
| } |
| } catch (BuildRetrievalError e) { |
| CLog.e(e); |
| if (currentDeviceName != null) { |
| IBuildInfo errorBuild = e.getBuildInfo(); |
| updateBuild(errorBuild, config); |
| context.addDeviceBuildInfo(currentDeviceName, errorBuild); |
| } |
| throw e; |
| } |
| createSharedResources(context); |
| setBinariesVersion(context); |
| return true; |
| } |
| |
| @Override |
| public void cleanUpBuilds(IInvocationContext context, IConfiguration config) { |
| // Ensure build infos are always cleaned up at the end of invocation. |
| for (String cleanUpDevice : context.getDeviceConfigNames()) { |
| if (context.getBuildInfo(cleanUpDevice) != null) { |
| try { |
| config.getDeviceConfigByName(cleanUpDevice) |
| .getBuildProvider() |
| .cleanUp(context.getBuildInfo(cleanUpDevice)); |
| } catch (RuntimeException e) { |
| // We catch an simply log exception in cleanUp to avoid missing any final |
| // step of the invocation. |
| CLog.e(e); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean shardConfig( |
| IConfiguration config, |
| IInvocationContext context, |
| IRescheduler rescheduler, |
| ITestLogger logger) { |
| return createShardHelper().shardConfig(config, context, rescheduler, logger); |
| } |
| |
| /** Create an return the {@link IShardHelper} to be used. */ |
| @VisibleForTesting |
| protected IShardHelper createShardHelper() { |
| return GlobalConfiguration.getInstance().getShardingStrategy(); |
| } |
| |
| @Override |
| public void doSetup( |
| IInvocationContext context, IConfiguration config, final ITestLogger listener) |
| throws TargetSetupError, BuildError, DeviceNotAvailableException { |
| long start = System.currentTimeMillis(); |
| try { |
| // Before all the individual setup, make the multi-pre-target-preparer devices setup |
| runMultiTargetPreparers( |
| config.getMultiPreTargetPreparers(), |
| listener, |
| context, |
| "multi pre target preparer setup"); |
| |
| // TODO: evaluate doing device setup in parallel |
| mTrackTargetPreparers = new HashMap<>(); |
| for (String deviceName : context.getDeviceConfigNames()) { |
| mTrackTargetPreparers.put(deviceName, new HashSet<>()); |
| ITestDevice device = context.getDevice(deviceName); |
| CLog.d("Starting setup for device: '%s'", device.getSerialNumber()); |
| if (device instanceof ITestLoggerReceiver) { |
| ((ITestLoggerReceiver) context.getDevice(deviceName)).setTestLogger(listener); |
| } |
| for (ITargetPreparer preparer : |
| config.getDeviceConfigByName(deviceName).getTargetPreparers()) { |
| // do not call the preparer if it was disabled |
| if (preparer.isDisabled()) { |
| CLog.d("%s has been disabled. skipping.", preparer); |
| continue; |
| } |
| if (preparer instanceof ITestLoggerReceiver) { |
| ((ITestLoggerReceiver) preparer).setTestLogger(listener); |
| } |
| CLog.d( |
| "starting preparer '%s' on device: '%s'", |
| preparer, device.getSerialNumber()); |
| preparer.setUp(device, context.getBuildInfo(deviceName)); |
| mTrackTargetPreparers.get(deviceName).add(preparer); |
| CLog.d( |
| "done with preparer '%s' on device: '%s'", |
| preparer, device.getSerialNumber()); |
| } |
| CLog.d("Done with setup of device: '%s'", device.getSerialNumber()); |
| } |
| // After all the individual setup, make the multi-devices setup |
| runMultiTargetPreparers( |
| config.getMultiTargetPreparers(), |
| listener, |
| context, |
| "multi target preparer setup"); |
| } finally { |
| // Note: These metrics are handled in a try in case of a kernel reset or device issue. |
| // Setup timing metric. It does not include flashing time on boot tests. |
| long setupDuration = System.currentTimeMillis() - start; |
| context.addInvocationTimingMetric(IInvocationContext.TimingEvent.SETUP, setupDuration); |
| InvocationMetricLogger.addInvocationMetrics(InvocationMetricKey.SETUP, setupDuration); |
| CLog.d("Setup duration: %s'", TimeUtil.formatElapsedTime(setupDuration)); |
| // Upload the setup logcat after setup is complete. |
| for (String deviceName : context.getDeviceConfigNames()) { |
| reportLogs(context.getDevice(deviceName), listener, Stage.SETUP); |
| } |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public final void runDevicePreInvocationSetup( |
| IInvocationContext context, IConfiguration config, ITestLogger logger) |
| throws DeviceNotAvailableException, TargetSetupError { |
| for (String deviceName : context.getDeviceConfigNames()) { |
| ITestDevice device = context.getDevice(deviceName); |
| |
| CLog.d("Starting device pre invocation setup for : '%s'", device.getSerialNumber()); |
| if (device instanceof ITestLoggerReceiver) { |
| ((ITestLoggerReceiver) context.getDevice(deviceName)).setTestLogger(logger); |
| } |
| device.preInvocationSetup( |
| context.getBuildInfo(deviceName), |
| context.getBuildInfos() |
| .stream() |
| .filter(buildInfo -> buildInfo.isTestResourceBuild()) |
| .collect(Collectors.toList())); |
| } |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public final void runDevicePostInvocationTearDown( |
| IInvocationContext context, IConfiguration config, Throwable exception) { |
| // Extra tear down step for the device |
| for (String deviceName : context.getDeviceConfigNames()) { |
| ITestDevice device = context.getDevice(deviceName); |
| device.postInvocationTearDown(exception); |
| } |
| } |
| |
| /** Runs the {@link IMultiTargetPreparer} specified. */ |
| private void runMultiTargetPreparers( |
| List<IMultiTargetPreparer> multiPreparers, |
| ITestLogger logger, |
| IInvocationContext context, |
| String description) |
| throws TargetSetupError, BuildError, DeviceNotAvailableException { |
| if (mTrackMultiPreparers == null) { |
| mTrackMultiPreparers = new HashSet<>(); |
| } |
| for (IMultiTargetPreparer multiPreparer : multiPreparers) { |
| // do not call the preparer if it was disabled |
| if (multiPreparer.isDisabled()) { |
| CLog.d("%s has been disabled. skipping.", multiPreparer); |
| continue; |
| } |
| if (multiPreparer instanceof ITestLoggerReceiver) { |
| ((ITestLoggerReceiver) multiPreparer).setTestLogger(logger); |
| } |
| CLog.d("Starting %s '%s'", description, multiPreparer); |
| multiPreparer.setUp(context); |
| mTrackMultiPreparers.add(multiPreparer); |
| CLog.d("done with %s '%s'", description, multiPreparer); |
| } |
| } |
| |
| /** Runs the {@link IMultiTargetPreparer} specified tearDown. */ |
| private Throwable runMultiTargetPreparersTearDown( |
| List<IMultiTargetPreparer> multiPreparers, |
| IInvocationContext context, |
| ITestLogger logger, |
| Throwable throwable, |
| String description) |
| throws Throwable { |
| ListIterator<IMultiTargetPreparer> iterator = |
| multiPreparers.listIterator(multiPreparers.size()); |
| Throwable deferredThrowable = null; |
| |
| while (iterator.hasPrevious()) { |
| IMultiTargetPreparer multipreparer = iterator.previous(); |
| if (multipreparer.isDisabled() || multipreparer.isTearDownDisabled()) { |
| CLog.d("%s has been disabled. skipping.", multipreparer); |
| continue; |
| } |
| if (mTrackMultiPreparers == null || !mTrackMultiPreparers.contains(multipreparer)) { |
| CLog.d("%s didn't run setUp, skipping tearDown.", multipreparer); |
| continue; |
| } |
| if (multipreparer instanceof ITestLoggerReceiver) { |
| ((ITestLoggerReceiver) multipreparer).setTestLogger(logger); |
| } |
| CLog.d("Starting %s '%s'", description, multipreparer); |
| try { |
| multipreparer.tearDown(context, throwable); |
| } catch (Throwable t) { |
| // We catch it and rethrow later to allow each multi_targetprep to be attempted. |
| // Only the first one will be thrown but all should be logged. |
| CLog.e("Deferring throw for:"); |
| CLog.e(t); |
| if (deferredThrowable == null) { |
| deferredThrowable = t; |
| } |
| } |
| CLog.d("Done with %s '%s'", description, multipreparer); |
| } |
| |
| return deferredThrowable; |
| } |
| |
| @Override |
| public void doTeardown( |
| IInvocationContext context, |
| IConfiguration config, |
| ITestLogger logger, |
| Throwable exception) |
| throws Throwable { |
| Throwable deferredThrowable = null; |
| |
| List<IMultiTargetPreparer> multiPreparers = config.getMultiTargetPreparers(); |
| deferredThrowable = |
| runMultiTargetPreparersTearDown( |
| multiPreparers, |
| context, |
| logger, |
| exception, |
| "multi target preparer teardown"); |
| |
| // Clear wifi settings, to prevent wifi errors from interfering with teardown process. |
| for (String deviceName : context.getDeviceConfigNames()) { |
| ITestDevice device = context.getDevice(deviceName); |
| device.clearLastConnectedWifiNetwork(); |
| List<ITargetPreparer> preparers = |
| config.getDeviceConfigByName(deviceName).getTargetPreparers(); |
| ListIterator<ITargetPreparer> itr = preparers.listIterator(preparers.size()); |
| while (itr.hasPrevious()) { |
| ITargetPreparer preparer = itr.previous(); |
| if (preparer instanceof ITargetCleaner) { |
| ITargetCleaner cleaner = (ITargetCleaner) preparer; |
| // do not call the cleaner if it was disabled |
| if (cleaner.isDisabled() || cleaner.isTearDownDisabled()) { |
| CLog.d("%s has been disabled. skipping.", cleaner); |
| continue; |
| } |
| if (mTrackTargetPreparers == null |
| || !mTrackTargetPreparers.containsKey(deviceName) |
| || !mTrackTargetPreparers.get(deviceName).contains(cleaner)) { |
| CLog.d("%s didn't run setUp, skipping tearDown.", cleaner); |
| continue; |
| } |
| // If setup hit a targetSetupError, the setUp() and setTestLogger might not have |
| // run, ensure we still have the logger. |
| if (preparer instanceof ITestLoggerReceiver) { |
| ((ITestLoggerReceiver) preparer).setTestLogger(logger); |
| } |
| try { |
| CLog.d( |
| "starting tearDown '%s' on device: '%s'", |
| preparer, device.getSerialNumber()); |
| cleaner.tearDown(device, context.getBuildInfo(deviceName), exception); |
| CLog.d( |
| "done with tearDown '%s' on device: '%s'", |
| preparer, device.getSerialNumber()); |
| } catch (Throwable e) { |
| // We catch it and rethrow later to allow each targetprep to be attempted. |
| // Only the first one will be thrown but all should be logged. |
| CLog.e("Deferring throw for:"); |
| CLog.e(e); |
| if (deferredThrowable == null) { |
| deferredThrowable = e; |
| } |
| } |
| } |
| } |
| } |
| |
| // Extra tear down step for the device |
| runDevicePostInvocationTearDown(context, config, exception); |
| |
| // After all, run the multi_pre_target_preparer tearDown. |
| List<IMultiTargetPreparer> multiPrePreparers = config.getMultiPreTargetPreparers(); |
| Throwable preTargetTearDownException = |
| runMultiTargetPreparersTearDown( |
| multiPrePreparers, |
| context, |
| logger, |
| exception, |
| "multi pre target preparer teardown"); |
| if (deferredThrowable == null) { |
| deferredThrowable = preTargetTearDownException; |
| } |
| |
| // Collect adb logs. |
| logHostAdb(logger); |
| |
| if (deferredThrowable != null) { |
| throw deferredThrowable; |
| } |
| } |
| |
| @Override |
| public void doCleanUp(IInvocationContext context, IConfiguration config, Throwable exception) { |
| for (String deviceName : context.getDeviceConfigNames()) { |
| List<ITargetPreparer> preparers = |
| config.getDeviceConfigByName(deviceName).getTargetPreparers(); |
| ListIterator<ITargetPreparer> itr = preparers.listIterator(preparers.size()); |
| while (itr.hasPrevious()) { |
| ITargetPreparer preparer = itr.previous(); |
| if (preparer instanceof IHostCleaner) { |
| IHostCleaner cleaner = (IHostCleaner) preparer; |
| if (preparer.isDisabled() || preparer.isTearDownDisabled()) { |
| CLog.d("%s has been disabled. skipping.", cleaner); |
| continue; |
| } |
| cleaner.cleanUp(context.getBuildInfo(deviceName), exception); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void runTests( |
| IInvocationContext context, IConfiguration config, ITestInvocationListener listener) |
| throws Throwable { |
| List<IRemoteTest> remainingTests = new ArrayList<>(config.getTests()); |
| UnexecutedTestReporterThread reporterThread = |
| new UnexecutedTestReporterThread(listener, remainingTests); |
| Runtime.getRuntime().addShutdownHook(reporterThread); |
| TestInvocation.printStageDelimiter(Stage.TEST, false); |
| try { |
| for (IRemoteTest test : config.getTests()) { |
| // For compatibility of those receivers, they are assumed to be single device alloc. |
| if (test instanceof IDeviceTest) { |
| ((IDeviceTest) test).setDevice(context.getDevices().get(0)); |
| } |
| if (test instanceof IBuildReceiver) { |
| ((IBuildReceiver) test) |
| .setBuild(context.getBuildInfo(context.getDevices().get(0))); |
| } |
| if (test instanceof ISystemStatusCheckerReceiver) { |
| ((ISystemStatusCheckerReceiver) test) |
| .setSystemStatusChecker(config.getSystemStatusCheckers()); |
| } |
| |
| // TODO: consider adding receivers for only the list of ITestDevice and IBuildInfo. |
| if (test instanceof IMultiDeviceTest) { |
| ((IMultiDeviceTest) test).setDeviceInfos(context.getDeviceBuildMap()); |
| } |
| if (test instanceof IInvocationContextReceiver) { |
| ((IInvocationContextReceiver) test).setInvocationContext(context); |
| } |
| |
| updateAutoCollectors(config); |
| |
| IRetryDecision decision = config.getRetryDecision(); |
| // Handle the no-retry use case |
| if (!decision.isAutoRetryEnabled() |
| || RetryStrategy.NO_RETRY.equals(decision.getRetryStrategy()) |
| || test instanceof ITestSuite) { |
| runTest(config, context, listener, test); |
| remainingTests.remove(test); |
| continue; |
| } |
| |
| ModuleListener mainGranularRunListener = new ModuleListener(null); |
| RetryLogSaverResultForwarder runListener = |
| initializeListeners(config, listener, mainGranularRunListener); |
| runTest(config, context, runListener, test); |
| remainingTests.remove(test); |
| runListener.incrementAttempt(); |
| |
| // Avoid entering the loop if no retry to be done. |
| if (!decision.shouldRetry( |
| test, 0, mainGranularRunListener.getTestRunForAttempts(0))) { |
| continue; |
| } |
| |
| long startTime = System.currentTimeMillis(); |
| try { |
| PrettyPrintDelimiter.printStageDelimiter("Starting auto-retry"); |
| for (int attemptNumber = 1; |
| attemptNumber < decision.getMaxRetryCount(); |
| attemptNumber++) { |
| boolean retry = |
| decision.shouldRetry( |
| test, |
| attemptNumber - 1, |
| mainGranularRunListener.getTestRunForAttempts( |
| attemptNumber - 1)); |
| if (!retry) { |
| continue; |
| } |
| CLog.d("auto-retry attempt number '%s'", attemptNumber); |
| // Run the tests again |
| runTest(config, context, runListener, test); |
| runListener.incrementAttempt(); |
| } |
| // Feed the last attempt if we reached here. |
| decision.addLastAttempt( |
| mainGranularRunListener.getTestRunForAttempts( |
| decision.getMaxRetryCount() - 1)); |
| } finally { |
| RetryStatistics retryStats = decision.getRetryStatistics(); |
| // Track how long we spend in retry |
| retryStats.mRetryTime = System.currentTimeMillis() - startTime; |
| addRetryTime(retryStats.mRetryTime); |
| } |
| } |
| } finally { |
| TestInvocation.printStageDelimiter(Stage.TEST, true); |
| // TODO: Look if this can be improved to DeviceNotAvailableException too. |
| Runtime.getRuntime().removeShutdownHook(reporterThread); |
| } |
| |
| } |
| |
| @Override |
| public boolean resetBuildAndReschedule( |
| Throwable exception, |
| ITestInvocationListener listener, |
| IConfiguration config, |
| IInvocationContext context) { |
| if (!(exception instanceof BuildError) && !(exception.getCause() instanceof BuildError)) { |
| for (String deviceName : context.getDeviceConfigNames()) { |
| config.getDeviceConfigByName(deviceName) |
| .getBuildProvider() |
| .buildNotTested(context.getBuildInfo(deviceName)); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public void reportLogs(ITestDevice device, ITestLogger listener, Stage stage) { |
| if (device == null) { |
| return; |
| } |
| IDevice idevice = device.getIDevice(); |
| // non stub device |
| if (!(idevice instanceof StubDevice)) { |
| try (InputStreamSource logcatSource = device.getLogcat()) { |
| device.clearLogcat(); |
| String name = |
| String.format( |
| "%s_%s", |
| TestInvocation.getDeviceLogName(stage), device.getSerialNumber()); |
| listener.testLog(name, LogDataType.LOGCAT, logcatSource); |
| } |
| } |
| // emulator logs |
| if (idevice != null && idevice.isEmulator()) { |
| try (InputStreamSource emulatorOutput = device.getEmulatorOutput()) { |
| // TODO: Clear the emulator log |
| String name = TestInvocation.getEmulatorLogName(stage); |
| listener.testLog(name, LogDataType.TEXT, emulatorOutput); |
| } |
| } |
| } |
| |
| /** Helper to create the test tag from the configuration. */ |
| private String getTestTag(IConfiguration config) { |
| String testTag = config.getCommandOptions().getTestTag(); |
| if (config.getCommandOptions().getTestTagSuffix() != null) { |
| testTag = |
| String.format("%s-%s", testTag, config.getCommandOptions().getTestTagSuffix()); |
| } |
| return testTag; |
| } |
| |
| /** Handle setting the test tag on the build info. */ |
| protected void setTestTag(IBuildInfo info, IConfiguration config) { |
| // When CommandOption is set, it overrides any test-tag from build_providers |
| if (!"stub".equals(config.getCommandOptions().getTestTag())) { |
| info.setTestTag(getTestTag(config)); |
| } else if (Strings.isNullOrEmpty(info.getTestTag())) { |
| // We ensure that that a default test-tag is always available. |
| info.setTestTag("stub"); |
| } else { |
| CLog.w( |
| "Using the test-tag from the build_provider. Consider updating your config to" |
| + " have no alias/namespace in front of test-tag."); |
| } |
| } |
| |
| /** |
| * Update the {@link IBuildInfo} with additional info from the {@link IConfiguration}. |
| * |
| * @param info the {@link IBuildInfo} |
| * @param config the {@link IConfiguration} |
| */ |
| void updateBuild(IBuildInfo info, IConfiguration config) { |
| if (config.getCommandLine() != null) { |
| // TODO: obfuscate the password if any. |
| info.addBuildAttribute(TestInvocation.COMMAND_ARGS_KEY, config.getCommandLine()); |
| } |
| if (config.getCommandOptions().getShardCount() != null) { |
| info.addBuildAttribute( |
| "shard_count", config.getCommandOptions().getShardCount().toString()); |
| } |
| if (config.getCommandOptions().getShardIndex() != null) { |
| info.addBuildAttribute( |
| "shard_index", config.getCommandOptions().getShardIndex().toString()); |
| } |
| setTestTag(info, config); |
| |
| if (info.getProperties().contains(BuildInfoProperties.DO_NOT_LINK_TESTS_DIR)) { |
| CLog.d("Skip linking external directory as FileProperty was set."); |
| return; |
| } |
| // Load environment tests dir. |
| if (info instanceof IDeviceBuildInfo) { |
| File testsDir = ((IDeviceBuildInfo) info).getTestsDir(); |
| if (testsDir != null && testsDir.exists()) { |
| handleLinkingExternalDirs( |
| (IDeviceBuildInfo) info, |
| testsDir, |
| EnvVariable.ANDROID_TARGET_OUT_TESTCASES, |
| BuildInfoFileKey.TARGET_LINKED_DIR.getFileKey()); |
| handleLinkingExternalDirs( |
| (IDeviceBuildInfo) info, |
| testsDir, |
| EnvVariable.ANDROID_HOST_OUT_TESTCASES, |
| BuildInfoFileKey.HOST_LINKED_DIR.getFileKey()); |
| } |
| } |
| } |
| |
| private void runTest( |
| IConfiguration config, |
| IInvocationContext context, |
| ITestInvocationListener listener, |
| IRemoteTest test) |
| throws DeviceNotAvailableException { |
| // We clone the collectors for each IRemoteTest to ensure no state conflicts. |
| List<IMetricCollector> clonedCollectors = new ArrayList<>(); |
| // Add automated collectors |
| for (AutoLogCollector auto : config.getCommandOptions().getAutoLogCollectors()) { |
| clonedCollectors.add(auto.getInstanceForValue()); |
| } |
| |
| // Add the collector from the configuration |
| clonedCollectors.addAll(CollectorHelper.cloneCollectors(config.getMetricCollectors())); |
| if (test instanceof IMetricCollectorReceiver) { |
| ((IMetricCollectorReceiver) test).setMetricCollectors(clonedCollectors); |
| // If test can receive collectors then let it handle the how to set them up |
| test.run(listener); |
| } else { |
| // Wrap collectors in each other and collection will be sequential, do this in the |
| // loop to ensure they are always initialized against the right context. |
| ITestInvocationListener listenerWithCollectors = listener; |
| for (IMetricCollector collector : clonedCollectors) { |
| if (collector.isDisabled()) { |
| CLog.d("%s has been disabled. Skipping.", collector); |
| } else { |
| listenerWithCollectors = collector.init(context, listenerWithCollectors); |
| } |
| } |
| test.run(listenerWithCollectors); |
| } |
| } |
| |
| private RetryLogSaverResultForwarder initializeListeners( |
| IConfiguration config, |
| ITestInvocationListener mainListener, |
| ITestInvocationListener mainGranularLevelListener) { |
| List<ITestInvocationListener> currentTestListeners = new ArrayList<>(); |
| currentTestListeners.add(mainGranularLevelListener); |
| currentTestListeners.add(mainListener); |
| return new RetryLogSaverResultForwarder(config.getLogSaver(), currentTestListeners) { |
| @Override |
| public void testLog( |
| String dataName, LogDataType dataType, InputStreamSource dataStream) { |
| // We know for sure that the sub-listeners are LogSaverResultForwarder |
| // so we delegate to them to save and generate the logAssociation. |
| testLogForward(dataName, dataType, dataStream); |
| } |
| }; |
| } |
| |
| private void addRetryTime(long retryTimeMs) { |
| long totalRetryMs = retryTimeMs; |
| String retryTime = |
| InvocationMetricLogger.getInvocationMetrics() |
| .get(InvocationMetricKey.AUTO_RETRY_TIME.toString()); |
| if (retryTime != null) { |
| totalRetryMs += Long.parseLong(retryTime) + retryTimeMs; |
| } |
| InvocationMetricLogger.addInvocationMetrics( |
| InvocationMetricKey.AUTO_RETRY_TIME, Long.toString(totalRetryMs)); |
| } |
| |
| private void handleLinkingExternalDirs( |
| IDeviceBuildInfo info, File testsDir, EnvVariable var, String baseName) { |
| File externalDir = getExternalTestCasesDirs(var); |
| if (externalDir == null) { |
| String path = SystemUtil.ENV_VARIABLE_PATHS_IN_TESTS_DIR.get(var); |
| File varDir = FileUtil.getFileForPath(testsDir, path); |
| if (varDir.exists()) { |
| // If we found a dir already in the tests dir we keep track of it |
| info.setFile( |
| baseName, |
| varDir, |
| /** version */ |
| "v1"); |
| } |
| return; |
| } |
| try { |
| // Avoid conflict by creating a randomized name for the arriving symlink file. |
| File subDir = FileUtil.createTempDir(baseName, testsDir); |
| subDir.delete(); |
| FileUtil.symlinkFile(externalDir, subDir); |
| // Tag the dir in the build info to be possibly cleaned. |
| info.setFile( |
| baseName, |
| subDir, |
| /** version */ |
| "v1"); |
| // Ensure we always delete the linking, no matter how the JVM exits. |
| subDir.deleteOnExit(); |
| } catch (IOException e) { |
| CLog.e("Failed to load external test dir %s. Ignoring it.", externalDir); |
| CLog.e(e); |
| } |
| } |
| |
| /** Populate the shared resources directory for all non-resource build */ |
| private void createSharedResources(IInvocationContext context) { |
| List<IBuildInfo> infos = context.getBuildInfos(); |
| if (infos.size() <= 1) { |
| return; |
| } |
| try { |
| File resourcesDir = null; |
| for (IBuildInfo info : infos) { |
| if (info.isTestResourceBuild()) { |
| if (resourcesDir == null) { |
| resourcesDir = FileUtil.createTempDir("invocation-resources-dir"); |
| } |
| // Create a reception sub-folder for each build info resource to avoid mixing |
| String name = |
| String.format( |
| "%s_%s_%s", |
| info.getBuildBranch(), |
| info.getBuildId(), |
| info.getBuildFlavor()); |
| File buildDir = FileUtil.createTempDir(name, resourcesDir); |
| for (BuildInfoFileKey key : BuildInfoKey.SHARED_KEY) { |
| File f = info.getFile(key); |
| if (f == null) { |
| continue; |
| } |
| File subDir = new File(buildDir, f.getName()); |
| FileUtil.symlinkFile(f, subDir); |
| } |
| } |
| } |
| if (resourcesDir == null) { |
| return; |
| } |
| // Only set the shared dir on real build if it exists. |
| CLog.d("Creating shared resources directory."); |
| for (IBuildInfo info : infos) { |
| if (!info.isTestResourceBuild()) { |
| info.setFile(BuildInfoFileKey.SHARED_RESOURCE_DIR, resourcesDir, "v1"); |
| } |
| } |
| } catch (IOException e) { |
| CLog.e("Failed to create the shared resources dir."); |
| CLog.e(e); |
| } |
| } |
| |
| private void setBinariesVersion(IInvocationContext context) { |
| String version = getAdbVersion(); |
| if (version != null) { |
| context.addInvocationAttribute(ADB_VERSION_KEY, version); |
| } |
| String javaVersion = System.getProperty("java.version"); |
| if (javaVersion != null && !javaVersion.isEmpty()) { |
| context.addInvocationAttribute(JAVA_VERSION_KEY, javaVersion); |
| } |
| } |
| |
| /** Convert the legacy *-on-failure options to the new auto-collect. */ |
| private void updateAutoCollectors(IConfiguration config) { |
| if (config.getCommandOptions().captureScreenshotOnFailure()) { |
| config.getCommandOptions() |
| .getAutoLogCollectors() |
| .add(AutoLogCollector.SCREENSHOT_ON_FAILURE); |
| } |
| if (config.getCommandOptions().captureLogcatOnFailure()) { |
| config.getCommandOptions() |
| .getAutoLogCollectors() |
| .add(AutoLogCollector.LOGCAT_ON_FAILURE); |
| } |
| } |
| |
| /** Collect the logs from $TMPDIR/adb.$UID.log. */ |
| @VisibleForTesting |
| void logHostAdb(ITestLogger logger) { |
| String tmpDir = "/tmp"; |
| if (System.getenv("TMPDIR") != null) { |
| tmpDir = System.getenv("TMPDIR"); |
| } |
| CommandResult uidRes = |
| RunUtil.getDefault() |
| .runTimedCmd(60000, "id", "-u", System.getProperty("user.name")); |
| if (!CommandStatus.SUCCESS.equals(uidRes.getStatus())) { |
| CLog.e("Failed to collect UID for adb logs: %s", uidRes.getStderr()); |
| return; |
| } |
| String uid = uidRes.getStdout().trim(); |
| File adbLog = new File(tmpDir, String.format("adb.%s.log", uid)); |
| if (!adbLog.exists()) { |
| CLog.i("Did not find adb log file: %s, upload skipped.", adbLog); |
| return; |
| } |
| CommandResult truncAdb = |
| RunUtil.getDefault() |
| .runTimedCmd(60000, "tail", "--bytes=10MB", adbLog.getAbsolutePath()); |
| if (!CommandStatus.SUCCESS.equals(truncAdb.getStatus())) { |
| CLog.e("Fail to truncate the adb log: %s\n%s", adbLog, truncAdb.getStderr()); |
| return; |
| } |
| try (InputStreamSource source = |
| new ByteArrayInputStreamSource(truncAdb.getStdout().getBytes())) { |
| logger.testLog("host_adb_log", LogDataType.TEXT, source); |
| } |
| } |
| |
| /** Returns the external directory coming from the environment. */ |
| @VisibleForTesting |
| File getExternalTestCasesDirs(EnvVariable envVar) { |
| return SystemUtil.getExternalTestCasesDir(envVar); |
| } |
| |
| /** Returns the adb version in use for the invocation. */ |
| protected String getAdbVersion() { |
| return GlobalConfiguration.getDeviceManagerInstance().getAdbVersion(); |
| } |
| } |